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}