blob: 9d93ed2fda45fff4cd85b1c1bc918513027d05c7 [file] [log] [blame]
// 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.extensions.webui.JavaScriptPlugin.STATIC_INIT_JS;
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.ListMultimap;
import com.google.common.flogger.FluentLogger;
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.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.webui.JavaScriptPlugin;
import com.google.gerrit.extensions.webui.WebUiPlugin;
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.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
class AutoRegisterModules {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
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 ListMultimap<TypeLiteral<?>, Class<?>> sysListen;
private String initJs;
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() : new ModuleGenerator.NOP();
this.httpGen = env.hasHttpModule() ? env.newHttpModuleGenerator() : new ModuleGenerator.NOP();
}
AutoRegisterModules discover() throws InvalidPluginException {
sysSingletons = new HashSet<>();
sysListen = LinkedListMultimap.create();
initJs = null;
sshGen.setPluginName(pluginName);
httpGen.setPluginName(pluginName);
scan();
if (!sysSingletons.isEmpty() || !sysListen.isEmpty() || initJs != null) {
sysModule = makeSystemModule();
}
sshModule = sshGen.create();
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);
}
if (initJs != null) {
DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new JavaScriptPlugin(initJs));
}
}
};
}
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);
}
if (env.hasHttpModule()) {
exportInitJs();
}
}
private void exportInitJs() {
try {
if (scanner.getEntry(STATIC_INIT_JS).isPresent()) {
initJs = STATIC_INIT_JS;
}
} catch (IOException e) {
logger.atWarning().withCause(e).log(
"Cannot access %s from plugin %s: "
+ "JavaScript auto-discovered plugin will not be registered",
STATIC_INIT_JS, pluginName);
}
}
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) {
logger.atWarning().log(
"In plugin %s asm incorrectly parsed %s with @Export(\"%s\")",
pluginName, clazz.getName(), def.annotationValue);
return;
}
if (is("org.apache.sshd.server.command.Command", clazz)) {
sshGen.export(export, clazz);
} else if (is("javax.servlet.http.HttpServlet", clazz)) {
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 {
logger.atWarning().log(
"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();
}
}
}