stop scanning classpath, bind extensions from plugin and module descriptor

This commit is contained in:
Sebastian Sdorra
2014-03-28 22:49:55 +01:00
parent cbcd63257d
commit 43b92f0aca
10 changed files with 236 additions and 626 deletions

View File

@@ -35,11 +35,15 @@ package sonia.scm.plugin;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.inject.Binder;
import com.google.inject.Module;
import sonia.scm.plugin.ext.ExtensionProcessor; import sonia.scm.plugin.ext.ExtensionProcessor;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import java.util.Collection; import java.util.Collection;
import java.util.Set;
/** /**
* *
@@ -52,12 +56,28 @@ public interface PluginLoader
* Method description * Method description
* *
* *
* @param processor * @param binder
*/ */
public void processExtensions(ExtensionProcessor processor); public void processExtensions(Binder binder);
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public Collection<Module> getInjectionModules();
/**
* Method description
*
*
* @return
*/
public Collection<ScmModule> getInstalledModules();
/** /**
* Method description * Method description
* *

View File

@@ -17,6 +17,15 @@
<dependencies> <dependencies>
<!-- annotation processor -->
<dependency>
<groupId>sonia.scm.maven</groupId>
<artifactId>scm-annotation-processor</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId> <artifactId>servlet-api</artifactId>

View File

@@ -181,7 +181,7 @@ public class ScmContextListener extends GuiceServletContextListener
*/ */
private Injector getDefaultInjector(ServletContext servletContext) private Injector getDefaultInjector(ServletContext servletContext)
{ {
DefaultPluginLoader pluginLoader = new DefaultPluginLoader(servletContext); DefaultPluginLoader pluginLoader = new DefaultPluginLoader();
ClassOverrides overrides = ClassOverrides.findOverrides(); ClassOverrides overrides = ClassOverrides.findOverrides();
ScmServletModule main = new ScmServletModule(pluginLoader, overrides); ScmServletModule main = new ScmServletModule(pluginLoader, overrides);
@@ -193,7 +193,7 @@ public class ScmContextListener extends GuiceServletContextListener
moduleList.add(ShiroWebModule.guiceFilterModule()); moduleList.add(ShiroWebModule.guiceFilterModule());
moduleList.add(main); moduleList.add(main);
moduleList.add(new ScmSecurityModule(servletContext)); moduleList.add(new ScmSecurityModule(servletContext));
moduleList.addAll(pluginLoader.getModuleSet()); moduleList.addAll(pluginLoader.getInjectionModules());
moduleList.addAll(overrides.getModules()); moduleList.addAll(overrides.getModules());
return Guice.createInjector(moduleList); return Guice.createInjector(moduleList);

View File

@@ -39,10 +39,10 @@ import com.google.inject.Provider;
import com.google.inject.multibindings.Multibinder; import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names; import com.google.inject.name.Names;
import com.google.inject.servlet.RequestScoped; import com.google.inject.servlet.RequestScoped;
import com.google.inject.servlet.ServletModule;
import com.google.inject.throwingproviders.ThrowingProviderBinder; import com.google.inject.throwingproviders.ThrowingProviderBinder;
import org.apache.shiro.authz.permission.PermissionResolver; import org.apache.shiro.authz.permission.PermissionResolver;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -66,11 +66,11 @@ import sonia.scm.net.HttpClient;
import sonia.scm.net.URLHttpClient; import sonia.scm.net.URLHttpClient;
import sonia.scm.plugin.DefaultPluginLoader; import sonia.scm.plugin.DefaultPluginLoader;
import sonia.scm.plugin.DefaultPluginManager; import sonia.scm.plugin.DefaultPluginManager;
import sonia.scm.plugin.Plugin;
import sonia.scm.plugin.PluginLoader; import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.PluginManager; import sonia.scm.plugin.PluginManager;
import sonia.scm.repository.DefaultRepositoryManager; import sonia.scm.repository.DefaultRepositoryManager;
import sonia.scm.repository.DefaultRepositoryProvider; import sonia.scm.repository.DefaultRepositoryProvider;
import sonia.scm.repository.HealthCheckContextListener;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryDAO; import sonia.scm.repository.RepositoryDAO;
import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryManager;
@@ -132,27 +132,21 @@ import sonia.scm.web.security.DefaultAdministrationContext;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import com.sun.jersey.api.core.PackagesResourceConfig; import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.api.core.ResourceConfig; import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.api.json.JSONConfiguration; import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.guice.JerseyServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
import com.sun.jersey.spi.container.servlet.ServletContainer; import com.sun.jersey.spi.container.servlet.ServletContainer;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set;
import sonia.scm.repository.HealthCheckContextListener;
import sonia.scm.repository.HealthChecker;
/** /**
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
public class ScmServletModule extends ServletModule public class ScmServletModule extends JerseyServletModule
{ {
/** Field description */ /** Field description */
@@ -267,7 +261,7 @@ public class ScmServletModule extends ServletModule
bind(CipherHandler.class).toInstance(cu.getCipherHandler()); bind(CipherHandler.class).toInstance(cu.getCipherHandler());
bind(EncryptionHandler.class, MessageDigestEncryptionHandler.class); bind(EncryptionHandler.class, MessageDigestEncryptionHandler.class);
bind(FileSystem.class, DefaultFileSystem.class); bind(FileSystem.class, DefaultFileSystem.class);
// bind health check stuff // bind health check stuff
bind(HealthCheckContextListener.class); bind(HealthCheckContextListener.class);
@@ -376,67 +370,18 @@ public class ScmServletModule extends ServletModule
*/ */
params.put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE.toString()); params.put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE.toString());
params.put(ResourceConfig.FEATURE_REDIRECT, Boolean.TRUE.toString()); params.put(ResourceConfig.FEATURE_REDIRECT, Boolean.TRUE.toString());
/*
* TODO remove UriExtensionsConfig and PackagesResourceConfig
* to stop jersey classpath scanning
*/
params.put(ServletContainer.RESOURCE_CONFIG_CLASS, params.put(ServletContainer.RESOURCE_CONFIG_CLASS,
UriExtensionsConfig.class.getName()); UriExtensionsConfig.class.getName());
params.put(PackagesResourceConfig.PROPERTY_PACKAGES, "unbound");
String restPath = getRestPackages();
if (logger.isInfoEnabled())
{
logger.info("configure jersey with package path: {}", restPath);
}
params.put(PackagesResourceConfig.PROPERTY_PACKAGES, restPath);
serve(PATTERN_RESTAPI).with(GuiceContainer.class, params); serve(PATTERN_RESTAPI).with(GuiceContainer.class, params);
} }
/**
* Method description
*
*
* @param packageSet
* @param plugin
*/
private void appendPluginPackages(Set<String> packageSet, Plugin plugin)
{
Set<String> pluginPackageSet = plugin.getPackageSet();
if (pluginPackageSet != null)
{
for (String pluginPkg : pluginPackageSet)
{
boolean append = true;
for (String pkg : packageSet)
{
if (pluginPkg.startsWith(pkg))
{
append = false;
break;
}
}
if (append)
{
if (logger.isDebugEnabled())
{
String name = "unknown";
if (plugin.getInformation() != null)
{
name = plugin.getInformation().getName();
}
logger.debug("plugin {} added rest path {}", name, pluginPkg);
}
packageSet.add(pluginPkg);
}
}
}
}
/** /**
* Method description * Method description
* *
@@ -524,44 +469,6 @@ public class ScmServletModule extends ServletModule
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
private String getRestPackages()
{
Set<String> packageSet = new HashSet<String>();
packageSet.add(SCMContext.DEFAULT_PACKAGE);
Collection<Plugin> plugins = pluginLoader.getInstalledPlugins();
if (plugins != null)
{
for (Plugin plugin : plugins)
{
appendPluginPackages(packageSet, plugin);
}
}
StringBuilder buffer = new StringBuilder();
Iterator<String> pkgIterator = packageSet.iterator();
while (pkgIterator.hasNext())
{
buffer.append(pkgIterator.next());
if (pkgIterator.hasNext())
{
buffer.append(";");
}
}
return buffer.toString();
}
/** /**
* Load ScmConfiguration with JAXB * Load ScmConfiguration with JAXB
* *

View File

@@ -38,11 +38,13 @@ import org.apache.shiro.authc.DisabledAccountException;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
/** /**
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@Provider
public class DisabledAccountExceptionMapper public class DisabledAccountExceptionMapper
extends StatusExceptionMapper<DisabledAccountException> extends StatusExceptionMapper<DisabledAccountException>
{ {

View File

@@ -38,11 +38,13 @@ import org.apache.shiro.authc.ExcessiveAttemptsException;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
/** /**
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@Provider
public class ExcessiveAttemptsExceptionMapper public class ExcessiveAttemptsExceptionMapper
extends StatusExceptionMapper<ExcessiveAttemptsException> extends StatusExceptionMapper<ExcessiveAttemptsException>
{ {

View File

@@ -35,7 +35,10 @@ package sonia.scm.plugin;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.inject.Binder; import com.google.inject.Binder;
import com.google.inject.Module; import com.google.inject.Module;
@@ -43,43 +46,21 @@ import com.google.inject.Module;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.SCMContext;
import sonia.scm.plugin.ext.AnnotatedClass;
import sonia.scm.plugin.ext.AnnotationCollector;
import sonia.scm.plugin.ext.AnnotationProcessor;
import sonia.scm.plugin.ext.AnnotationScanner;
import sonia.scm.plugin.ext.AnnotationScannerFactory;
import sonia.scm.plugin.ext.DefaultAnnotationScannerFactory;
import sonia.scm.plugin.ext.Extension;
import sonia.scm.plugin.ext.ExtensionBinder; import sonia.scm.plugin.ext.ExtensionBinder;
import sonia.scm.plugin.ext.ExtensionProcessor;
import sonia.scm.plugin.ext.Extensions;
import sonia.scm.util.ClassLoaders; import sonia.scm.util.ClassLoaders;
import sonia.scm.util.IOUtil;
import sonia.scm.web.security.DefaultAuthenticationHandler;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLDecoder;
import java.util.Collection; import java.util.Collection;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import javax.servlet.ServletContext; import javax.xml.bind.JAXBContext;
import javax.servlet.ServletContextListener; import javax.xml.bind.JAXBException;
import javax.xml.bind.JAXB;
/** /**
* *
@@ -89,24 +70,11 @@ public class DefaultPluginLoader implements PluginLoader
{ {
/** Field description */ /** Field description */
public static final String ENCODING = "UTF-8"; public static final String PATH_MODULECONFIG = "META-INF/scm/module.xml";
/** Field description */
public static final String EXTENSION_JAR = ".jar";
/** Field description */ /** Field description */
public static final String PATH_PLUGINCONFIG = "META-INF/scm/plugin.xml"; public static final String PATH_PLUGINCONFIG = "META-INF/scm/plugin.xml";
/** Field description */
public static final String PATH_WEBINFLIB = "/WEB-INF/lib";
/** Field description */
public static final String PATH_SCMCORE = PATH_WEBINFLIB.concat("/scm-core");
/** Field description */
public static final String REGE_COREPLUGIN =
"^.*(?:/|\\\\)WEB-INF(?:/|\\\\)lib(?:/|\\\\).*\\.jar$";
/** the logger for DefaultPluginLoader */ /** the logger for DefaultPluginLoader */
private static final Logger logger = private static final Logger logger =
LoggerFactory.getLogger(DefaultPluginLoader.class); LoggerFactory.getLogger(DefaultPluginLoader.class);
@@ -116,90 +84,54 @@ public class DefaultPluginLoader implements PluginLoader
/** /**
* Constructs ... * Constructs ...
* *
*
* @param servletContext
*/ */
public DefaultPluginLoader(ServletContext servletContext) public DefaultPluginLoader()
{ {
this.servletContext = servletContext;
this.annotationScannerFactory = new DefaultAnnotationScannerFactory();
ClassLoader classLoader = ClassLoader classLoader =
ClassLoaders.getContextClassLoader(DefaultPluginLoader.class); ClassLoaders.getContextClassLoader(DefaultPluginLoader.class);
try try
{ {
locateCoreFile(); JAXBContext context = JAXBContext.newInstance(ScmModule.class,
loadPlugins(classLoader); Plugin.class);
scanForAnnotations();
findModules(); modules = getInstalled(classLoader, context, PATH_MODULECONFIG);
plugins = getInstalled(classLoader, context, PATH_PLUGINCONFIG);
appendExtensions(multiple, single, extensions, modules);
appendExtensions(multiple, single, extensions, plugins);
} }
catch (IOException ex) catch (Exception ex)
{ {
throw new RuntimeException(ex); throw Throwables.propagate(ex);
} }
} }
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param classLoader
* @param packages
* @param extensionPointProcessor
* @param extensionProcessor
* @param <T>
*
* @return
*/
public <T extends Annotation> AnnotationScanner createAnnotationScanner(
ClassLoader classLoader, Collection<String> packages,
AnnotationProcessor<ExtensionPoint> extensionPointProcessor,
AnnotationProcessor<Extension> extensionProcessor)
{
AnnotationScanner scanner = annotationScannerFactory.create(classLoader,
packages);
if (extensionPointProcessor != null)
{
scanner.addProcessor(ExtensionPoint.class, extensionPointProcessor);
}
if (extensionProcessor != null)
{
scanner.addProcessor(Extension.class, extensionProcessor);
}
return scanner;
}
/** /**
* Method description * Method description
* *
* *
* @param binder * @param binder
*/ */
@Override
public void processExtensions(Binder binder) public void processExtensions(Binder binder)
{ {
new ExtensionBinder(binder).bind(bounds, extensionPoints, extensions); logger.info("start processing extensions");
}
/** appendExtensions(multiple, single, extensions, modules);
* Method description appendExtensions(multiple, single, extensions, plugins);
*
* if (logger.isInfoEnabled())
* @param processor
*/
@Override
public void processExtensions(ExtensionProcessor processor)
{
for (AnnotatedClass<Extension> extension : extensions)
{ {
processor.processExtension(extension.getAnnotation(), logger.info(
extension.getAnnotatedClass()); "found {} extensions for {} multiple and {} single extension points",
extensions.size(), multiple.size(), single.size());
} }
// TODO bind jax-rs providers always as singleton
new ExtensionBinder(binder).bind(multiple, single, extensions);
} }
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
@@ -211,9 +143,9 @@ public class DefaultPluginLoader implements PluginLoader
* @return * @return
*/ */
@Override @Override
public Collection<Plugin> getInstalledPlugins() public Set<Module> getInjectionModules()
{ {
return installedPlugins; return ImmutableSet.copyOf(injectionModules);
} }
/** /**
@@ -222,9 +154,22 @@ public class DefaultPluginLoader implements PluginLoader
* *
* @return * @return
*/ */
public Set<Module> getModuleSet() @Override
public Collection<ScmModule> getInstalledModules()
{ {
return moduleSet; return modules;
}
/**
* Method description
*
*
* @return
*/
@Override
public Collection<Plugin> getInstalledPlugins()
{
return plugins;
} }
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
@@ -233,409 +178,103 @@ public class DefaultPluginLoader implements PluginLoader
* Method description * Method description
* *
* *
* @param moduleClass * @param multiple
* @param single
* @param extensions
* @param mods
*/ */
private void addModule(Class moduleClass) private void appendExtensions(Set<Class> multiple, Set<Class> single,
Set<Class> extensions, Iterable<? extends ScmModule> mods)
{ {
try for (ScmModule mod : mods)
{ {
logger.info("add module {}", moduleClass); for (ExtensionPointElement epe : mod.getExtensionPoints())
moduleSet.add((Module) moduleClass.newInstance());
}
catch (Exception ex)
{
logger.error(
"could not create module instance of ".concat(moduleClass.getName()),
ex);
}
}
/**
* Method description
*
*
* @param path
*
* @return
*/
private String decodePath(String path)
{
File file = new File(path);
if (!file.exists())
{
try
{ {
path = URLDecoder.decode(path, ENCODING); if (epe.isMultiple())
}
catch (IOException ex)
{
logger.error("could not decode path ".concat(path), ex);
}
}
return path;
}
/**
* Method description
*
*
* @param url
*
* @return
*/
private String extractResourcePath(URL url)
{
String path = url.toExternalForm();
if (path.startsWith("file:"))
{
path = path.substring("file:".length(),
path.length() - "/META-INF/scm/plugin.xml".length());
}
else
{
// jar:file:/some/path/file.jar!/META-INF/scm/plugin.xml
path = path.substring("jar:file:".length(), path.lastIndexOf('!'));
path = decodePath(path);
}
logger.trace("extrace resource path {} from url {}", path, url);
return path;
}
/**
* Method description
*
*/
private void findModules()
{
for (AnnotatedClass<Extension> extension : extensions)
{
Class extensionClass = extension.getAnnotatedClass();
if (Module.class.isAssignableFrom(extensionClass))
{
bounds.add(extension);
addModule(extensionClass);
}
}
}
/**
* Method description
*
*
* @param url
*/
private void loadPlugin(URL url)
{
String path = extractResourcePath(url);
if (logger.isTraceEnabled())
{
logger.trace("try to load plugin from {}", path);
}
try
{
boolean corePlugin = path.matches(REGE_COREPLUGIN);
if (logger.isInfoEnabled())
{
logger.info("load {}plugin {}", corePlugin
? "core "
: " ", path);
}
Plugin plugin = JAXB.unmarshal(url, Plugin.class);
PluginInformation info = plugin.getInformation();
PluginCondition condition = plugin.getCondition();
if (condition != null)
{
info.setCondition(condition);
}
if (info != null)
{
info.setState(corePlugin
? PluginState.CORE
: PluginState.INSTALLED);
}
plugin.setPath(path);
if (logger.isDebugEnabled())
{
logger.debug("add plugin {} to installed plugins", info.getId());
}
installedPlugins.add(plugin);
}
catch (Exception ex)
{
logger.error("could not load plugin ".concat(path), ex);
}
}
/**
* Method description
*
*
* @param classLoader
*
* @throws IOException
*/
private void loadPlugins(ClassLoader classLoader) throws IOException
{
Enumeration<URL> urlEnum = classLoader.getResources(PATH_PLUGINCONFIG);
if (urlEnum != null)
{
while (urlEnum.hasMoreElements())
{
URL url = urlEnum.nextElement();
loadPlugin(url);
}
if (logger.isDebugEnabled())
{
logger.debug("loaded {} plugins", installedPlugins.size());
}
}
else if (logger.isWarnEnabled())
{
logger.warn("no plugin descriptor found");
}
}
/**
* Method description
*
*
* @param classLoader
*
* @throws MalformedURLException
*/
@SuppressWarnings("unchecked")
private void locateCoreFile() throws MalformedURLException
{
Set<String> paths = servletContext.getResourcePaths(PATH_WEBINFLIB);
for (String path : paths)
{
if (path.startsWith(PATH_SCMCORE) && path.endsWith(EXTENSION_JAR))
{
coreFile = servletContext.getResource(path);
break;
}
}
if (coreFile == null)
{
throw new IllegalStateException("could not find scm-core file");
}
}
/**
* Method description
*
*
* @param classLoader
* @param packageSet
* @param extensionPointCollector
* @param extensionCollector
* @param file
*
* @throws IOException
*/
private void scanFile(ClassLoader classLoader, Collection<String> packageSet,
AnnotationCollector<ExtensionPoint> extensionPointCollector,
AnnotationCollector<Extension> extensionCollector, File file)
throws IOException
{
if (logger.isTraceEnabled())
{
String type = file.isDirectory()
? "directory"
: "jar";
logger.trace("search extensions in packages {} of {} file {}",
new Object[] { packageSet,
type, file });
}
if (file.isDirectory())
{
createAnnotationScanner(classLoader, packageSet, extensionPointCollector,
extensionCollector).scanDirectory(file);
}
else
{
InputStream input = null;
try
{
input = new FileInputStream(file);
createAnnotationScanner(classLoader, packageSet,
extensionPointCollector, extensionCollector).scanArchive(input);
}
finally
{
IOUtil.close(input);
}
}
}
/**
* Method description
*
*
* @param processor
*
* @param binder
*/
private void scanForAnnotations()
{
ClassLoader classLoader =
ClassLoaders.getContextClassLoader(DefaultPluginLoader.class);
AnnotationCollector<ExtensionPoint> extensionPointCollector =
new AnnotationCollector<ExtensionPoint>();
AnnotationCollector<Extension> extensionCollector =
new AnnotationCollector<Extension>();
logger.debug("search extension points in {}", coreFile);
Set<String> corePackages = ImmutableSet.of("sonia.scm");
try
{
scanURL(classLoader, corePackages, extensionPointCollector, null,
coreFile);
}
catch (Exception ex)
{
throw new IllegalStateException("could not process scm-core", ex);
}
for (Plugin plugin : installedPlugins)
{
if (logger.isDebugEnabled())
{
logger.debug("search extensions from plugin {}",
plugin.getInformation().getId());
}
try
{
Set<String> packageSet = plugin.getPackageSet();
if (packageSet == null)
{ {
packageSet = new HashSet<String>(); multiple.add(epe.getClazz());
}
packageSet.add(SCMContext.DEFAULT_PACKAGE);
File pluginFile = new File(plugin.getPath());
if (pluginFile.exists())
{
scanFile(classLoader, packageSet, extensionPointCollector,
extensionCollector, pluginFile);
} }
else else
{ {
logger.error("could not find plugin file {}", plugin.getPath()); single.add(epe.getClazz());
} }
} }
catch (IOException ex)
for (Class extensionClass : mod.getExtensions())
{ {
logger.error("error during extension processing", ex); if (Module.class.isAssignableFrom(extensionClass))
{
try
{
injectionModules.add((Module) extensionClass.newInstance());
}
catch (Exception ex)
{
logger.error("could not create instance of module", ex);
}
}
else
{
extensions.add(extensionClass);
}
} }
Iterables.addAll(extensions, mod.getJaxrsProviders());
Iterables.addAll(extensions, mod.getJaxrsResources());
} }
//J-
extensionPoints = extensionPointCollector.getAnnotatedClasses();
extensionPoints.add(
new AnnotatedClass<ExtensionPoint>(
Extensions.createExtensionPoint(true),
ServletContextListener.class
)
);
extensions = extensionCollector.getAnnotatedClasses();
extensions.add(
new AnnotatedClass<Extension>(
Extensions.createExtension(),
DefaultAuthenticationHandler.class
)
);
//J+
} }
//~--- get methods ----------------------------------------------------------
/** /**
* Method description * Method description
* *
* *
* @param classLoader * @param classLoader
* @param packageSet * @param context
* @param extensionPointCollector * @param path
* @param extensionCollector * @param <T>
* @param file *
* @return
* *
* @throws IOException * @throws IOException
* @throws JAXBException
*/ */
private void scanURL(ClassLoader classLoader, Collection<String> packageSet, private <T> Set<T> getInstalled(ClassLoader classLoader, JAXBContext context,
AnnotationCollector<ExtensionPoint> extensionPointCollector, String path)
AnnotationCollector<Extension> extensionCollector, URL file) throws IOException, JAXBException
throws IOException
{ {
InputStream content = null; Builder<T> builder = ImmutableSet.builder();
Enumeration<URL> urls = classLoader.getResources(path);
try while (urls.hasMoreElements())
{ {
content = file.openStream(); URL url = urls.nextElement();
createAnnotationScanner(classLoader, packageSet, extensionPointCollector, T module = (T) context.createUnmarshaller().unmarshal(url);
extensionCollector).scanArchive(content);
} builder.add(module);
finally
{
IOUtil.close(content);
} }
return builder.build();
} }
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** Field description */ /** Field description */
private final AnnotationScannerFactory annotationScannerFactory; private Set<ScmModule> modules;
/** Field description */ /** Field description */
private final Set<AnnotatedClass<Extension>> bounds = Sets.newHashSet(); private Set<Class> multiple = Sets.newHashSet();
/** Field description */ /** Field description */
private final Set<Module> moduleSet = Sets.newHashSet(); private Set<Plugin> plugins;
/** Field description */ /** Field description */
private final Set<Plugin> installedPlugins = new HashSet<Plugin>(); private Set<Class> single = Sets.newHashSet();
/** Field description */ /** Field description */
private final ServletContext servletContext; private Set<Module> injectionModules = Sets.newHashSet();
/** Field description */ /** Field description */
private URL coreFile; private Set<Class> extensions = Sets.newHashSet();
/** Field description */
private Set<AnnotatedClass<ExtensionPoint>> extensionPoints;
/** Field description */
private Set<AnnotatedClass<Extension>> extensions;
} }

View File

@@ -211,7 +211,8 @@ public class DefaultPluginManager implements PluginManager
// ugly workaround // ugly workaround
Plugin newPlugin = new Plugin(); Plugin newPlugin = new Plugin();
newPlugin.setInformation(plugin); // TODO check
// newPlugin.setInformation(plugin);
installedPlugins.put(id, newPlugin); installedPlugins.put(id, newPlugin);
} }
} }

View File

@@ -38,6 +38,8 @@ package sonia.scm.plugin.ext;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.inject.Binder; import com.google.inject.Binder;
import com.google.inject.Scopes;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.binder.ScopedBindingBuilder; import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.multibindings.Multibinder; import com.google.inject.multibindings.Multibinder;
@@ -45,18 +47,19 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.EagerSingleton; import sonia.scm.EagerSingleton;
import sonia.scm.plugin.ExtensionPoint;
import sonia.scm.util.Util; import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import java.util.Set; import java.util.Set;
import javax.ws.rs.ext.Provider;
/** /**
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
public class ExtensionBinder public final class ExtensionBinder
{ {
/** /**
@@ -84,54 +87,58 @@ public class ExtensionBinder
* Method description * Method description
* *
* *
* * @param mutipleExtensionPoints
* @param bounds * @param singleExtensionPoints
* @param extensionPoints
* @param extensions * @param extensions
*/ */
public void bind(Set<AnnotatedClass<Extension>> bounds, public void bind(Set<Class> mutipleExtensionPoints,
Set<AnnotatedClass<ExtensionPoint>> extensionPoints, Set<Class> singleExtensionPoints, Set<Class> extensions)
Set<AnnotatedClass<Extension>> extensions)
{ {
if (logger.isInfoEnabled()) if (logger.isInfoEnabled())
{ {
logger.info("bind {} extensions to {} extension points", logger.info("bind {} extensions to {} extension points",
extensions.size(), extensionPoints.size()); extensions.size(),
mutipleExtensionPoints.size() + singleExtensionPoints.size());
} }
for (AnnotatedClass<ExtensionPoint> extensionPoint : extensionPoints) Set<Class> boundClasses = Sets.newHashSet();
{
ExtensionPoint extensionPointAnnotation = extensionPoint.getAnnotation();
Class extensionPointClass = extensionPoint.getAnnotatedClass();
if (extensionPointAnnotation.multi()) for (Class extensionPointClass : mutipleExtensionPoints)
{
bindMultiExtensionPoint(boundClasses, extensionPointClass, extensions);
}
for (Class extensionPointClass : singleExtensionPoints)
{
bindExtensionPoint(boundClasses, extensionPointClass, extensions);
}
Set<Class> extensionsCopy = Sets.newHashSet(extensions);
Iterables.removeAll(extensionsCopy, boundClasses);
for (Class extension : extensionsCopy)
{
AnnotatedBindingBuilder abb = binder.bind(extension);
if (isProvider(extension))
{ {
bindMultiExtensionPoint(bounds, extensionPointClass, extensions); logger.info("bind provider {} as singleton", extension);
abb.in(Scopes.SINGLETON);
}
else if (isEagerSingleton(extension))
{
logger.info("bind {} as eager singleton, without extensionpoint",
extension);
abb.asEagerSingleton();
} }
else else
{ {
bindExtensionPoint(bounds, extensionPointClass, extensions); logger.info("bind {}, without extensionpoint", extension);
binder.bind(extension);
} }
} }
Set<AnnotatedClass<Extension>> extensionsCopy = Sets.newHashSet(extensions);
Iterables.removeAll(extensionsCopy, bounds);
for (AnnotatedClass<Extension> extension : extensionsCopy)
{
boolean eagerSingleton = isEagerSingleton(extension.getAnnotatedClass());
String as = Util.EMPTY_STRING;
if (eagerSingleton)
{
as = " as eager singleton";
}
logger.info("bind {}{}, without extensionpoint",
extension.getAnnotatedClass(), as);
binder.bind(extension.getAnnotatedClass());
}
} }
/** /**
@@ -140,23 +147,31 @@ public class ExtensionBinder
* *
* *
* @param found * @param found
*
* @param boundClasses
* @param extensionPointClass * @param extensionPointClass
* @param extensions * @param extensions
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void bindExtensionPoint(Set<AnnotatedClass<Extension>> found, private void bindExtensionPoint(Set<Class> boundClasses,
Class extensionPointClass, Set<AnnotatedClass<Extension>> extensions) Class extensionPointClass, Set<Class> extensions)
{ {
for (AnnotatedClass<Extension> extension : extensions) boolean bound = false;
{
Class extensionClass = extension.getAnnotatedClass();
for (Class extensionClass : extensions)
{
if (extensionPointClass.isAssignableFrom(extensionClass)) if (extensionPointClass.isAssignableFrom(extensionClass))
{ {
found.add(extension); if (bound)
bindSingleInstance(extensionPointClass, extensionClass); {
throw new IllegalStateException(
"extension point ".concat(extensionPointClass.getName()).concat(
" is not multiple and is already bound to another class"));
}
break; bindSingleInstance(extensionPointClass, extensionClass);
boundClasses.add(extensionClass);
bound = true;
} }
} }
} }
@@ -167,12 +182,14 @@ public class ExtensionBinder
* *
* *
* @param found * @param found
*
* @param boundClasses
* @param extensionPointClass * @param extensionPointClass
* @param extensions * @param extensions
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void bindMultiExtensionPoint(Set<AnnotatedClass<Extension>> found, private void bindMultiExtensionPoint(Set<Class> boundClasses,
Class extensionPointClass, Set<AnnotatedClass<Extension>> extensions) Class extensionPointClass, Iterable<Class> extensions)
{ {
if (logger.isInfoEnabled()) if (logger.isInfoEnabled())
{ {
@@ -182,10 +199,8 @@ public class ExtensionBinder
Multibinder multibinder = Multibinder.newSetBinder(binder, Multibinder multibinder = Multibinder.newSetBinder(binder,
extensionPointClass); extensionPointClass);
for (AnnotatedClass<Extension> extension : extensions) for (Class extensionClass : extensions)
{ {
Class extensionClass = extension.getAnnotatedClass();
if (extensionPointClass.isAssignableFrom(extensionClass)) if (extensionPointClass.isAssignableFrom(extensionClass))
{ {
boolean eagerSingleton = isEagerSingleton(extensionClass); boolean eagerSingleton = isEagerSingleton(extensionClass);
@@ -203,8 +218,6 @@ public class ExtensionBinder
extensionClass.getName(), extensionPointClass.getName(), as); extensionClass.getName(), extensionPointClass.getName(), as);
} }
found.add(extension);
ScopedBindingBuilder sbb = multibinder.addBinding().to(extensionClass); ScopedBindingBuilder sbb = multibinder.addBinding().to(extensionClass);
if (eagerSingleton) if (eagerSingleton)
@@ -212,6 +225,8 @@ public class ExtensionBinder
sbb.asEagerSingleton(); sbb.asEagerSingleton();
logger.info("bind {} as eager singleton"); logger.info("bind {} as eager singleton");
} }
boundClasses.add(extensionClass);
} }
} }
} }
@@ -261,13 +276,26 @@ public class ExtensionBinder
* *
* @return * @return
*/ */
private boolean isEagerSingleton(Class<?> extensionClass) private boolean isEagerSingleton(Class extensionClass)
{ {
return extensionClass.isAnnotationPresent(EagerSingleton.class); return extensionClass.isAnnotationPresent(EagerSingleton.class);
} }
/**
* Method description
*
*
* @param extensionClass
*
* @return
*/
private boolean isProvider(Class extensionClass)
{
return extensionClass.isAnnotationPresent(Provider.class);
}
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** Field description */ /** Field description */
private Binder binder; private final Binder binder;
} }

View File

@@ -52,11 +52,13 @@ import java.io.IOException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import sonia.scm.plugin.ext.Extension;
/** /**
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@Extension
@Singleton @Singleton
public class DefaultAuthenticationHandler implements AuthenticationHandler public class DefaultAuthenticationHandler implements AuthenticationHandler
{ {