Decouple plugins from their "jar" external form
Until now all the server-side plugins have been associated to a single jar file in the /plugin directory. As first step to allow different forms of plugins (e.g. script files, directories or anything else that can provide classes and resources) we need to de-couple the underlying Jar file from the server side plugin. We introduce the concept of "plugin-scanner" as the interface to scan the external form to discover: - plugin classes - plugin resources - plugin meta-data (i.e. Manifest) Change-Id: I769595a030545a5f272f453c3cf435b74719e1e7
This commit is contained in:
@@ -29,6 +29,7 @@ import com.google.gerrit.server.documentation.MarkdownFormatter;
|
|||||||
import com.google.gerrit.server.plugins.Plugin;
|
import com.google.gerrit.server.plugins.Plugin;
|
||||||
import com.google.gerrit.server.plugins.PluginsCollection;
|
import com.google.gerrit.server.plugins.PluginsCollection;
|
||||||
import com.google.gerrit.server.plugins.ReloadPluginListener;
|
import com.google.gerrit.server.plugins.ReloadPluginListener;
|
||||||
|
import com.google.gerrit.server.plugins.ServerPlugin;
|
||||||
import com.google.gerrit.server.plugins.StartPluginListener;
|
import com.google.gerrit.server.plugins.StartPluginListener;
|
||||||
import com.google.gerrit.server.ssh.SshInfo;
|
import com.google.gerrit.server.ssh.SshInfo;
|
||||||
import com.google.gwtexpui.server.CacheHeaders;
|
import com.google.gwtexpui.server.CacheHeaders;
|
||||||
@@ -268,7 +269,7 @@ class HttpPluginServlet extends HttpServlet
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (file.startsWith(holder.staticPrefix)) {
|
if (file.startsWith(holder.staticPrefix)) {
|
||||||
JarFile jar = holder.plugin.getJarFile();
|
JarFile jar = jarFileOf(holder.plugin);
|
||||||
if (jar != null) {
|
if (jar != null) {
|
||||||
JarEntry entry = jar.getJarEntry(file);
|
JarEntry entry = jar.getJarEntry(file);
|
||||||
if (exists(entry)) {
|
if (exists(entry)) {
|
||||||
@@ -286,7 +287,7 @@ class HttpPluginServlet extends HttpServlet
|
|||||||
} else if (file.startsWith(holder.docPrefix) && file.endsWith("/")) {
|
} else if (file.startsWith(holder.docPrefix) && file.endsWith("/")) {
|
||||||
res.sendRedirect(uri + "index.html");
|
res.sendRedirect(uri + "index.html");
|
||||||
} else if (file.startsWith(holder.docPrefix)) {
|
} else if (file.startsWith(holder.docPrefix)) {
|
||||||
JarFile jar = holder.plugin.getJarFile();
|
JarFile jar = jarFileOf(holder.plugin);
|
||||||
JarEntry entry = jar.getJarEntry(file);
|
JarEntry entry = jar.getJarEntry(file);
|
||||||
if (!exists(entry)) {
|
if (!exists(entry)) {
|
||||||
entry = findSource(jar, file);
|
entry = findSource(jar, file);
|
||||||
@@ -632,6 +633,14 @@ class HttpPluginServlet extends HttpServlet
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static JarFile jarFileOf(Plugin plugin) {
|
||||||
|
if(plugin instanceof ServerPlugin) {
|
||||||
|
return ((ServerPlugin) plugin).getJarFile();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class PluginHolder {
|
private static class PluginHolder {
|
||||||
final Plugin plugin;
|
final Plugin plugin;
|
||||||
final GuiceFilter filter;
|
final GuiceFilter filter;
|
||||||
@@ -648,7 +657,7 @@ class HttpPluginServlet extends HttpServlet
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String getPrefix(Plugin plugin, String attr, String def) {
|
private static String getPrefix(Plugin plugin, String attr, String def) {
|
||||||
JarFile jarFile = plugin.getJarFile();
|
JarFile jarFile = jarFileOf(plugin);
|
||||||
if (jarFile == null) {
|
if (jarFile == null) {
|
||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import com.google.common.collect.Sets;
|
|||||||
import com.google.gerrit.extensions.annotations.Export;
|
import com.google.gerrit.extensions.annotations.Export;
|
||||||
import com.google.gerrit.extensions.annotations.ExtensionPoint;
|
import com.google.gerrit.extensions.annotations.ExtensionPoint;
|
||||||
import com.google.gerrit.extensions.annotations.Listen;
|
import com.google.gerrit.extensions.annotations.Listen;
|
||||||
import com.google.gerrit.server.plugins.JarScanner.ExtensionMetaData;
|
import com.google.gerrit.server.plugins.PluginContentScanner.ExtensionMetaData;
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
import com.google.inject.Scopes;
|
import com.google.inject.Scopes;
|
||||||
@@ -34,12 +34,11 @@ import java.lang.reflect.ParameterizedType;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.jar.JarFile;
|
|
||||||
|
|
||||||
class AutoRegisterModules {
|
class AutoRegisterModules {
|
||||||
private final String pluginName;
|
private final String pluginName;
|
||||||
private final PluginGuiceEnvironment env;
|
private final PluginGuiceEnvironment env;
|
||||||
private final JarFile jarFile;
|
private final PluginContentScanner scanner;
|
||||||
private final ClassLoader classLoader;
|
private final ClassLoader classLoader;
|
||||||
private final ModuleGenerator sshGen;
|
private final ModuleGenerator sshGen;
|
||||||
private final ModuleGenerator httpGen;
|
private final ModuleGenerator httpGen;
|
||||||
@@ -53,11 +52,11 @@ class AutoRegisterModules {
|
|||||||
|
|
||||||
AutoRegisterModules(String pluginName,
|
AutoRegisterModules(String pluginName,
|
||||||
PluginGuiceEnvironment env,
|
PluginGuiceEnvironment env,
|
||||||
JarFile jarFile,
|
PluginContentScanner scanner,
|
||||||
ClassLoader classLoader) {
|
ClassLoader classLoader) {
|
||||||
this.pluginName = pluginName;
|
this.pluginName = pluginName;
|
||||||
this.env = env;
|
this.env = env;
|
||||||
this.jarFile = jarFile;
|
this.scanner = scanner;
|
||||||
this.classLoader = classLoader;
|
this.classLoader = classLoader;
|
||||||
this.sshGen = env.hasSshModule() ? env.newSshModuleGenerator() : null;
|
this.sshGen = env.hasSshModule() ? env.newSshModuleGenerator() : null;
|
||||||
this.httpGen = env.hasHttpModule() ? env.newHttpModuleGenerator() : null;
|
this.httpGen = env.hasHttpModule() ? env.newHttpModuleGenerator() : null;
|
||||||
@@ -111,7 +110,7 @@ class AutoRegisterModules {
|
|||||||
|
|
||||||
private void scan() throws InvalidPluginException {
|
private void scan() throws InvalidPluginException {
|
||||||
Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> extensions =
|
Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> extensions =
|
||||||
JarScanner.scan(jarFile, pluginName, Arrays.asList(Export.class, Listen.class));
|
scanner.scan(pluginName, Arrays.asList(Export.class, Listen.class));
|
||||||
for (ExtensionMetaData export : extensions.get(Export.class)) {
|
for (ExtensionMetaData export : extensions.get(Export.class)) {
|
||||||
export(export);
|
export(export);
|
||||||
}
|
}
|
||||||
@@ -123,18 +122,18 @@ class AutoRegisterModules {
|
|||||||
private void export(ExtensionMetaData def) throws InvalidPluginException {
|
private void export(ExtensionMetaData def) throws InvalidPluginException {
|
||||||
Class<?> clazz;
|
Class<?> clazz;
|
||||||
try {
|
try {
|
||||||
clazz = Class.forName(def.getClassName(), false, classLoader);
|
clazz = Class.forName(def.className, false, classLoader);
|
||||||
} catch (ClassNotFoundException err) {
|
} catch (ClassNotFoundException err) {
|
||||||
throw new InvalidPluginException(String.format(
|
throw new InvalidPluginException(String.format(
|
||||||
"Cannot load %s with @Export(\"%s\")",
|
"Cannot load %s with @Export(\"%s\")",
|
||||||
def.getClassName(), def.getAnnotationValue()), err);
|
def.className, def.annotationValue), err);
|
||||||
}
|
}
|
||||||
|
|
||||||
Export export = clazz.getAnnotation(Export.class);
|
Export export = clazz.getAnnotation(Export.class);
|
||||||
if (export == null) {
|
if (export == null) {
|
||||||
PluginLoader.log.warn(String.format(
|
PluginLoader.log.warn(String.format(
|
||||||
"In plugin %s asm incorrectly parsed %s with @Export(\"%s\")",
|
"In plugin %s asm incorrectly parsed %s with @Export(\"%s\")",
|
||||||
pluginName, clazz.getName(), def.getAnnotationValue()));
|
pluginName, clazz.getName(), def.annotationValue));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,11 +161,11 @@ class AutoRegisterModules {
|
|||||||
private void listen(ExtensionMetaData def) throws InvalidPluginException {
|
private void listen(ExtensionMetaData def) throws InvalidPluginException {
|
||||||
Class<?> clazz;
|
Class<?> clazz;
|
||||||
try {
|
try {
|
||||||
clazz = Class.forName(def.getClassName(), false, classLoader);
|
clazz = Class.forName(def.className, false, classLoader);
|
||||||
} catch (ClassNotFoundException err) {
|
} catch (ClassNotFoundException err) {
|
||||||
throw new InvalidPluginException(String.format(
|
throw new InvalidPluginException(String.format(
|
||||||
"Cannot load %s with @Listen",
|
"Cannot load %s with @Listen",
|
||||||
def.getClassName()), err);
|
def.className), err);
|
||||||
}
|
}
|
||||||
|
|
||||||
Listen listen = clazz.getAnnotation(Listen.class);
|
Listen listen = clazz.getAnnotation(Listen.class);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.google.common.base.Strings;
|
|||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
@@ -38,6 +39,7 @@ import org.objectweb.asm.MethodVisitor;
|
|||||||
import org.objectweb.asm.Opcodes;
|
import org.objectweb.asm.Opcodes;
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
@@ -46,10 +48,12 @@ import java.util.Collections;
|
|||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.jar.Attributes;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.jar.Manifest;
|
||||||
|
|
||||||
public class JarScanner {
|
public class JarScanner implements PluginContentScanner {
|
||||||
private static final int SKIP_ALL = ClassReader.SKIP_CODE
|
private static final int SKIP_ALL = ClassReader.SKIP_CODE
|
||||||
| ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
|
| ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
|
||||||
private static final Function<ClassData, ExtensionMetaData> CLASS_DATA_TO_EXTENSION_META_DATA =
|
private static final Function<ClassData, ExtensionMetaData> CLASS_DATA_TO_EXTENSION_META_DATA =
|
||||||
@@ -61,27 +65,19 @@ public class JarScanner {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static class ExtensionMetaData {
|
private final JarFile jarFile;
|
||||||
private final String className;
|
|
||||||
private final String annotationValue;
|
|
||||||
|
|
||||||
private ExtensionMetaData(String className, String annotationValue) {
|
public JarScanner(File srcFile) throws InvalidPluginException {
|
||||||
this.className = className;
|
try {
|
||||||
this.annotationValue = annotationValue;
|
this.jarFile = new JarFile(srcFile);
|
||||||
}
|
} catch (IOException e) {
|
||||||
|
throw new InvalidPluginException("Cannot scan plugin file " + srcFile, e);
|
||||||
public String getAnnotationValue() {
|
|
||||||
return annotationValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getClassName() {
|
|
||||||
return className;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> scan(
|
@Override
|
||||||
JarFile jarFile, String pluginName,
|
public Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> scan(
|
||||||
Iterable<Class<? extends Annotation>> annotations)
|
String pluginName, Iterable<Class<? extends Annotation>> annotations)
|
||||||
throws InvalidPluginException {
|
throws InvalidPluginException {
|
||||||
Set<String> descriptors = Sets.newHashSet();
|
Set<String> descriptors = Sets.newHashSet();
|
||||||
Multimap<String, JarScanner.ClassData> rawMap = ArrayListMultimap.create();
|
Multimap<String, JarScanner.ClassData> rawMap = ArrayListMultimap.create();
|
||||||
@@ -262,4 +258,62 @@ public class JarScanner {
|
|||||||
public void visitEnd() {
|
public void visitEnd() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<PluginEntry> getEntry(String resourcePath) throws IOException {
|
||||||
|
JarEntry jarEntry = jarFile.getJarEntry(resourcePath);
|
||||||
|
if (jarEntry == null || jarEntry.getSize() == 0) {
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(resourceOf(jarEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Enumeration<PluginEntry> entries() {
|
||||||
|
return Collections.enumeration(Lists.transform(
|
||||||
|
Collections.list(jarFile.entries()),
|
||||||
|
new Function<JarEntry, PluginEntry>() {
|
||||||
|
public PluginEntry apply(JarEntry jarEntry) {
|
||||||
|
try {
|
||||||
|
return resourceOf(jarEntry);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalArgumentException("Cannot convert jar entry "
|
||||||
|
+ jarEntry + " to a resource", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream(PluginEntry entry)
|
||||||
|
throws IOException {
|
||||||
|
return jarFile.getInputStream(jarFile
|
||||||
|
.getEntry(entry.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Manifest getManifest() throws IOException {
|
||||||
|
return jarFile.getManifest();
|
||||||
|
}
|
||||||
|
|
||||||
|
private PluginEntry resourceOf(JarEntry jarEntry) throws IOException {
|
||||||
|
return new PluginEntry(jarEntry.getName(), jarEntry.getTime(),
|
||||||
|
jarEntry.getSize(), attributesOf(jarEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Object, String> attributesOf(JarEntry jarEntry)
|
||||||
|
throws IOException {
|
||||||
|
Attributes attributes = jarEntry.getAttributes();
|
||||||
|
if (attributes == null) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
return Maps.transformEntries(attributes,
|
||||||
|
new Maps.EntryTransformer<Object, Object, String>() {
|
||||||
|
@Override
|
||||||
|
public String transformEntry(Object key, Object value) {
|
||||||
|
return (String) value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,11 +66,6 @@ class JsPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public JarFile getJarFile() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Injector getSysInjector() {
|
public Injector getSysInjector() {
|
||||||
return null;
|
return null;
|
||||||
@@ -109,4 +104,9 @@ class JsPlugin extends Plugin {
|
|||||||
new JavaScriptPlugin(fileName));
|
new JavaScriptPlugin(fileName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PluginContentScanner getContentScanner() {
|
||||||
|
return PluginContentScanner.EMPTY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ public class ListPlugins implements RestReadView<TopLevelResource> {
|
|||||||
version = p.getVersion();
|
version = p.getVersion();
|
||||||
disabled = p.isDisabled() ? true : null;
|
disabled = p.isDisabled() ? true : null;
|
||||||
|
|
||||||
if (p.getJarFile() != null) {
|
if (p.getSrcFile() != null) {
|
||||||
indexUrl = String.format("plugins/%s/", p.getName());
|
indexUrl = String.format("plugins/%s/", p.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ public abstract class Plugin {
|
|||||||
|
|
||||||
abstract void stop(PluginGuiceEnvironment env);
|
abstract void stop(PluginGuiceEnvironment env);
|
||||||
|
|
||||||
public abstract JarFile getJarFile();
|
public abstract PluginContentScanner getContentScanner();
|
||||||
|
|
||||||
public abstract Injector getSysInjector();
|
public abstract Injector getSysInjector();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (C) 2014 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// 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 com.google.gerrit.server.plugins;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.jar.Manifest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans the plugin returning classes and resources.
|
||||||
|
*
|
||||||
|
* Gerrit uses the scanner to automatically discover the classes
|
||||||
|
* and resources exported by the plugin for auto discovery
|
||||||
|
* of exported SSH commands, Servlets and listeners.
|
||||||
|
*/
|
||||||
|
public interface PluginContentScanner {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scanner without resources.
|
||||||
|
*/
|
||||||
|
PluginContentScanner EMPTY = new PluginContentScanner() {
|
||||||
|
@Override
|
||||||
|
public Manifest getManifest() throws IOException {
|
||||||
|
return new Manifest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> scan(
|
||||||
|
String pluginName, Iterable<Class<? extends Annotation>> annotations)
|
||||||
|
throws InvalidPluginException {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<PluginEntry> getEntry(String resourcePath)
|
||||||
|
throws IOException {
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream(PluginEntry entry) throws IOException {
|
||||||
|
throw new FileNotFoundException("Empty plugin");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Enumeration<PluginEntry> entries() {
|
||||||
|
return Collections.emptyEnumeration();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin class extension meta-data
|
||||||
|
*
|
||||||
|
* Class name and annotation value of the class
|
||||||
|
* provided by a plugin to extend an existing
|
||||||
|
* extension point in Gerrit.
|
||||||
|
*/
|
||||||
|
public static class ExtensionMetaData {
|
||||||
|
public final String className;
|
||||||
|
public final String annotationValue;
|
||||||
|
|
||||||
|
public ExtensionMetaData(String className, String annotationValue) {
|
||||||
|
this.className = className;
|
||||||
|
this.annotationValue = annotationValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the plugin meta-data manifest
|
||||||
|
*
|
||||||
|
* @return Manifest of the plugin or null if plugin has no meta-data
|
||||||
|
* @throws IOException if an I/O problem occurred whilst accessing the Manifest
|
||||||
|
*/
|
||||||
|
Manifest getManifest() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans the plugin for declared public annotated classes
|
||||||
|
*
|
||||||
|
* @param pluginName the plugin name
|
||||||
|
* @param annotations annotations declared by the plugin classes
|
||||||
|
* @return map of annotations and associated plugin classes found
|
||||||
|
* @throws InvalidPluginException if the plugin is not valid or corrupted
|
||||||
|
*/
|
||||||
|
Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> scan(
|
||||||
|
String pluginName, Iterable<Class<? extends Annotation>> annotations)
|
||||||
|
throws InvalidPluginException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the plugin resource associated to a path
|
||||||
|
*
|
||||||
|
* @param resourcePath full path of the resource inside the plugin package
|
||||||
|
* @return the resource object or Optional.absent() if the resource was not found
|
||||||
|
* @throws IOException if there was a problem retrieving the resource
|
||||||
|
*/
|
||||||
|
Optional<PluginEntry> getEntry(String resourcePath) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the InputStream of the resource entry
|
||||||
|
*
|
||||||
|
* @param entry resource entry inside the plugin package
|
||||||
|
* @return the resource input stream
|
||||||
|
* @throws IOException if there was an I/O problem accessing the resource
|
||||||
|
*/
|
||||||
|
InputStream getInputStream(PluginEntry entry) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all the resources inside a plugin
|
||||||
|
*
|
||||||
|
* @return the enumeration of all resources found
|
||||||
|
*/
|
||||||
|
Enumeration<PluginEntry> entries();
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// Copyright (C) 2014 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// 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 com.google.gerrit.server.plugins;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin static resource entry
|
||||||
|
*
|
||||||
|
* Bean representing a static resource inside a plugin.
|
||||||
|
* All static resources are available at <plugin web url>/static
|
||||||
|
* and served by the HttpPluginServlet.
|
||||||
|
*/
|
||||||
|
public class PluginEntry {
|
||||||
|
public static final String ATTR_CHARACTER_ENCODING = "Character-Encoding";
|
||||||
|
public static final String ATTR_CONTENT_TYPE = "Content-Type";
|
||||||
|
|
||||||
|
private static final Map<Object,String> EMPTY_ATTRS = Collections.emptyMap();
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final long time;
|
||||||
|
private final long size;
|
||||||
|
private final Map<Object, String> attrs;
|
||||||
|
|
||||||
|
public PluginEntry(String name, long time, long size,
|
||||||
|
Map<Object, String> attrs) {
|
||||||
|
this.name = name;
|
||||||
|
this.time = time;
|
||||||
|
this.size = size;
|
||||||
|
this.attrs = attrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginEntry(String name, long time, long size) {
|
||||||
|
this(name, time, size, EMPTY_ATTRS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTime() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Object, String> getAttrs() {
|
||||||
|
return attrs;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -589,8 +589,8 @@ public class PluginLoader implements LifecycleListener {
|
|||||||
|
|
||||||
Plugin plugin = new ServerPlugin(name, url,
|
Plugin plugin = new ServerPlugin(name, url,
|
||||||
pluginUserFactory.create(name),
|
pluginUserFactory.create(name),
|
||||||
srcJar, snapshot,
|
srcJar, snapshot, new JarFile(srcJar),
|
||||||
jarFile, manifest,
|
new JarScanner(srcJar),
|
||||||
new File(dataDir, name), type, pluginLoader,
|
new File(dataDir, name), type, pluginLoader,
|
||||||
sysModule, sshModule, httpModule);
|
sysModule, sshModule, httpModule);
|
||||||
cleanupHandles.put(plugin, new CleanupHandle(tmp, jarFile));
|
cleanupHandles.put(plugin, new CleanupHandle(tmp, jarFile));
|
||||||
|
|||||||
@@ -35,12 +35,13 @@ import com.google.inject.ProvisionException;
|
|||||||
import org.eclipse.jgit.internal.storage.file.FileSnapshot;
|
import org.eclipse.jgit.internal.storage.file.FileSnapshot;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.jar.Attributes;
|
import java.util.jar.Attributes;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
import java.util.jar.Manifest;
|
import java.util.jar.Manifest;
|
||||||
|
|
||||||
class ServerPlugin extends Plugin {
|
public class ServerPlugin extends Plugin {
|
||||||
|
|
||||||
/** Unique key that changes whenever a plugin reloads. */
|
/** Unique key that changes whenever a plugin reloads. */
|
||||||
public static final class CacheKey {
|
public static final class CacheKey {
|
||||||
@@ -59,6 +60,7 @@ class ServerPlugin extends Plugin {
|
|||||||
|
|
||||||
private final JarFile jarFile;
|
private final JarFile jarFile;
|
||||||
private final Manifest manifest;
|
private final Manifest manifest;
|
||||||
|
private final PluginContentScanner scanner;
|
||||||
private final File dataDir;
|
private final File dataDir;
|
||||||
private final String pluginCanonicalWebUrl;
|
private final String pluginCanonicalWebUrl;
|
||||||
private final ClassLoader classLoader;
|
private final ClassLoader classLoader;
|
||||||
@@ -78,28 +80,39 @@ class ServerPlugin extends Plugin {
|
|||||||
File srcJar,
|
File srcJar,
|
||||||
FileSnapshot snapshot,
|
FileSnapshot snapshot,
|
||||||
JarFile jarFile,
|
JarFile jarFile,
|
||||||
Manifest manifest,
|
PluginContentScanner scanner,
|
||||||
File dataDir,
|
File dataDir,
|
||||||
ApiType apiType,
|
ApiType apiType,
|
||||||
ClassLoader classLoader,
|
ClassLoader classLoader,
|
||||||
@Nullable Class<? extends Module> sysModule,
|
@Nullable Class<? extends Module> sysModule,
|
||||||
@Nullable Class<? extends Module> sshModule,
|
@Nullable Class<? extends Module> sshModule,
|
||||||
@Nullable Class<? extends Module> httpModule) {
|
@Nullable Class<? extends Module> httpModule)
|
||||||
|
throws InvalidPluginException {
|
||||||
super(name, srcJar, pluginUser, snapshot, apiType);
|
super(name, srcJar, pluginUser, snapshot, apiType);
|
||||||
this.pluginCanonicalWebUrl = pluginCanonicalWebUrl;
|
this.pluginCanonicalWebUrl = pluginCanonicalWebUrl;
|
||||||
this.jarFile = jarFile;
|
this.jarFile = jarFile;
|
||||||
this.manifest = manifest;
|
this.scanner = scanner;
|
||||||
this.dataDir = dataDir;
|
this.dataDir = dataDir;
|
||||||
this.classLoader = classLoader;
|
this.classLoader = classLoader;
|
||||||
this.sysModule = sysModule;
|
this.sysModule = sysModule;
|
||||||
this.sshModule = sshModule;
|
this.sshModule = sshModule;
|
||||||
this.httpModule = httpModule;
|
this.httpModule = httpModule;
|
||||||
|
this.manifest = getPluginManifest(scanner);
|
||||||
}
|
}
|
||||||
|
|
||||||
File getSrcJar() {
|
File getSrcJar() {
|
||||||
return getSrcFile();
|
return getSrcFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Manifest getPluginManifest(PluginContentScanner scanner)
|
||||||
|
throws InvalidPluginException {
|
||||||
|
try {
|
||||||
|
return scanner.getManifest();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InvalidPluginException("Cannot get plugin manifest", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public String getVersion() {
|
public String getVersion() {
|
||||||
Attributes main = manifest.getMainAttributes();
|
Attributes main = manifest.getMainAttributes();
|
||||||
@@ -136,7 +149,7 @@ class ServerPlugin extends Plugin {
|
|||||||
|
|
||||||
AutoRegisterModules auto = null;
|
AutoRegisterModules auto = null;
|
||||||
if (sysModule == null && sshModule == null && httpModule == null) {
|
if (sysModule == null && sshModule == null && httpModule == null) {
|
||||||
auto = new AutoRegisterModules(getName(), env, jarFile, classLoader);
|
auto = new AutoRegisterModules(getName(), env, scanner, classLoader);
|
||||||
auto.discover();
|
auto.discover();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,4 +283,9 @@ class ServerPlugin extends Plugin {
|
|||||||
manager.add(handle);
|
manager.add(handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PluginContentScanner getContentScanner() {
|
||||||
|
return scanner;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user