@@ -780,8 +780,10 @@ public abstract class AbstractPluginManager implements PluginManager { | |||
} | |||
// retrieves the plugin descriptor | |||
PluginDescriptorFinder pluginDescriptorFinder = getPluginDescriptorFinder(); | |||
log.debug("Use '{}' to find plugins descriptors", pluginDescriptorFinder); | |||
log.debug("Finding plugin descriptor for plugin '{}'", pluginPath); | |||
PluginDescriptor pluginDescriptor = getPluginDescriptorFinder().find(pluginPath); | |||
PluginDescriptor pluginDescriptor = pluginDescriptorFinder.find(pluginPath); | |||
validatePluginDescriptor(pluginDescriptor); | |||
log.debug("Found descriptor {}", pluginDescriptor); | |||
String pluginClassName = pluginDescriptor.getPluginClass(); |
@@ -0,0 +1,81 @@ | |||
/* | |||
* Copyright 2017 Decebal Suiu | |||
* | |||
* 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.pf4j; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import java.nio.file.Path; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
/** | |||
* @author Decebal Suiu | |||
*/ | |||
public class CompoundPluginDescriptorFinder implements PluginDescriptorFinder { | |||
private static final Logger log = LoggerFactory.getLogger(CompoundPluginDescriptorFinder.class); | |||
private List<PluginDescriptorFinder> finders = new ArrayList<>(); | |||
public CompoundPluginDescriptorFinder add(PluginDescriptorFinder finder) { | |||
if (finder == null) { | |||
throw new IllegalArgumentException("null not allowed"); | |||
} | |||
finders.add(finder); | |||
return this; | |||
} | |||
public int size() { | |||
return finders.size(); | |||
} | |||
@Override | |||
public boolean isApplicable(Path pluginPath) { | |||
for (PluginDescriptorFinder finder : finders) { | |||
if (finder.isApplicable(pluginPath)) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
@Override | |||
public PluginDescriptor find(Path pluginPath) throws PluginException { | |||
for (PluginDescriptorFinder finder : finders) { | |||
if (finder.isApplicable(pluginPath)) { | |||
log.debug("'{}' is applicable for plugin '{}'", finder, pluginPath); | |||
try { | |||
PluginDescriptor pluginDescriptor = finder.find(pluginPath); | |||
if (pluginDescriptor != null) { | |||
return pluginDescriptor; | |||
} | |||
} catch (Exception e) { | |||
// log the exception and continue with the next finder | |||
log.error(e.getMessage()); // ?! | |||
} | |||
} else { | |||
log.debug("'{}' is not applicable for plugin '{}'", finder, pluginPath); | |||
} | |||
} | |||
throw new PluginException("No PluginDescriptorFinder for plugin '{}'", pluginPath); | |||
} | |||
} |
@@ -34,10 +34,8 @@ public class DefaultExtensionFinder implements ExtensionFinder, PluginStateListe | |||
public DefaultExtensionFinder(PluginManager pluginManager) { | |||
this.pluginManager = pluginManager; | |||
finders = new ArrayList<>(); | |||
addExtensionFinder(new LegacyExtensionFinder(pluginManager)); | |||
// addExtensionFinder(new ServiceProviderExtensionFinder(pluginManager)); | |||
add(new LegacyExtensionFinder(pluginManager)); | |||
// add(new ServiceProviderExtensionFinder(pluginManager)); | |||
} | |||
@Override | |||
@@ -70,7 +68,6 @@ public class DefaultExtensionFinder implements ExtensionFinder, PluginStateListe | |||
return extensions; | |||
} | |||
@Override | |||
public Set<String> findClassNames(String pluginId) { | |||
Set<String> classNames = new HashSet<>(); | |||
@@ -91,10 +88,10 @@ public class DefaultExtensionFinder implements ExtensionFinder, PluginStateListe | |||
} | |||
public DefaultExtensionFinder addServiceProviderExtensionFinder() { | |||
return addExtensionFinder(new ServiceProviderExtensionFinder(pluginManager)); | |||
return add(new ServiceProviderExtensionFinder(pluginManager)); | |||
} | |||
public DefaultExtensionFinder addExtensionFinder(ExtensionFinder finder) { | |||
public DefaultExtensionFinder add(ExtensionFinder finder) { | |||
finders.add(finder); | |||
return this; |
@@ -1,64 +0,0 @@ | |||
/* | |||
* Copyright 2013 Decebal Suiu | |||
* | |||
* 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.pf4j; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.nio.file.Paths; | |||
import java.util.jar.Manifest; | |||
/** | |||
* The default implementation for {@link PluginDescriptorFinder}. | |||
* Now, this class it's a "link" to {@link ManifestPluginDescriptorFinder}. | |||
* | |||
* @author Decebal Suiu | |||
*/ | |||
public class DefaultPluginDescriptorFinder extends ManifestPluginDescriptorFinder { | |||
private static final Logger log = LoggerFactory.getLogger(ManifestPluginDescriptorFinder.class); | |||
private PluginClasspath pluginClasspath; | |||
public DefaultPluginDescriptorFinder(PluginClasspath pluginClasspath) { | |||
this.pluginClasspath = pluginClasspath; | |||
} | |||
@Override | |||
public Manifest readManifest(Path pluginPath) throws PluginException { | |||
// TODO it's ok with first classes root? Another idea is to specify in PluginClasspath the folder. | |||
if (pluginClasspath.getClassesDirectories().size() == 0) { | |||
throw new PluginException("Failed to read manifest, no classes folder in classpath"); | |||
} | |||
String classes = pluginClasspath.getClassesDirectories().get(0); | |||
Path manifestPath = pluginPath.resolve(Paths.get(classes,"/META-INF/MANIFEST.MF")); | |||
log.debug("Lookup plugin descriptor in '{}'", manifestPath); | |||
if (Files.notExists(manifestPath)) { | |||
throw new PluginException("Cannot find '{}' path", manifestPath); | |||
} | |||
try (InputStream input = Files.newInputStream(manifestPath)) { | |||
return new Manifest(input); | |||
} catch (IOException e) { | |||
throw new PluginException(e.getMessage(), e); | |||
} | |||
} | |||
} |
@@ -37,6 +37,11 @@ public class DefaultPluginManager extends AbstractPluginManager { | |||
super(); | |||
} | |||
/** | |||
* Use {@link DefaultPluginManager#DefaultPluginManager(Path)}. | |||
* | |||
* @param pluginsDir | |||
*/ | |||
@Deprecated | |||
public DefaultPluginManager(File pluginsDir) { | |||
this(pluginsDir.toPath()); | |||
@@ -46,14 +51,11 @@ public class DefaultPluginManager extends AbstractPluginManager { | |||
super(pluginsRoot); | |||
} | |||
/** | |||
* By default if {@link DefaultPluginManager#isDevelopment()} returns {@code true} | |||
* than a {@link PropertiesPluginDescriptorFinder} is returned | |||
* else this method returns {@link DefaultPluginDescriptorFinder}. | |||
*/ | |||
@Override | |||
protected PluginDescriptorFinder createPluginDescriptorFinder() { | |||
return isDevelopment() ? new PropertiesPluginDescriptorFinder() : new DefaultPluginDescriptorFinder(pluginClasspath); | |||
protected CompoundPluginDescriptorFinder createPluginDescriptorFinder() { | |||
return new CompoundPluginDescriptorFinder() | |||
.add(new PropertiesPluginDescriptorFinder()) | |||
.add(new ManifestPluginDescriptorFinder()); | |||
} | |||
@Override |
@@ -19,15 +19,12 @@ import org.pf4j.util.AndFileFilter; | |||
import org.pf4j.util.DirectoryFileFilter; | |||
import org.pf4j.util.HiddenFilter; | |||
import org.pf4j.util.JarFileFilter; | |||
import org.pf4j.util.NameFileFilter; | |||
import org.pf4j.util.NotFileFilter; | |||
import org.pf4j.util.OrFileFilter; | |||
import org.pf4j.util.NameFileFilter; | |||
import java.io.FileFilter; | |||
import java.io.IOException; | |||
import java.nio.file.Path; | |||
import java.util.jar.JarFile; | |||
import java.util.jar.Manifest; | |||
/** | |||
* It's a {@link PluginManager} that loads plugin from a jar file. | |||
@@ -43,11 +40,6 @@ public class JarPluginManager extends DefaultPluginManager { | |||
return new JarPluginRepository(getPluginsRoot(), isDevelopment()); | |||
} | |||
@Override | |||
protected PluginDescriptorFinder createPluginDescriptorFinder() { | |||
return isDevelopment() ? new PropertiesPluginDescriptorFinder() : new JarPluginDescriptorFinder(); | |||
} | |||
@Override | |||
protected PluginLoader createPluginLoader() { | |||
return new JarPluginLoader(this, pluginClasspath); | |||
@@ -79,19 +71,6 @@ public class JarPluginManager extends DefaultPluginManager { | |||
} | |||
class JarPluginDescriptorFinder extends ManifestPluginDescriptorFinder { | |||
@Override | |||
public Manifest readManifest(Path pluginPath) throws PluginException { | |||
try { | |||
return new JarFile(pluginPath.toFile()).getManifest(); | |||
} catch (IOException e) { | |||
throw new PluginException(e); | |||
} | |||
} | |||
} | |||
class JarPluginLoader extends DefaultPluginLoader { | |||
public JarPluginLoader(PluginManager pluginManager, PluginClasspath pluginClasspath) { |
@@ -15,10 +15,17 @@ | |||
*/ | |||
package org.pf4j; | |||
import org.pf4j.util.FileUtils; | |||
import org.pf4j.util.StringUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.util.jar.Attributes; | |||
import java.util.jar.JarFile; | |||
import java.util.jar.Manifest; | |||
/** | |||
@@ -26,7 +33,14 @@ import java.util.jar.Manifest; | |||
* | |||
* @author Decebal Suiu | |||
*/ | |||
public abstract class ManifestPluginDescriptorFinder implements PluginDescriptorFinder { | |||
public class ManifestPluginDescriptorFinder implements PluginDescriptorFinder { | |||
private static final Logger log = LoggerFactory.getLogger(ManifestPluginDescriptorFinder.class); | |||
@Override | |||
public boolean isApplicable(Path pluginPath) { | |||
return Files.exists(pluginPath) && (Files.isDirectory(pluginPath) || FileUtils.isJarFile(pluginPath)); | |||
} | |||
@Override | |||
public PluginDescriptor find(Path pluginPath) throws PluginException { | |||
@@ -35,7 +49,43 @@ public abstract class ManifestPluginDescriptorFinder implements PluginDescriptor | |||
return createPluginDescriptor(manifest); | |||
} | |||
public abstract Manifest readManifest(Path pluginPath) throws PluginException; | |||
protected Manifest readManifest(Path pluginPath) throws PluginException { | |||
if (FileUtils.isJarFile(pluginPath)) { | |||
try { | |||
Manifest manifest = new JarFile(pluginPath.toFile()).getManifest(); | |||
if (manifest != null) { | |||
return manifest; | |||
} | |||
} catch (IOException e) { | |||
throw new PluginException(e); | |||
} | |||
} | |||
Path manifestPath = getManifestPath(pluginPath); | |||
if (manifestPath == null) { | |||
throw new PluginException("Cannot find the manifest path"); | |||
} | |||
log.debug("Lookup plugin descriptor in '{}'", manifestPath); | |||
if (Files.notExists(manifestPath)) { | |||
throw new PluginException("Cannot find '{}' path", manifestPath); | |||
} | |||
try (InputStream input = Files.newInputStream(manifestPath)) { | |||
return new Manifest(input); | |||
} catch (IOException e) { | |||
throw new PluginException(e); | |||
} | |||
} | |||
protected Path getManifestPath(Path pluginPath) throws PluginException { | |||
if (Files.isDirectory(pluginPath)) { | |||
// legacy (the path is something like "classes/META-INF/MANIFEST.MF") | |||
return FileUtils.findFile(pluginPath,"MANIFEST.MF"); | |||
} | |||
return null; | |||
} | |||
protected PluginDescriptor createPluginDescriptor(Manifest manifest) { | |||
PluginDescriptor pluginDescriptor = createPluginDescriptorInstance(); |
@@ -19,13 +19,22 @@ import java.nio.file.Path; | |||
/** | |||
* Find a plugin descriptor for a plugin path. | |||
* You can find in manifest file {@link DefaultPluginDescriptorFinder}, | |||
* xml file, properties file, java services (with {@link java.util.ServiceLoader}), etc. | |||
* You can find the plugin descriptor in manifest file {@link ManifestPluginDescriptorFinder}, | |||
* properties file {@link PropertiesPluginDescriptorFinder}, xml file, | |||
* java services (with {@link java.util.ServiceLoader}), etc. | |||
* | |||
* @author Decebal Suiu | |||
*/ | |||
public interface PluginDescriptorFinder { | |||
/** | |||
* Returns true if this finder is applicable to the given {@link Path}. | |||
* | |||
* @param pluginPath | |||
* @return | |||
*/ | |||
boolean isApplicable(Path pluginPath); | |||
PluginDescriptor find(Path pluginPath) throws PluginException; | |||
} |
@@ -15,9 +15,10 @@ | |||
*/ | |||
package org.pf4j; | |||
import org.pf4j.util.FileUtils; | |||
import org.pf4j.util.StringUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.pf4j.util.StringUtils; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
@@ -47,7 +48,12 @@ public class PropertiesPluginDescriptorFinder implements PluginDescriptorFinder | |||
this.propertiesFileName = propertiesFileName; | |||
} | |||
@Override | |||
@Override | |||
public boolean isApplicable(Path pluginPath) { | |||
return Files.exists(pluginPath) && (Files.isDirectory(pluginPath) || FileUtils.isJarFile(pluginPath)); | |||
} | |||
@Override | |||
public PluginDescriptor find(Path pluginPath) throws PluginException { | |||
Properties properties = readProperties(pluginPath); | |||
@@ -56,6 +62,10 @@ public class PropertiesPluginDescriptorFinder implements PluginDescriptorFinder | |||
protected Properties readProperties(Path pluginPath) throws PluginException { | |||
Path propertiesPath = getPropertiesPath(pluginPath, propertiesFileName); | |||
if (propertiesPath == null) { | |||
throw new PluginException("Cannot find the properties path"); | |||
} | |||
log.debug("Lookup plugin descriptor in '{}'", propertiesPath); | |||
if (Files.notExists(propertiesPath)) { | |||
throw new PluginException("Cannot find '{}' path", propertiesPath); | |||
@@ -65,14 +75,23 @@ public class PropertiesPluginDescriptorFinder implements PluginDescriptorFinder | |||
try (InputStream input = Files.newInputStream(propertiesPath)) { | |||
properties.load(input); | |||
} catch (IOException e) { | |||
throw new PluginException(e.getMessage(), e); | |||
throw new PluginException(e); | |||
} | |||
return properties; | |||
} | |||
protected Path getPropertiesPath(Path pluginPath, String propertiesFileName) throws PluginException { | |||
return pluginPath.resolve(Paths.get(propertiesFileName)); | |||
if (Files.isDirectory(pluginPath)) { | |||
return pluginPath.resolve(Paths.get(propertiesFileName)); | |||
} else { | |||
// it's a jar file | |||
try { | |||
return FileUtils.getPath(pluginPath, propertiesFileName); | |||
} catch (IOException e) { | |||
throw new PluginException(e); | |||
} | |||
} | |||
} | |||
protected PluginDescriptor createPluginDescriptor(Properties properties) { |
@@ -16,6 +16,7 @@ | |||
package org.pf4j; | |||
import org.pf4j.processor.ServiceProviderExtensionStorage; | |||
import org.pf4j.util.FileUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
@@ -24,8 +25,6 @@ import java.io.Reader; | |||
import java.net.URISyntaxException; | |||
import java.net.URL; | |||
import java.nio.charset.StandardCharsets; | |||
import java.nio.file.FileSystem; | |||
import java.nio.file.FileSystems; | |||
import java.nio.file.FileVisitOption; | |||
import java.nio.file.FileVisitResult; | |||
import java.nio.file.Files; | |||
@@ -65,8 +64,7 @@ public class ServiceProviderExtensionFinder extends AbstractExtensionFinder { | |||
if (url != null) { | |||
Path extensionPath; | |||
if (url.toURI().getScheme().equals("jar")) { | |||
FileSystem fileSystem = FileSystems.newFileSystem(url.toURI(), Collections.<String, Object>emptyMap()); | |||
extensionPath = fileSystem.getPath(getExtensionsResource()); | |||
extensionPath = FileUtils.getPath(url.toURI(), getExtensionsResource()); | |||
} else { | |||
extensionPath = Paths.get(url.toURI()); | |||
} | |||
@@ -100,8 +98,7 @@ public class ServiceProviderExtensionFinder extends AbstractExtensionFinder { | |||
if (url != null) { | |||
Path extensionPath; | |||
if (url.toURI().getScheme().equals("jar")) { | |||
FileSystem fileSystem = FileSystems.newFileSystem(url.toURI(), Collections.<String, Object>emptyMap()); | |||
extensionPath = fileSystem.getPath(getExtensionsResource()); | |||
extensionPath = FileUtils.getPath(url.toURI(), getExtensionsResource()); | |||
} else { | |||
extensionPath = Paths.get(url.toURI()); | |||
} |
@@ -23,7 +23,10 @@ import java.io.File; | |||
import java.io.FileFilter; | |||
import java.io.FileReader; | |||
import java.io.IOException; | |||
import java.net.URI; | |||
import java.nio.charset.StandardCharsets; | |||
import java.nio.file.FileSystem; | |||
import java.nio.file.FileSystems; | |||
import java.nio.file.FileVisitResult; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
@@ -32,6 +35,7 @@ import java.nio.file.attribute.BasicFileAttributes; | |||
import java.nio.file.attribute.FileTime; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import java.util.List; | |||
/** | |||
@@ -199,4 +203,49 @@ public class FileUtils { | |||
return Files.isRegularFile(path) && path.toString().toLowerCase().endsWith(".zip"); | |||
} | |||
/** | |||
* Return true only if path is a jar file. | |||
* | |||
* @param path to a file/dir | |||
* @return true if file with {@code .jar} ending | |||
*/ | |||
public static boolean isJarFile(Path path) { | |||
return Files.isRegularFile(path) && path.toString().toLowerCase().endsWith(".jar"); | |||
} | |||
public static Path getPath(Path path, String first, String... more) throws IOException { | |||
URI uri = path.toUri(); | |||
if (isJarFile(path)) { | |||
uri = URI.create("jar:file:" + path.toString()); | |||
} | |||
return getPath(uri, first, more); | |||
} | |||
public static Path getPath(URI uri, String first, String... more) throws IOException { | |||
FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.<String, String>emptyMap()); | |||
return fileSystem.getPath(first, more); | |||
} | |||
public static Path findFile(Path directoryPath, String fileName) { | |||
File[] files = directoryPath.toFile().listFiles(); | |||
if (files != null) { | |||
for (File file : files) { | |||
if (file.isFile()) { | |||
if (file.getName().equals(fileName)) { | |||
return file.toPath(); | |||
} | |||
} else if (file.isDirectory()) { | |||
Path foundFile = findFile(file.toPath(), fileName); | |||
if (foundFile != null) { | |||
return foundFile; | |||
} | |||
} | |||
} | |||
} | |||
return null; | |||
} | |||
} |
@@ -0,0 +1,104 @@ | |||
/* | |||
* Copyright 2017 Decebal Suiu | |||
* | |||
* 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.pf4j; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.pf4j.plugin.PluginZip; | |||
import java.nio.charset.StandardCharsets; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import static org.junit.Assert.*; | |||
/** | |||
* @author Decebal Suiu | |||
*/ | |||
public class CompoundPluginDescriptorFinderTest { | |||
@Rule | |||
public TemporaryFolder testFolder = new TemporaryFolder(); | |||
@Test | |||
public void add() { | |||
CompoundPluginDescriptorFinder instance = new CompoundPluginDescriptorFinder(); | |||
assertEquals(0, instance.size()); | |||
instance.add(new PropertiesPluginDescriptorFinder()); | |||
assertEquals(1, instance.size()); | |||
} | |||
@Test | |||
public void find() throws Exception { | |||
Path pluginPath = testFolder.newFolder("test-plugin-1").toPath(); | |||
Files.write(pluginPath.resolve("plugin.properties"), getPlugin1Properties(), StandardCharsets.UTF_8); | |||
PluginDescriptorFinder instance = new CompoundPluginDescriptorFinder() | |||
.add(new PropertiesPluginDescriptorFinder()); | |||
PluginDescriptor pluginDescriptor = instance.find(pluginPath); | |||
assertNotNull(pluginDescriptor); | |||
assertEquals("test-plugin-1", pluginDescriptor.getPluginId()); | |||
assertEquals("0.0.1", pluginDescriptor.getVersion()); | |||
} | |||
@Test | |||
public void findInJar() throws Exception { | |||
PluginDescriptorFinder instance = new CompoundPluginDescriptorFinder() | |||
.add(new PropertiesPluginDescriptorFinder()); | |||
PluginZip pluginJar = new PluginZip.Builder(testFolder.newFile("my-plugin-1.2.3.jar"), "myPlugin") | |||
.pluginVersion("1.2.3") | |||
.build(); | |||
PluginDescriptor pluginDescriptor = instance.find(pluginJar.path()); | |||
assertNotNull(pluginDescriptor); | |||
assertEquals("myPlugin", pluginJar.pluginId()); | |||
assertEquals("1.2.3", pluginJar.pluginVersion()); | |||
} | |||
@Test(expected = PluginException.class) | |||
public void testNotFound() throws Exception { | |||
PluginDescriptorFinder instance = new CompoundPluginDescriptorFinder(); | |||
instance.find(getPluginsRoot().resolve("test-plugin-3")); | |||
} | |||
private List<String> getPlugin1Properties() { | |||
String[] lines = new String[] { | |||
"plugin.id=test-plugin-1\n" | |||
+ "plugin.version=0.0.1\n" | |||
+ "plugin.description=Test Plugin 1\n" | |||
+ "plugin.provider=Decebal Suiu\n" | |||
+ "plugin.class=org.pf4j.plugin.TestPlugin\n" | |||
+ "plugin.dependencies=test-plugin-2,test-plugin-3@~1.0\n" | |||
+ "plugin.requires=>=1\n" | |||
+ "plugin.license=Apache-2.0\n" | |||
+ "\n" | |||
+ "" | |||
}; | |||
return Arrays.asList(lines); | |||
} | |||
private Path getPluginsRoot() { | |||
return testFolder.getRoot().toPath(); | |||
} | |||
} |
@@ -16,49 +16,42 @@ | |||
package org.pf4j; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.pf4j.plugin.MockPluginManager; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.pf4j.plugin.PluginZip; | |||
import java.io.BufferedWriter; | |||
import java.io.FileWriter; | |||
import java.io.IOException; | |||
import java.net.URI; | |||
import java.nio.file.*; | |||
import java.util.Collections; | |||
import java.nio.file.Files; | |||
import java.nio.file.Paths; | |||
import static junit.framework.TestCase.assertNull; | |||
import static org.junit.Assert.*; | |||
public class LoadPluginsTest { | |||
private Path tmpDir; | |||
private MockPluginManager pluginManager; | |||
private MockZipPlugin p1; | |||
private MockZipPlugin p2; | |||
private MockZipPlugin p3; | |||
@Rule | |||
public TemporaryFolder testFolder = new TemporaryFolder(); | |||
private DefaultPluginManager pluginManager; | |||
@Before | |||
public void setup() throws IOException { | |||
tmpDir = Files.createTempDirectory("pf4j-test"); | |||
tmpDir.toFile().deleteOnExit(); | |||
p1 = new MockZipPlugin("myPlugin", "1.2.3", "my-plugin-1.2.3", "my-plugin-1.2.3.zip"); | |||
p2 = new MockZipPlugin("myPlugin", "2.0.0", "my-plugin-2.0.0", "my-plugin-2.0.0.ZIP"); | |||
p3 = new MockZipPlugin("other", "3.0.0", "other-3.0.0", "other-3.0.0.Zip"); | |||
pluginManager = new MockPluginManager( | |||
tmpDir, | |||
new PropertiesPluginDescriptorFinder("my.properties")); | |||
pluginManager = new DefaultPluginManager(testFolder.getRoot().toPath()); | |||
} | |||
@Test | |||
public void load() throws Exception { | |||
p1.create(); | |||
assertTrue(Files.exists(p1.zipFile)); | |||
PluginZip pluginZip = new PluginZip.Builder(testFolder.newFile("my-plugin-1.2.3.zip"), "myPlugin") | |||
.pluginVersion("1.2.3") | |||
.build(); | |||
assertTrue(Files.exists(pluginZip.path())); | |||
assertEquals(0, pluginManager.getPlugins().size()); | |||
pluginManager.loadPlugins(); | |||
assertTrue(Files.exists(p1.zipFile)); | |||
assertTrue(Files.exists(p1.unzipped)); | |||
assertTrue(Files.exists(pluginZip.path())); | |||
assertTrue(Files.exists(pluginZip.unzippedPath())); | |||
assertEquals(1, pluginManager.getPlugins().size()); | |||
assertEquals(p1.id, pluginManager.idForPath(p1.unzipped)); | |||
assertEquals(pluginZip.pluginId(), pluginManager.idForPath(pluginZip.unzippedPath())); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
@@ -68,105 +61,93 @@ public class LoadPluginsTest { | |||
@Test | |||
public void loadTwiceFails() throws Exception { | |||
p1.create(); | |||
assertNotNull(pluginManager.loadPluginFromPath(p1.zipFile)); | |||
assertNull(pluginManager.loadPluginFromPath(p1.zipFile)); | |||
PluginZip pluginZip = new PluginZip.Builder(testFolder.newFile("my-plugin-1.2.3.zip"), "myPlugin") | |||
.pluginVersion("1.2.3") | |||
.build(); | |||
assertNotNull(pluginManager.loadPluginFromPath(pluginZip.path())); | |||
assertNull(pluginManager.loadPluginFromPath(pluginZip.path())); | |||
} | |||
@Test | |||
public void loadUnloadLoad() throws Exception { | |||
p1.create(); | |||
PluginZip pluginZip = new PluginZip.Builder(testFolder.newFile("my-plugin-1.2.3.zip"), "myPlugin") | |||
.pluginVersion("1.2.3") | |||
.build(); | |||
pluginManager.loadPlugins(); | |||
assertEquals(1, pluginManager.getPlugins().size()); | |||
assertTrue(pluginManager.unloadPlugin(pluginManager.idForPath(p1.unzipped))); | |||
assertTrue(pluginManager.unloadPlugin(pluginManager.idForPath(pluginZip.unzippedPath()))); | |||
// duplicate check | |||
assertNull(pluginManager.idForPath(p1.unzipped)); | |||
assertNull(pluginManager.idForPath(pluginZip.unzippedPath())); | |||
// Double unload ok | |||
assertFalse(pluginManager.unloadPlugin(pluginManager.idForPath(p1.unzipped))); | |||
assertNotNull(pluginManager.loadPlugin(p1.unzipped)); | |||
assertFalse(pluginManager.unloadPlugin(pluginManager.idForPath(pluginZip.unzippedPath()))); | |||
assertNotNull(pluginManager.loadPlugin(pluginZip.unzippedPath())); | |||
} | |||
@Test | |||
public void upgrade() throws Exception { | |||
p1.create(); | |||
new PluginZip.Builder(testFolder.newFile("my-plugin-1.2.3.zip"), "myPlugin") | |||
.pluginVersion("1.2.3") | |||
.build(); | |||
pluginManager.loadPlugins(); | |||
pluginManager.startPlugins(); | |||
assertEquals(1, pluginManager.getPlugins().size()); | |||
assertEquals("1.2.3", pluginManager.getPlugin(p2.id).getDescriptor().getVersion()); | |||
assertEquals(1, pluginManager.getStartedPlugins().size()); | |||
p2.create(); | |||
PluginZip pluginZip2 = new PluginZip.Builder(testFolder.newFile("my-plugin-2.0.0.ZIP"), "myPlugin") | |||
.pluginVersion("2.0.0") | |||
.build(); | |||
assertEquals("1.2.3", pluginManager.getPlugin(pluginZip2.pluginId()).getDescriptor().getVersion()); | |||
pluginManager.loadPlugins(); | |||
pluginManager.startPlugin(p2.id); | |||
pluginManager.startPlugin(pluginZip2.pluginId()); | |||
assertEquals(1, pluginManager.getPlugins().size()); | |||
assertEquals("2.0.0", pluginManager.getPlugin(p2.id).getDescriptor().getVersion()); | |||
assertEquals("2.0.0", pluginManager.getPlugin(pluginZip2.pluginId()).getDescriptor().getVersion()); | |||
assertEquals("2.0.0", pluginManager.getStartedPlugins().get(1).getDescriptor().getVersion()); | |||
} | |||
@Test | |||
public void getRoot() throws Exception { | |||
assertEquals(tmpDir, pluginManager.getPluginsRoot()); | |||
assertEquals(testFolder.getRoot().toPath(), pluginManager.getPluginsRoot()); | |||
} | |||
@Test | |||
public void notAPlugin() throws Exception { | |||
Path notAPlugin = tmpDir.resolve("not-a-zip"); | |||
Files.createFile(notAPlugin); | |||
testFolder.newFile("not-a-zip"); | |||
pluginManager.loadPlugins(); | |||
assertEquals(0, pluginManager.getPlugins().size()); | |||
} | |||
@Test | |||
public void deletePlugin() throws Exception { | |||
p1.create(); | |||
p3.create(); | |||
PluginZip pluginZip1 = new PluginZip.Builder(testFolder.newFile("my-plugin-1.2.3.zip"), "myPlugin") | |||
.pluginVersion("1.2.3") | |||
.build(); | |||
PluginZip pluginZip3 = new PluginZip.Builder(testFolder.newFile("other-3.0.0.Zip"), "other") | |||
.pluginVersion("3.0.0") | |||
.build(); | |||
pluginManager.loadPlugins(); | |||
pluginManager.startPlugins(); | |||
assertEquals(2, pluginManager.getPlugins().size()); | |||
pluginManager.deletePlugin(p1.id); | |||
assertEquals(1, pluginManager.getPlugins().size()); | |||
assertFalse(Files.exists(p1.zipFile)); | |||
assertFalse(Files.exists(p1.unzipped)); | |||
assertTrue(Files.exists(p3.zipFile)); | |||
assertTrue(Files.exists(p3.unzipped)); | |||
} | |||
private class MockZipPlugin { | |||
public final String id; | |||
public final String version; | |||
public final String filename; | |||
public final Path zipFile; | |||
public final Path unzipped; | |||
public final Path propsFile; | |||
public final URI fileURI; | |||
public String zipname; | |||
public MockZipPlugin(String id, String version, String filename, String zipname) throws IOException { | |||
this.id = id; | |||
this.version = version; | |||
this.filename = filename; | |||
this.zipname = zipname; | |||
zipFile = tmpDir.resolve(zipname).toAbsolutePath(); | |||
unzipped = tmpDir.resolve(filename); | |||
propsFile = tmpDir.resolve("my.properties"); | |||
fileURI = URI.create("jar:file:"+zipFile.toString()); | |||
} | |||
public void create() throws IOException { | |||
try (FileSystem zipfs = FileSystems.newFileSystem(fileURI, Collections.singletonMap("create", "true"))) { | |||
Path propsInZip = zipfs.getPath("/" + propsFile.getFileName().toString()); | |||
BufferedWriter br = new BufferedWriter(new FileWriter(propsFile.toString())); | |||
br.write("plugin.id=" + id); | |||
br.newLine(); | |||
br.write("plugin.version=" + version); | |||
br.newLine(); | |||
br.write("plugin.class=org.pf4j.plugin.TestPlugin"); | |||
br.close(); | |||
Files.move(propsFile, propsInZip); | |||
} | |||
} | |||
pluginManager.deletePlugin(pluginZip1.pluginId()); | |||
assertEquals(1, pluginManager.getPlugins().size()); | |||
assertFalse(Files.exists(pluginZip1.path())); | |||
assertFalse(Files.exists(pluginZip1.unzippedPath())); | |||
assertTrue(Files.exists(pluginZip3.path())); | |||
assertTrue(Files.exists(pluginZip3.unzippedPath())); | |||
} | |||
} |
@@ -74,11 +74,11 @@ public class ManifestPluginDescriptorFinderTest { | |||
} | |||
/** | |||
* Test of {@link DefaultPluginDescriptorFinder#find(Path)} method. | |||
* Test of {@link ManifestPluginDescriptorFinder#find(Path)} method. | |||
*/ | |||
@Test | |||
public void testFind() throws Exception { | |||
PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath()); | |||
PluginDescriptorFinder instance = new ManifestPluginDescriptorFinder(); | |||
PluginDescriptor plugin1 = instance.find(getPluginsRoot().resolve("test-plugin-1")); | |||
PluginDescriptor plugin2 = instance.find(getPluginsRoot().resolve("test-plugin-2")); | |||
@@ -105,11 +105,11 @@ public class ManifestPluginDescriptorFinderTest { | |||
} | |||
/** | |||
* Test of {@link DefaultPluginDescriptorFinder#find(Path)} method. | |||
* Test of {@link ManifestPluginDescriptorFinder#find(Path)} method. | |||
*/ | |||
@Test(expected = PluginException.class) | |||
public void testFindNotFound() throws Exception { | |||
PluginDescriptorFinder instance = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath()); | |||
PluginDescriptorFinder instance = new ManifestPluginDescriptorFinder(); | |||
instance.find(getPluginsRoot().resolve("test-plugin-3")); | |||
} | |||
@@ -19,6 +19,7 @@ import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.pf4j.plugin.PluginZip; | |||
import java.io.IOException; | |||
import java.nio.charset.Charset; | |||
@@ -96,11 +97,26 @@ public class PropertiesPluginDescriptorFinderTest { | |||
} | |||
@Test(expected = PluginException.class) | |||
public void testFindNotFound() throws Exception { | |||
public void testNotFound() throws Exception { | |||
PluginDescriptorFinder instance = new PropertiesPluginDescriptorFinder(); | |||
instance.find(getPluginsRoot().resolve("test-plugin-3")); | |||
} | |||
@Test | |||
public void findInJar() throws Exception { | |||
PluginZip pluginJar = new PluginZip.Builder(testFolder.newFile("my-plugin-1.2.3.jar"), "myPlugin") | |||
.pluginVersion("1.2.3") | |||
.build(); | |||
assertTrue(Files.exists(pluginJar.path())); | |||
PluginDescriptorFinder instance = new PropertiesPluginDescriptorFinder(); | |||
PluginDescriptor pluginDescriptor = instance.find(pluginJar.path()); | |||
assertNotNull(pluginDescriptor); | |||
assertEquals("myPlugin", pluginJar.pluginId()); | |||
assertEquals("1.2.3", pluginJar.pluginVersion()); | |||
} | |||
private List<String> getPlugin1Properties() { | |||
String[] lines = new String[] { | |||
"plugin.id=test-plugin-1\n" |
@@ -18,7 +18,6 @@ package org.pf4j.plugin; | |||
import org.pf4j.Plugin; | |||
/** | |||
* | |||
* @author Mario Franco | |||
*/ | |||
public class AnotherFailTestPlugin extends Plugin { |
@@ -16,7 +16,6 @@ | |||
package org.pf4j.plugin; | |||
/** | |||
* | |||
* @author Mario Franco | |||
*/ | |||
public class FailTestPlugin { |
@@ -1,51 +0,0 @@ | |||
/* | |||
* Copyright 2017 Decebal Suiu | |||
* | |||
* 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.pf4j.plugin; | |||
import org.pf4j.DefaultPluginClasspath; | |||
import org.pf4j.DefaultPluginDescriptorFinder; | |||
import org.pf4j.DefaultPluginManager; | |||
import org.pf4j.PluginDescriptorFinder; | |||
import java.nio.file.Path; | |||
/** | |||
* Manager for testing | |||
*/ | |||
public class MockPluginManager extends DefaultPluginManager { | |||
private PluginDescriptorFinder finder = new DefaultPluginDescriptorFinder(new DefaultPluginClasspath()); | |||
public MockPluginManager() { | |||
super(); | |||
} | |||
public MockPluginManager(Path root, PluginDescriptorFinder finder) { | |||
super(root); | |||
this.finder = finder; | |||
} | |||
@Override | |||
protected PluginDescriptorFinder getPluginDescriptorFinder() { | |||
return finder; | |||
} | |||
@Override | |||
protected PluginDescriptorFinder createPluginDescriptorFinder() { | |||
return finder; | |||
} | |||
} |
@@ -0,0 +1,107 @@ | |||
/* | |||
* Copyright 2015 Decebal Suiu | |||
* | |||
* 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.pf4j.plugin; | |||
import java.io.File; | |||
import java.io.FileOutputStream; | |||
import java.io.IOException; | |||
import java.nio.file.Path; | |||
import java.util.Properties; | |||
import java.util.zip.ZipEntry; | |||
import java.util.zip.ZipOutputStream; | |||
/** | |||
* Represents a plugin zip/jar file. | |||
* The "plugin.properties" file is created on the fly from the information supplied in Builder. | |||
* | |||
* @author Decebal Suiu | |||
*/ | |||
public class PluginZip { | |||
private final File file; | |||
private final String pluginId; | |||
private final String pluginVersion; | |||
protected PluginZip(Builder builder) { | |||
this.file = builder.file; | |||
this.pluginId = builder.pluginId; | |||
this.pluginVersion = builder.pluginVersion; | |||
} | |||
public File file() { | |||
return file; | |||
} | |||
public Path path() { | |||
return file.toPath(); | |||
} | |||
public String pluginId() { | |||
return pluginId; | |||
} | |||
public String pluginVersion() { | |||
return pluginVersion; | |||
} | |||
public Path unzippedPath() { | |||
Path path = path(); | |||
String fileName = path.getFileName().toString(); | |||
return path.getParent().resolve(fileName.substring(0, fileName.length() - 4)); // without ".zip" suffix | |||
} | |||
public static class Builder { | |||
private final File file; | |||
private final String pluginId; | |||
private String pluginVersion; | |||
public Builder(File file, String pluginId) { | |||
this.file = file; | |||
this.pluginId = pluginId; | |||
} | |||
public Builder pluginVersion(String pluginVersion) { | |||
this.pluginVersion = pluginVersion; | |||
return this; | |||
} | |||
public PluginZip build() throws IOException { | |||
createPropertiesFile(); | |||
return new PluginZip(this); | |||
} | |||
protected void createPropertiesFile() throws IOException { | |||
Properties properties = new Properties(); | |||
properties.setProperty("plugin.id", pluginId); | |||
properties.setProperty("plugin.version", pluginVersion); | |||
properties.setProperty("plugin.class", "org.pf4j.plugin.TestPlugin"); | |||
ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(file)); | |||
ZipEntry propertiesFile = new ZipEntry("plugin.properties"); | |||
outputStream.putNextEntry(propertiesFile); | |||
properties.store(outputStream, ""); | |||
outputStream.closeEntry(); | |||
outputStream.close(); | |||
} | |||
} | |||
} |
@@ -19,7 +19,6 @@ import org.pf4j.Plugin; | |||
import org.pf4j.PluginWrapper; | |||
/** | |||
* | |||
* @author Mario Franco | |||
*/ | |||
public class TestPlugin extends Plugin { |
@@ -15,60 +15,36 @@ | |||
*/ | |||
package org.pf4j.util; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.pf4j.plugin.PluginZip; | |||
import java.io.BufferedWriter; | |||
import java.io.FileWriter; | |||
import java.io.IOException; | |||
import java.net.URI; | |||
import java.nio.file.FileSystem; | |||
import java.nio.file.FileSystems; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.util.Collections; | |||
import static org.junit.Assert.*; | |||
public class FileUtilsTest { | |||
private Path zipFile; | |||
private Path tmpDir; | |||
private Path propsFile; | |||
@Before | |||
public void setup() throws IOException { | |||
tmpDir = Files.createTempDirectory("pf4j-test"); | |||
tmpDir.toFile().deleteOnExit(); | |||
zipFile = tmpDir.resolve("my.zip").toAbsolutePath(); | |||
propsFile = tmpDir.resolve("plugin.properties"); | |||
URI file = URI.create("jar:file:"+zipFile.toString()); | |||
try (FileSystem zipfs = FileSystems.newFileSystem(file, Collections.singletonMap("create", "true"))) { | |||
Path propsInZip = zipfs.getPath("/plugin.properties"); | |||
BufferedWriter br = new BufferedWriter(new FileWriter(propsFile.toString())); | |||
br.write("plugin.id=test"); | |||
br.newLine(); | |||
br.write("plugin.version=1.2.3"); | |||
br.newLine(); | |||
br.write("plugin.class=foo.bar"); | |||
br.close(); | |||
Files.move(propsFile, propsInZip); | |||
} | |||
} | |||
@Rule | |||
public TemporaryFolder testFolder = new TemporaryFolder(); | |||
@Test | |||
public void expandIfZip() throws Exception { | |||
Path unzipped = FileUtils.expandIfZip(zipFile); | |||
assertEquals(tmpDir.resolve("my"), unzipped); | |||
assertTrue(Files.exists(tmpDir.resolve("my/plugin.properties"))); | |||
PluginZip pluginZip = new PluginZip.Builder(testFolder.newFile("my-plugin-1.2.3.zip"), "myPlugin") | |||
.pluginVersion("1.2.3") | |||
.build(); | |||
Path unzipped = FileUtils.expandIfZip(pluginZip.path()); | |||
assertEquals(pluginZip.unzippedPath(), unzipped); | |||
assertTrue(Files.exists(unzipped.resolve("plugin.properties"))); | |||
// Non-zip file remains unchanged | |||
assertEquals(propsFile, FileUtils.expandIfZip(propsFile)); | |||
// File without .suffix | |||
Path extra = Files.createFile(tmpDir.resolve("extra")); | |||
Path extra = testFolder.newFile("extra").toPath(); | |||
assertEquals(extra, FileUtils.expandIfZip(extra)); | |||
// Folder | |||
Path folder = Files.createFile(tmpDir.resolve("folder")); | |||
Path folder = testFolder.newFile("folder").toPath(); | |||
assertEquals(folder, FileUtils.expandIfZip(folder)); | |||
} | |||