Skip to content

Commit

Permalink
#7 🏎️ Dynamic removal of plug-ins;
Browse files Browse the repository at this point in the history
Basic PoC; need to take thread-safety into account; also need to verify unloading of classes
  • Loading branch information
gunnarmorling committed Apr 29, 2020
1 parent 8b475cf commit 560e6cd
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ public interface LayerBuilder {

LayerBuilder layer(String name);

LayerBuilder layer(String name, String derivedFrom);

Layers build();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@ public interface LayersBuilder {

LayerBuilder layer(String name);

LayerBuilder layer(String name, String derivedFrom);

Layers build();
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ protected Component(String name, List<String> parents) {

public static Component fromLayer(LayerBuilderImpl layer) {
if (layer.getLayerDir() != null) {
return new Plugin(layer.getName(), layer.getLayerDir(), layer.getParents());
return new Plugin(layer.getName(), layer.getDerivedFrom(), layer.getLayerDir(), layer.getParents());
}
else {
return new Layer(layer.getName(), layer.getModuleGavs(), layer.getParents());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ public class LayerBuilderImpl implements LayerBuilder {

private final LayersBuilderImpl layersBuilder;
private final String name;
private final String derivedFrom;
private Path layerDir;
private final List<String> moduleGavs;
private final List<String> parents;

public LayerBuilderImpl(LayersBuilderImpl layersBuilder, String name) {
public LayerBuilderImpl(LayersBuilderImpl layersBuilder, String name, String derivedFrom) {
this.layersBuilder = layersBuilder;
this.name = name;
this.derivedFrom = derivedFrom;
this.moduleGavs = new ArrayList<>();
this.parents = new ArrayList<>();
}
Expand Down Expand Up @@ -61,6 +63,11 @@ public LayerBuilder layer(String name) {
return layersBuilder.layer(name);
}

@Override
public LayerBuilder layer(String name, String derivedFrom) {
return layersBuilder.layer(name, derivedFrom);
}

@Override
public Layers build() {
return layersBuilder.build();
Expand All @@ -74,6 +81,10 @@ public String getName() {
return name;
}

public String getDerivedFrom() {
return derivedFrom;
}

public List<String> getModuleGavs() {
return moduleGavs;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@ public class LayersBuilderImpl implements LayersBuilder {

@Override
public LayerBuilder layer(String name) {
return layer(name, null);
}

@Override
public LayerBuilder layer(String name, String derivedFrom) {
if (currentLayer != null) {
addLayer(currentLayer);
}
currentLayer = new LayerBuilderImpl(this, name);
currentLayer = new LayerBuilderImpl(this, name, derivedFrom);

return currentLayer;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public Layers createLayers(LayersConfig layersConfig, Path layersConfigDir) {
LayersBuilder builder = Layers.builder();
for(Entry<String, Layer> layer : layersConfig.getLayers().entrySet()) {
if (layer.getValue().getDirectory() != null) {
List<String> layerNames = handleDirectoryOfLayers(layer.getValue(), layersConfigDir, builder);
List<String> layerNames = handleDirectoryOfLayers(layer, layersConfigDir, builder);
layerDirsByName.put(layer.getKey(), layerNames);
}
else {
Expand Down Expand Up @@ -74,21 +74,24 @@ private void handleLayer(Entry<String, Layer> layer, Map<String, List<String>> l
}
}

private List<String> handleDirectoryOfLayers(Layer layer,
/**
* Processes a directory of layers, i.e. plug-ins.
*/
private List<String> handleDirectoryOfLayers(Entry<String, Layer> layer,
Path layersConfigDir, LayersBuilder builder) {
Path layersDir = layersConfigDir.resolve(layer.getDirectory()).normalize();
Path layersDir = layersConfigDir.resolve(layer.getValue().getDirectory()).normalize();
if (!Files.isDirectory(layersDir)) {
throw new IllegalArgumentException("Specified layer directory doesn't exist: " + layersDir);
}

ArrayList<String> layerNames = new ArrayList<String>();
List<Path> layerDirs = getLayerDirs(layersDir);
for (Path layerDir : layerDirs) {
LayerBuilder layerBuilder = builder.layer(layerDir.getFileName().toString());
LayerBuilder layerBuilder = builder.layer(layerDir.getFileName().toString(), layer.getKey());

layerBuilder.withModulesIn(layerDir);
layerNames.add(layerDir.getFileName().toString());
for (String parent : layer.getParents()) {
for (String parent : layer.getValue().getParents()) {
layerBuilder.withParent(parent);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
Expand All @@ -43,15 +44,42 @@
import org.jboss.shrinkwrap.resolver.api.maven.Maven;
import org.jboss.shrinkwrap.resolver.api.maven.MavenResolverSystem;
import org.moditect.layrry.Layers;
import org.moditect.layrry.internal.util.FilesHelper;

public class LayersImpl implements Layers {

/**
* The configured components (static layers or plug-ins) by name.
*/
private final Map<String, Component> components;

/**
* The actual module layers by name.
*/
private final Map<String, ModuleLayer> moduleLayers;

/**
* Temporary directory where all plug-ins will be copied to. Modules will be
* sourced from there, allowing to remove plug-ins by deleting their original
* directory.
*/
private final Path pluginsWorkingDir;

private final Map<Path, String> pluginConfigByDirectory;

private int pluginIndex = 0;

public LayersImpl(Map<String, Component> components) {
this.components = Collections.unmodifiableMap(components);
this.moduleLayers = new HashMap<>();
this.moduleLayers = new ConcurrentHashMap<>();
this.pluginConfigByDirectory = new ConcurrentHashMap<>();

try {
this.pluginsWorkingDir = Files.createTempDirectory("layrry-plugins");
}
catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
Expand All @@ -63,7 +91,20 @@ public void run(String main, String... args) {
for(Entry<String, Component> entry : components.entrySet()) {
Component component = entry.getValue();

List<Path> modulePathEntries = getModulePathEntries(component, resolver);
List<Path> modulePathEntries;

if (component.isPlugin()) {
Plugin plugin = (Plugin)component;
Path pluginDir = pluginsWorkingDir.resolve(pluginIndex++ + "-" + plugin.getName());
FilesHelper.copyFolder(plugin.getLayerDir(),pluginDir);
pluginConfigByDirectory.put(plugin.getPluginDir(), plugin.getDerivedFrom());

modulePathEntries = Arrays.asList(pluginDir);
}
else {
modulePathEntries = getModulePathEntries(component, resolver);
}

List<ModuleLayer> parentLayers = getParentLayers(entry.getKey(), component.getParents());
ModuleLayer moduleLayer = createModuleLayer(parentLayers, modulePathEntries);

Expand Down Expand Up @@ -154,7 +195,8 @@ private List<ModuleLayer> getParentLayers(String name, List<String> parents) {

private class Deployer {

private final Method notify;
private final Method notifyOnAddition;
private final Method notifyOnRemoval;
private final Object supportInstance;

public Deployer(Map<Path, List<String>> parentsByPluginDirectory) {
Expand All @@ -163,7 +205,8 @@ public Deployer(Map<Path, List<String>> parentsByPluginDirectory) {
ClassLoader loader = platformLayer.findLoader("org.moditect.layrry.platform");
Class<?> support = loader.loadClass("org.moditect.layrry.platform.internal.PluginLifecycleSupport");
supportInstance = support.newInstance();
notify = support.getDeclaredMethods()[0];
notifyOnAddition = support.getDeclaredMethod("notifyPluginListenersOnAddition", ModuleLayer.class, String.class, ModuleLayer.class);
notifyOnRemoval = support.getDeclaredMethod("notifyPluginListenersOnRemoval", ModuleLayer.class, String.class, ModuleLayer.class);
}
catch(Exception e) {
throw new RuntimeException(e);
Expand All @@ -175,27 +218,38 @@ public Deployer(Map<Path, List<String>> parentsByPluginDirectory) {
executor.execute(() -> {
try (WatchService watch = pluginDirectory.getKey().getFileSystem().newWatchService()) {
pluginDirectory.getKey().register(watch,
StandardWatchEventKinds.ENTRY_CREATE
// StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE
// StandardWatchEventKinds.ENTRY_MODIFY,
// StandardWatchEventKinds.OVERFLOW
);

WatchKey key;
while ((key = watch.take()).isValid()) {
for (WatchEvent<?> event : key.pollEvents()) {
if (!Files.isDirectory((Path) event.context())) {
continue;
}
Path pluginSourceDir = pluginDirectory.getKey().resolve((Path) event.context());

String pluginName = ((Path) event.context()).getFileName().toString();
List<Path> modulePathEntries = Arrays.asList(pluginDirectory.getKey().resolve((Path) event.context()));
List<ModuleLayer> parentLayers = getParentLayers(pluginName, pluginDirectory.getValue());
String derivedFrom = pluginConfigByDirectory.get(pluginDirectory.getKey());
String pluginName = derivedFrom + "-" + ((Path) event.context()).getFileName().toString();

ModuleLayer moduleLayer = createModuleLayer(parentLayers, modulePathEntries);
if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
Path pluginDir = pluginsWorkingDir.resolve(pluginIndex++ + "-" + pluginName);
FilesHelper.copyFolder(pluginSourceDir, pluginDir);
List<Path> modulePathEntries = Arrays.asList(pluginDir);
List<ModuleLayer> parentLayers = getParentLayers(pluginName, pluginDirectory.getValue());

moduleLayers.put(pluginName, moduleLayer);
deploy(pluginName, moduleLayer);
ModuleLayer moduleLayer = createModuleLayer(parentLayers, modulePathEntries);

moduleLayers.put(pluginName, moduleLayer);
deploy(pluginName, moduleLayer);
}
else {
ModuleLayer pluginLayer = moduleLayers.get(pluginName);
undeploy(pluginName, pluginLayer);
moduleLayers.remove(pluginName);

System.gc();
}
}

key.reset();
Expand Down Expand Up @@ -228,7 +282,19 @@ public void deploy(String pluginName, ModuleLayer pluginLayer) {
// for each existing layer, notify any potential lifecycle listeners about the new layer
for (ModuleLayer moduleLayer : moduleLayers.values()) {
try {
notify.invoke(supportInstance, moduleLayer, pluginName, pluginLayer);
notifyOnAddition.invoke(supportInstance, moduleLayer, pluginName, pluginLayer);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new IllegalArgumentException(e);
}
}
}

public void undeploy(String pluginName, ModuleLayer pluginLayer) {
// for each existing layer, notify any potential lifecycle listeners about the new layer
for (ModuleLayer moduleLayer : moduleLayers.values()) {
try {
notifyOnRemoval.invoke(supportInstance, moduleLayer, pluginName, pluginLayer);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new IllegalArgumentException(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@

public class Plugin extends Component {

private final String derivedFrom;
private final Path layerDir;

public Plugin(String name, Path layerDir, List<String> parents) {
super(name, parents);
public Plugin(String name, String derivedFrom, Path layerDir, List<String> parents) {
super(derivedFrom + "-" + name, parents);
this.derivedFrom = derivedFrom;
this.layerDir = layerDir;
}

public String getDerivedFrom() {
return derivedFrom;
}

public Path getLayerDir() {
return layerDir;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Copyright 2020 The ModiTect authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.moditect.layrry.internal.util;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.stream.Stream;

public class FilesHelper {

private FilesHelper() {
}

public static void copyFolder(Path src, Path dest) {
try (Stream<Path> stream = Files.walk(src)) {
stream.forEach(source -> copy(source, dest.resolve(src.relativize(source))));
}
catch (IOException e) {
throw new RuntimeException(e);
}
}

public static void copy(Path source, Path dest) {
try {
Files.copy(source, dest, StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,20 @@ public void notifyPluginListenersOnAddition(ModuleLayer moduleLayer, String plug
}
}

// notifyPluginListenersOnRemoval
public void notifyPluginListenersOnRemoval(ModuleLayer moduleLayer, String pluginName, ModuleLayer pluginLayer) {
ServiceLoader<PluginLifecycleListener> loader = ServiceLoader.load(moduleLayer, PluginLifecycleListener.class);

PluginDescriptor plugin = new PluginDescriptor(pluginName, pluginLayer);

Iterator<PluginLifecycleListener> listeners = loader.iterator();
while(listeners.hasNext()) {
PluginLifecycleListener listener = listeners.next();

// notify each listener only through it defining layer, but not via other layers
// derived from that
if (listener.getClass().getModule().getLayer().equals(moduleLayer)) {
listener.pluginRemoved(plugin);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public void pluginAdded(PluginDescriptor plugin) {

@Override
public void pluginRemoved(PluginDescriptor plugin) {
moduleLayers.remove(plugin.getName());
}

public static Map<String, ModuleLayer> getModuleLayers() {
Expand Down

0 comments on commit 560e6cd

Please sign in to comment.