package geniusweb.partiesserver.repository; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import geniusweb.actions.PartyId; import tudelft.utilities.listener.DefaultListenable; import tudelft.utilities.logging.ReportToLogger; import tudelft.utilities.logging.Reporter; import tudelft.utilities.repository.NoResourcesNowException; import tudelft.utilities.repository.Repository; /** * RunningPartiesRepo stores all currently running parties. Listeners are * notified when a party is added or removed. */ public class RunningPartiesRepo extends DefaultListenable implements Repository { /** * List of currently running parties. */ public static final int MAX_SLOTS = 16; private static final RunningPartiesRepo instance = new RunningPartiesRepo( new ReportToLogger("partiesserver")); private static final long MAXWAIT_MS = 600000; // 10 minutes private final Reporter log; // not static to facilitate testing. private final Map runningParties = new ConcurrentHashMap<>(); /** * singleton pattern. Do not call this but use {@link #instance()}. Testers * can still use this (instead of {@link #instance()) to get a fresn empty * runningParties map. We need singleton pattern because tomcat acts as a * black box calling us from "nowhere" (it works with annotations). */ RunningPartiesRepo(Reporter reporter) { this.log = reporter; } public static RunningPartiesRepo instance() { return instance; } @Override public Collection list() { return Collections.unmodifiableCollection(runningParties.values()); } @Override public RunningParty get(PartyId id) { return runningParties.get(id); } @Override public void put(RunningParty newParty) throws NoResourcesNowException { PartyId id = newParty.getID(); synchronized (runningParties) { if (runningParties.containsKey(id)) { throw new IllegalArgumentException( "Party " + newParty + " already in the repository"); } if (availableSlots() <= 0) { Date date = estimateCleanupTime(); System.out.println("AAGHG!!" + runningParties.size()); throw new NoResourcesNowException( "There are currently no free slots to register party " + newParty, date); } runningParties.put(id, newParty); } notifyListeners(id); } @Override public void replace(RunningParty newParty) { PartyId id = newParty.getID(); synchronized (runningParties) { if (!runningParties.containsKey(id)) { throw new IllegalArgumentException( "Party " + newParty + " is not in the repository"); } runningParties.put(id, newParty); } notifyListeners(id); } /** * @return an estimate of when the repo will be cleaned up (party gets * terminated). Returns at most {@link #MAXWAIT_MS}. May return time * before NOW if party is was expected to be already terminated. */ public Date estimateCleanupTime() { long now = System.currentTimeMillis(); Date date = new Date(now + MAXWAIT_MS); synchronized (runningParties) { for (RunningParty party : runningParties.values()) { if (party.getEndDate().before(date)) { date = party.getEndDate(); } } } return date; } @Override public void remove(PartyId id) { synchronized (runningParties) { RunningParty party = runningParties.get(id); if (party == null) { return; // no such party is there right now. } runningParties.remove(id); try { party.getParty().terminate(); } catch (Throwable e) { log.log(Level.WARNING, "Party " + id + " did not terminate properly", e); } } notifyListeners(id); } /** * @return the maximum number of slots in this factory. Each slot can run * one party. */ public int maximumSlots() { return MAX_SLOTS; } /** * * @return the currently available number of slots in this factory. Each * slot can run one party. */ public int availableSlots() { synchronized (runningParties) { return MAX_SLOTS - runningParties.size(); } } }