// Copyright (C) 2012 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 static com.google.gerrit.server.plugins.AutoRegisterUtil.calculateBindAnnotation; import static com.google.gerrit.server.plugins.PluginGuiceEnvironment.is; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.google.gerrit.extensions.annotations.Export; import com.google.gerrit.extensions.annotations.ExtensionPoint; import com.google.gerrit.extensions.annotations.Listen; import com.google.gerrit.server.plugins.PluginContentScanner.ExtensionMetaData; import com.google.inject.AbstractModule; import com.google.inject.Module; import com.google.inject.Scopes; import com.google.inject.TypeLiteral; import java.lang.annotation.Annotation; import java.lang.reflect.ParameterizedType; import java.util.Arrays; import java.util.Map; import java.util.Set; class AutoRegisterModules { private final String pluginName; private final PluginGuiceEnvironment env; private final PluginContentScanner scanner; private final ClassLoader classLoader; private final ModuleGenerator sshGen; private final ModuleGenerator httpGen; private Set<Class<?>> sysSingletons; private Multimap<TypeLiteral<?>, Class<?>> sysListen; Module sysModule; Module sshModule; Module httpModule; AutoRegisterModules(String pluginName, PluginGuiceEnvironment env, PluginContentScanner scanner, ClassLoader classLoader) { this.pluginName = pluginName; this.env = env; this.scanner = scanner; this.classLoader = classLoader; this.sshGen = env.hasSshModule() ? env.newSshModuleGenerator() : null; this.httpGen = env.hasHttpModule() ? env.newHttpModuleGenerator() : null; } AutoRegisterModules discover() throws InvalidPluginException { sysSingletons = Sets.newHashSet(); sysListen = LinkedListMultimap.create(); if (sshGen != null) { sshGen.setPluginName(pluginName); } if (httpGen != null) { httpGen.setPluginName(pluginName); } scan(); if (!sysSingletons.isEmpty() || !sysListen.isEmpty()) { sysModule = makeSystemModule(); } if (sshGen != null) { sshModule = sshGen.create(); } if (httpGen != null) { httpModule = httpGen.create(); } return this; } private Module makeSystemModule() { return new AbstractModule() { @Override protected void configure() { for (Class<?> clazz : sysSingletons) { bind(clazz).in(Scopes.SINGLETON); } for (Map.Entry<TypeLiteral<?>, Class<?>> e : sysListen.entries()) { @SuppressWarnings("unchecked") TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey(); @SuppressWarnings("unchecked") Class<Object> impl = (Class<Object>) e.getValue(); Annotation n = calculateBindAnnotation(impl); bind(type).annotatedWith(n).to(impl); } } }; } private void scan() throws InvalidPluginException { Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> extensions = scanner.scan(pluginName, Arrays.asList(Export.class, Listen.class)); for (ExtensionMetaData export : extensions.get(Export.class)) { export(export); } for (ExtensionMetaData listener : extensions.get(Listen.class)) { listen(listener); } } private void export(ExtensionMetaData def) throws InvalidPluginException { Class<?> clazz; try { clazz = Class.forName(def.className, false, classLoader); } catch (ClassNotFoundException err) { throw new InvalidPluginException(String.format( "Cannot load %s with @Export(\"%s\")", def.className, def.annotationValue), err); } Export export = clazz.getAnnotation(Export.class); if (export == null) { PluginLoader.log.warn(String.format( "In plugin %s asm incorrectly parsed %s with @Export(\"%s\")", pluginName, clazz.getName(), def.annotationValue)); return; } if (is("org.apache.sshd.server.Command", clazz)) { if (sshGen != null) { sshGen.export(export, clazz); } } else if (is("javax.servlet.http.HttpServlet", clazz)) { if (httpGen != null) { httpGen.export(export, clazz); listen(clazz, clazz); } } else { int cnt = sysListen.size(); listen(clazz, clazz); if (cnt == sysListen.size()) { // If no bindings were recorded, the extension isn't recognized. throw new InvalidPluginException(String.format( "Class %s with @Export(\"%s\") not supported", clazz.getName(), export.value())); } } } private void listen(ExtensionMetaData def) throws InvalidPluginException { Class<?> clazz; try { clazz = Class.forName(def.className, false, classLoader); } catch (ClassNotFoundException err) { throw new InvalidPluginException(String.format( "Cannot load %s with @Listen", def.className), err); } Listen listen = clazz.getAnnotation(Listen.class); if (listen != null) { listen(clazz, clazz); } else { PluginLoader.log.warn(String.format( "In plugin %s asm incorrectly parsed %s with @Listen", pluginName, clazz.getName())); } } private void listen(java.lang.reflect.Type type, Class<?> clazz) throws InvalidPluginException { while (type != null) { Class<?> rawType; if (type instanceof ParameterizedType) { rawType = (Class<?>) ((ParameterizedType) type).getRawType(); } else if (type instanceof Class) { rawType = (Class<?>) type; } else { return; } if (rawType.getAnnotation(ExtensionPoint.class) != null) { TypeLiteral<?> tl = TypeLiteral.get(type); if (env.hasDynamicItem(tl)) { sysSingletons.add(clazz); sysListen.put(tl, clazz); httpGen.listen(tl, clazz); sshGen.listen(tl, clazz); } else if (env.hasDynamicSet(tl)) { sysSingletons.add(clazz); sysListen.put(tl, clazz); httpGen.listen(tl, clazz); sshGen.listen(tl, clazz); } else if (env.hasDynamicMap(tl)) { if (clazz.getAnnotation(Export.class) == null) { throw new InvalidPluginException(String.format( "Class %s requires @Export(\"name\") annotation for %s", clazz.getName(), rawType.getName())); } sysSingletons.add(clazz); sysListen.put(tl, clazz); httpGen.listen(tl, clazz); sshGen.listen(tl, clazz); } else { throw new InvalidPluginException(String.format( "Cannot register %s, server does not accept %s", clazz.getName(), rawType.getName())); } return; } java.lang.reflect.Type[] interfaces = rawType.getGenericInterfaces(); if (interfaces != null) { for (java.lang.reflect.Type i : interfaces) { listen(i, clazz); } } type = rawType.getGenericSuperclass(); } } }