001package org.intellimate.izou.activator; 002 003import org.intellimate.izou.util.AddonThreadPoolUser; 004import org.intellimate.izou.util.IdentifiableSet; 005import org.intellimate.izou.util.IzouModule; 006import org.intellimate.izou.identification.IllegalIDException; 007import org.intellimate.izou.main.Main; 008import org.intellimate.izou.security.exceptions.IzouPermissionException; 009 010import java.net.URL; 011import java.util.ArrayList; 012import java.util.Collections; 013import java.util.List; 014import java.util.concurrent.CompletableFuture; 015import java.util.concurrent.ConcurrentHashMap; 016import java.util.concurrent.atomic.AtomicInteger; 017 018/** 019 * The ActivatorManager holds all the Activator-instances and runs them parallel in Threads. 020 * It automatically restarts Activators, which did finish exceptionally, up to 100 times. 021 */ 022@SuppressWarnings("WeakerAccess") 023public class ActivatorManager extends IzouModule implements AddonThreadPoolUser { 024 private final int MAX_CRASH = 100; 025 private final int MAX_PERMISSION_DENIED = 2; 026 IdentifiableSet<ActivatorModel> activatorModels = new IdentifiableSet<>(); 027 ConcurrentHashMap<ActivatorModel, CompletableFuture> futures = new ConcurrentHashMap<>(); 028 ConcurrentHashMap<ActivatorModel, AtomicInteger> crashCounter = new ConcurrentHashMap<>(); 029 ConcurrentHashMap<ActivatorModel, AtomicInteger> permissionDeniedCounter = new ConcurrentHashMap<>(); 030 private List<URL> aspectsOrAffected = Collections.synchronizedList(new ArrayList<>()); 031 032 public ActivatorManager(Main main) { 033 super(main); 034 } 035 036 /** 037 * adds an activator and automatically submits it to the Thread-Pool 038 * @param activatorModel the activator to add 039 * @throws IllegalIDException not yet implemented 040 */ 041 public void addActivator(ActivatorModel activatorModel) throws IllegalIDException { 042 activatorModels.add(activatorModel); 043 crashCounter.put(activatorModel, new AtomicInteger(0)); 044 permissionDeniedCounter.put(activatorModel, new AtomicInteger(0)); 045 submitActivator(activatorModel); 046 } 047 048 /** 049 * removes the activator and stops the Thread 050 * @param activatorModel the activator to remove 051 */ 052 public void removeActivator(ActivatorModel activatorModel) { 053 activatorModels.remove(activatorModel); 054 CompletableFuture remove = futures.remove(activatorModel); 055 if (remove != null) { 056 remove.cancel(true); 057 } 058 crashCounter.remove(activatorModel); 059 permissionDeniedCounter.remove(activatorModel); 060 } 061 062 /** 063 * submits the activator to the ThreadPool 064 * @param activatorModel teh activator to submit 065 */ 066 private void submitActivator(ActivatorModel activatorModel) { 067 CompletableFuture<Void> future = submit(() -> { 068 try { 069 return activatorModel.call(); 070 } catch (Throwable e) { 071 if (e instanceof IzouPermissionException) { 072 error("Activator: " + activatorModel.getID() + " was denied permission.", e); 073 074 // Return null if permission was denied by a permission module, in this case restart 2 times 075 return null; 076 } else if (e instanceof SecurityException) { 077 error("Activator: " + activatorModel.getID() + " was denied access.", e); 078 079 // Return false if access was denied by the security manager, in this case, do not restart 080 return false; 081 } 082 error("Activator: " + activatorModel.getID() + " crashed", e); 083 084 // Return true if the addOn did not crash because of security reasons, restart 100 times 085 return true; 086 } 087 }).thenAccept(restart -> { 088 if (restart != null && restart.equals(false)) { 089 debug("Activator: " + activatorModel.getID() + " returned false, will not restart"); 090 } else if (restart == null) { 091 error("Activator: " + activatorModel.getID() + " returned not true"); 092 if (permissionDeniedCounter.get(activatorModel).get() < MAX_PERMISSION_DENIED) { 093 error("Until now activator: " + activatorModel.getID() + " was restarted: " + 094 permissionDeniedCounter.get(activatorModel).get() + " times, attempting restart."); 095 permissionDeniedCounter.get(activatorModel).incrementAndGet(); 096 submitActivator(activatorModel); 097 } else { 098 error("Activator: " + activatorModel.getID() + " reached permission based restarting limit with " + 099 permissionDeniedCounter.get(activatorModel).get() + " restarts."); 100 } 101 } else { 102 if (crashCounter.get(activatorModel).get() < MAX_CRASH) { 103 error("Until now activator: " + activatorModel.getID() + " was restarted: " + 104 crashCounter.get(activatorModel).get() + " times, attempting restart."); 105 crashCounter.get(activatorModel).incrementAndGet(); 106 submitActivator(activatorModel); 107 } else { 108 error("Activator: " + activatorModel.getID() + " reached crash based restarting limit with " + 109 crashCounter.get(activatorModel).get() + " restarts."); 110 } 111 } 112 }); 113 114 CompletableFuture existing = futures.put(activatorModel, future); 115 if (existing != null && !existing.isDone()) existing.cancel(true); 116 } 117}