Browse Source

Improve PluginJar, add ClassDataProvider concept

tags/release-3.0.0
Decebal Suiu 5 years ago
parent
commit
6fd8ae2384

+ 16
- 0
pf4j/src/test/java/org/pf4j/JarPluginManagerTest.java View File

import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.pf4j.plugin.PluginJar; import org.pf4j.plugin.PluginJar;
import org.pf4j.plugin.TestExtension;
import org.pf4j.plugin.TestExtensionPoint;
import org.pf4j.plugin.TestPlugin; import org.pf4j.plugin.TestPlugin;


import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;


import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
pluginJar = new PluginJar.Builder(pluginsPath.resolve("test-plugin.jar"), "test-plugin") pluginJar = new PluginJar.Builder(pluginsPath.resolve("test-plugin.jar"), "test-plugin")
.pluginClass(TestPlugin.class.getName()) .pluginClass(TestPlugin.class.getName())
.pluginVersion("1.2.3") .pluginVersion("1.2.3")
.extension(TestExtension.class.getName())
.build(); .build();


pluginManager = new JarPluginManager(pluginsPath); pluginManager = new JarPluginManager(pluginsPath);
pluginManager = null; pluginManager = null;
} }


@Test
public void getExtensions() {
pluginManager.loadPlugins();
pluginManager.startPlugins();

List<TestExtensionPoint> extensions = pluginManager.getExtensions(TestExtensionPoint.class);
assertEquals(1, extensions.size());

String something = extensions.get(0).saySomething();
assertEquals(new TestExtension().saySomething(), something);
}

@Test @Test
public void unloadPlugin() throws Exception { public void unloadPlugin() throws Exception {
pluginManager.loadPlugins(); pluginManager.loadPlugins();

+ 33
- 0
pf4j/src/test/java/org/pf4j/plugin/ClassDataProvider.java View File

/*
* Copyright (C) 2012-present the original author or 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.pf4j.plugin;

/**
* Defines the interface for classes that know to supply class data for a class name.
* The idea is to have the possibility to retrieve the data for a class from different sources:
* <ul>
* <li>Class path - the class is already loaded by the class loader</li>
* <li>String - the string (the source code) is compiled dynamically via {@link javax.tools.JavaCompiler}</>
* <li>Generate the source code programmatically using something like {@code https://github.com/square/javapoet}</li>
* </ul>
*
* @author Decebal Suiu
*/
public interface ClassDataProvider {

byte[] getClassData(String className);

}

+ 55
- 0
pf4j/src/test/java/org/pf4j/plugin/DefaultClassDataProvider.java View File

/*
* Copyright (C) 2012-present the original author or 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.pf4j.plugin;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
* Get class data from the class path.
*
* @author Decebal Suiu
*/
public class DefaultClassDataProvider implements ClassDataProvider {

@Override
public byte[] getClassData(String className) {
String path = className.replace('.', '/') + ".class";
InputStream classDataStream = getClass().getClassLoader().getResourceAsStream(path);
if (classDataStream == null) {
throw new RuntimeException("Cannot find class data");
}

try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
copyStream(classDataStream, outputStream);
return outputStream.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}

private void copyStream(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];

int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}

}

+ 5
- 0
pf4j/src/test/java/org/pf4j/plugin/FailTestExtension.java View File

public FailTestExtension(String name) { public FailTestExtension(String name) {
} }


@Override
public String saySomething() {
return "I am a fail test extension";
}

} }

+ 54
- 6
pf4j/src/test/java/org/pf4j/plugin/PluginJar.java View File



import org.pf4j.ManifestPluginDescriptorFinder; import org.pf4j.ManifestPluginDescriptorFinder;


import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes; import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream; import java.util.jar.JarOutputStream;
import java.util.jar.Manifest; import java.util.jar.Manifest;


/** /**
/**
* Represents a plugin {@code jar} file. * Represents a plugin {@code jar} file.
* The {@code MANIFEST.MF} file is created on the fly from the information supplied in {@link Builder}. * The {@code MANIFEST.MF} file is created on the fly from the information supplied in {@link Builder}.
* *
private String pluginClass; private String pluginClass;
private String pluginVersion; private String pluginVersion;
private Map<String, String> manifestAttributes = new LinkedHashMap<>(); private Map<String, String> manifestAttributes = new LinkedHashMap<>();
private Set<String> extensions = new LinkedHashSet<>();
private ClassDataProvider classDataProvider = new DefaultClassDataProvider();


public Builder(Path path, String pluginId) { public Builder(Path path, String pluginId) {
this.path = path; this.path = path;
return this; return this;
} }


public Builder extension(String extensionClassName) {
extensions.add(extensionClassName);

return this;
}

public Builder classDataProvider(ClassDataProvider classDataProvider) {
this.classDataProvider = classDataProvider;

return this;
}

public PluginJar build() throws IOException { public PluginJar build() throws IOException {
createManifestFile();
Manifest manifest = createManifest();
try (OutputStream outputStream = new FileOutputStream(path.toFile())) {
JarOutputStream jarOutputStream = new JarOutputStream(outputStream, manifest);
if (!extensions.isEmpty()) {
// add extensions.idx
JarEntry jarEntry = new JarEntry("META-INF/extensions.idx");
jarOutputStream.putNextEntry(jarEntry);
jarOutputStream.write(extensionsAsByteArray());
jarOutputStream.closeEntry();
// add extensions classes
for (String extension : extensions) {
String extensionPath = extension.replace('.', '/') + ".class";
JarEntry classEntry = new JarEntry(extensionPath);
jarOutputStream.putNextEntry(classEntry);
jarOutputStream.write(classDataProvider.getClassData(extension));
jarOutputStream.closeEntry();
}
}
jarOutputStream.close();
}


return new PluginJar(this); return new PluginJar(this);
} }


protected void createManifestFile() throws IOException {
private Manifest createManifest() {
Map<String, String> map = new LinkedHashMap<>(); Map<String, String> map = new LinkedHashMap<>();
map.put(ManifestPluginDescriptorFinder.PLUGIN_ID, pluginId); map.put(ManifestPluginDescriptorFinder.PLUGIN_ID, pluginId);
map.put(ManifestPluginDescriptorFinder.PLUGIN_VERSION, pluginVersion); map.put(ManifestPluginDescriptorFinder.PLUGIN_VERSION, pluginVersion);
map.putAll(manifestAttributes); map.putAll(manifestAttributes);
} }


Manifest manifest = createManifest(map);
JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(path.toFile()), manifest);
outputStream.close();
return PluginJar.createManifest(map);
}

private byte[] extensionsAsByteArray() throws IOException {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
PrintWriter writer = new PrintWriter(outputStream);
for (String extension : extensions) {
writer.println(extension);
}
writer.flush();

return outputStream.toByteArray();
}
} }


} }

+ 5
- 0
pf4j/src/test/java/org/pf4j/plugin/TestExtension.java View File

@Extension @Extension
public class TestExtension implements TestExtensionPoint { public class TestExtension implements TestExtensionPoint {


@Override
public String saySomething() {
return "I am a test extension";
}

} }

+ 2
- 0
pf4j/src/test/java/org/pf4j/plugin/TestExtensionPoint.java View File

*/ */
public interface TestExtensionPoint extends ExtensionPoint { public interface TestExtensionPoint extends ExtensionPoint {


String saySomething();

} }

Loading…
Cancel
Save