Skip to content

Commit

Permalink
First attempt at having the app poll the plugins directory to dynamic…
Browse files Browse the repository at this point in the history
…ally reload plugins. Class unloading occurs when threadpools are shutdown and the correct hooks are triggered, now just a matter of making sure the correct triggers happen at the correct time.
  • Loading branch information
Graham Allan committed Nov 1, 2013
1 parent 7eb6cac commit 42e16ea
Show file tree
Hide file tree
Showing 18 changed files with 284 additions and 243 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.netmelody.cieye.core.observation;

import org.netmelody.cieye.core.domain.CiServerType;

public interface ForeignAgencies {
ObservationAgency agencyFor(CiServerType type);
boolean hasChanged();
}

This file was deleted.

2 changes: 2 additions & 0 deletions src/main/java/org/netmelody/cieye/server/CiSpyHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ public interface CiSpyHandler {
long millisecondsUntilNextUpdate(Feature feature);

boolean takeNoteOf(String targetId, String note);

void endMission();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.netmelody.cieye.server.configuration;

import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.SuffixFileFilter;

public final class PluginDirectory {

private final File directory;
private Date lastReadDate = new Date(0L);

public PluginDirectory(File directory) {
this.directory = directory;
}

public Iterable<File> jars() {
lastReadDate = new Date();
File[] jarFiles = directory.listFiles((FileFilter)new SuffixFileFilter(".jar"));
return jarFiles == null ? Collections.<File>emptySet() : Arrays.asList(jarFiles);
}

public boolean updateAvailable() {
return FileUtils.isFileNewer(directory, lastReadDate);
}

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package org.netmelody.cieye.server.observation;
package org.netmelody.cieye.server.configuration;

import java.util.ResourceBundle;

import org.netmelody.cieye.core.domain.CiServerType;
import org.netmelody.cieye.core.observation.ForeignAgencies;
import org.netmelody.cieye.core.observation.ObservationAgency;

public final class ResourceBundleObservationAgencyConfiguration implements ObservationAgencyConfiguration {
public final class ResourceBundleObservationAgencyConfiguration implements ForeignAgencies {

private static final ResourceBundle AGENCY_CONFIGURATION = ResourceBundle.getBundle(ResourceBundleObservationAgencyConfiguration.class.getName());

Expand All @@ -25,4 +26,9 @@ public ObservationAgency agencyFor(CiServerType type) {
throw new IllegalStateException("Failed to load CI Observation Module for " + typeName, e);
}
}

@Override
public boolean hasChanged() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.netmelody.cieye.core.observation.ForeignAgents;
import org.netmelody.cieye.core.observation.ForeignAgencies;
import org.netmelody.cieye.core.observation.KnownOffendersDirectory;
import org.netmelody.cieye.server.CiEyeServerInformationFetcher;
import org.netmelody.cieye.server.LandscapeFetcher;
Expand All @@ -17,7 +17,7 @@ public final class ServerConfiguration {
private final ServerInformation information = new ServerInformation(settings.settingsLocation());
private final RecordedKnownOffenders detective = new RecordedKnownOffenders(settings.picturesFile());
private final RecordedObservationTargets targets = new RecordedObservationTargets(settings.viewsFile());
private final RecordedForeignAgents foreignAgents = new RecordedForeignAgents(settings.pluginDirectory());
private final ServiceLoadingRecordedForeignAgencies foreignAgencies = new ServiceLoadingRecordedForeignAgencies(settings.pluginDirectory());
private final Album album = new Album(settings.picturesDirectory());

private static final class Refresher implements Runnable {
Expand All @@ -32,9 +32,10 @@ public void run() {
}

public ServerConfiguration() {
final ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
final ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
executor.scheduleWithFixedDelay(new Refresher(detective), 1L, 10L, TimeUnit.SECONDS);
executor.scheduleWithFixedDelay(new Refresher(targets), 1L, 10L, TimeUnit.SECONDS);
executor.scheduleWithFixedDelay(new Refresher(foreignAgencies), 1L, 10L, TimeUnit.SECONDS);
}

public KnownOffendersDirectory detective() {
Expand All @@ -53,7 +54,7 @@ public PictureFetcher album() {
return album;
}

public ForeignAgents foreignAgents() {
return foreignAgents;
public ForeignAgencies foreignAgents() {
return foreignAgencies;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.netmelody.cieye.server.configuration;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Date;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import org.netmelody.cieye.core.domain.CiServerType;
import org.netmelody.cieye.core.logging.LogKeeper;
import org.netmelody.cieye.core.logging.Logbook;
import org.netmelody.cieye.core.observation.ForeignAgencies;
import org.netmelody.cieye.core.observation.ObservationAgency;

import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

public final class ServiceLoadingRecordedForeignAgencies implements ForeignAgencies, Refreshable {
private final static Logbook LOGBOOK = LogKeeper.logbookFor(ServiceLoadingRecordedForeignAgencies.class);

private final PluginDirectory pluginDirectory;

private final AtomicReference<Date> lastUpdate = new AtomicReference<Date>(new Date(0L));
private final AtomicReference<Date> lastAsked = new AtomicReference<Date>(new Date(0L));

private final AtomicReference<ServiceLoader<ObservationAgency>> services;

public ServiceLoadingRecordedForeignAgencies(PluginDirectory pluginDirectory) {
this.pluginDirectory = pluginDirectory;
this.services = new AtomicReference<ServiceLoader<ObservationAgency>>(newServiceLoader());
}

@Override
public ObservationAgency agencyFor(CiServerType type) {
final String typeName = type.name();
Iterator<ObservationAgency> agencies = available();

while (agencies.hasNext()) {
ObservationAgency agency = agencies.next();
if (agency.canProvideSpyFor(type)) {
return agency;
}
}

throw new IllegalStateException("No CI Observation Module for " + typeName);
}

private ServiceLoader<ObservationAgency> newServiceLoader() {
return ServiceLoader.load(ObservationAgency.class, pluginsClassLoader());
}

private Iterator<ObservationAgency> available() {
return services.get().iterator();
}

private ClassLoader pluginsClassLoader() {
Iterable<File> jarFiles = pluginDirectory.jars();
return new URLClassLoader(FluentIterable.from(urlsOf(jarFiles)).toArray(URL.class));
}

private Set<URL> urlsOf(Iterable<File> jarFiles) {
Set<URL> urls = Sets.newHashSet();
Set<Throwable> problems = Sets.newHashSet();
for (File file : jarFiles) {
try {
urls.add(file.toURI().toURL());
} catch (MalformedURLException e) {
problems.add(e);
}
}

if (!problems.isEmpty()) {
logProblems(problems);
}

return ImmutableSet.copyOf(urls);
}

private static void logProblems(Set<Throwable> problems) {
System.out.printf("Found [%d] problems obtaining plugin jars. See logs for more details.%n", problems.size());
for (Throwable throwable : problems) {
LOGBOOK.error("Error loading plugin.", throwable);
}
}

@Override
public void refresh() {
if (pluginDirectory.updateAvailable()) {
services.set(newServiceLoader());
lastUpdate.set(new Date());
}
}


@Override
public boolean hasChanged() {
Date lastAskedDate = lastAsked.getAndSet(new Date());
return lastUpdate.get().after(lastAskedDate);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public String settingsLocation() {
}
}

public File pluginDirectory() {
return pluginsDir;
public PluginDirectory pluginDirectory() {
return new PluginDirectory(pluginsDir);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import org.netmelody.cieye.core.domain.Feature;
import org.netmelody.cieye.core.observation.CiSpy;
import org.netmelody.cieye.core.observation.CommunicationNetwork;
import org.netmelody.cieye.core.observation.ForeignAgents;
import org.netmelody.cieye.core.observation.ForeignAgencies;
import org.netmelody.cieye.core.observation.KnownOffendersDirectory;
import org.netmelody.cieye.server.CiSpyAllocator;
import org.netmelody.cieye.server.CiSpyHandler;
Expand All @@ -16,9 +16,9 @@

public final class IntelligenceAgency implements CiSpyAllocator {

private final ObservationAgencyConfiguration agencyConfiguration;
private final LoadingCache<Feature, CiSpyHandler> handlers =
CacheBuilder.newBuilder().build(from(new Function<Feature, CiSpyHandler>() {
CacheBuilder.newBuilder()
.build(from(new Function<Feature, CiSpyHandler>() {
@Override
public CiSpyHandler apply(Feature feature) {
return createSpyFor(feature);
Expand All @@ -27,20 +27,31 @@ public CiSpyHandler apply(Feature feature) {

private final CommunicationNetwork network;
private final KnownOffendersDirectory directory;
private final ForeignAgencies foreignAgencies;

public IntelligenceAgency(CommunicationNetwork network, KnownOffendersDirectory directory, ForeignAgents foreignAgents) {
public IntelligenceAgency(CommunicationNetwork network, KnownOffendersDirectory directory, ForeignAgencies foreignAgencies) {
this.network = network;
this.directory = directory;
this.agencyConfiguration = new ServiceLoaderObservationAgencyConfiguration(foreignAgents);
this.foreignAgencies = foreignAgencies;
}

@Override
public CiSpyHandler spyFor(Feature feature) {
if (foreignAgencies.hasChanged()) {
recallSpies();
}
return handlers.getUnchecked(feature);
}

private void recallSpies() {
for (CiSpyHandler ciSpyHandler : handlers.asMap().values()) {
ciSpyHandler.endMission();
}
handlers.invalidateAll();
}

private CiSpyHandler createSpyFor(Feature feature) {
final CiSpy spy = agencyConfiguration.agencyFor(feature.type()).provideSpyFor(feature, network, directory);
return new PollingSpyHandler(spy);
final CiSpy spy = foreignAgencies.agencyFor(feature.type()).provideSpyFor(feature, network, directory);
return new PollingSpyHandler(spy, feature);
}
}

This file was deleted.

Loading

0 comments on commit 42e16ea

Please sign in to comment.