blob: c6133ea802dfa038ca4b4eb0ebba54c3d608e88b [file] [log] [blame]
// 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 static com.google.common.base.Preconditions.checkState;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.extensions.annotations.Export;
import com.google.gerrit.server.plugins.Plugin.ApiType;
import com.google.inject.Module;
import com.google.inject.servlet.ServletModule;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.Manifest;
/**
* Base plugin scanner for a set of pre-loaded classes.
*
* <p>Utility base class for simplifying the development of Server plugin scanner based on a set of
* externally pre-loaded classes.
*
* <p>Extending this class you can implement very easily a PluginContentScanner from a set of
* pre-loaded Java Classes and an API Type. The convention used by this class is: - there is at most
* one Guice module per Gerrit module type (SysModule, HttpModule, SshModule) - plugin is set to be
* restartable in Gerrit Plugin MANIFEST - only Export and Listen annotated classes can be
* self-discovered
*/
public abstract class AbstractPreloadedPluginScanner implements PluginContentScanner {
protected final String pluginName;
protected final String pluginVersion;
protected final Set<Class<?>> preloadedClasses;
protected final ApiType apiType;
private Class<?> sshModuleClass;
private Class<?> httpModuleClass;
private Class<?> sysModuleClass;
public AbstractPreloadedPluginScanner(
String pluginName,
String pluginVersion,
Set<Class<?>> preloadedClasses,
Plugin.ApiType apiType) {
this.pluginName = pluginName;
this.pluginVersion = pluginVersion;
this.preloadedClasses = preloadedClasses;
this.apiType = apiType;
}
@Override
public Manifest getManifest() throws IOException {
scanGuiceModules(preloadedClasses);
StringBuilder manifestString =
new StringBuilder(
"PluginName: "
+ pluginName
+ "\n"
+ "Implementation-Version: "
+ pluginVersion
+ "\n"
+ "Gerrit-ReloadMode: restart\n"
+ "Gerrit-ApiType: "
+ apiType
+ "\n");
appendIfNotNull(manifestString, "Gerrit-SshModule: ", sshModuleClass);
appendIfNotNull(manifestString, "Gerrit-HttpModule: ", httpModuleClass);
appendIfNotNull(manifestString, "Gerrit-Module: ", sysModuleClass);
return new Manifest(new ByteArrayInputStream(manifestString.toString().getBytes(UTF_8)));
}
@Override
public Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> scan(
String pluginName, Iterable<Class<? extends Annotation>> annotations)
throws InvalidPluginException {
ImmutableMap.Builder<Class<? extends Annotation>, Iterable<ExtensionMetaData>> result =
ImmutableMap.builder();
for (Class<? extends Annotation> annotation : annotations) {
Set<ExtensionMetaData> classMetaDataSet = new HashSet<>();
result.put(annotation, classMetaDataSet);
for (Class<?> clazz : preloadedClasses) {
if (!Modifier.isAbstract(clazz.getModifiers()) && clazz.getAnnotation(annotation) != null) {
classMetaDataSet.add(
new ExtensionMetaData(clazz.getName(), getExportAnnotationValue(clazz, annotation)));
}
}
}
return result.build();
}
private void appendIfNotNull(StringBuilder string, String header, Class<?> guiceModuleClass) {
if (guiceModuleClass != null) {
string.append(header);
string.append(guiceModuleClass.getName());
string.append("\n");
}
}
private void scanGuiceModules(Set<Class<?>> classes) throws IOException {
try {
Class<?> sysModuleBaseClass = Module.class;
Class<?> httpModuleBaseClass = ServletModule.class;
Class<?> sshModuleBaseClass = Class.forName("com.google.gerrit.sshd.CommandModule");
sshModuleClass = null;
httpModuleClass = null;
sysModuleClass = null;
for (Class<?> clazz : classes) {
if (clazz.isLocalClass()) {
continue;
}
if (sshModuleBaseClass.isAssignableFrom(clazz)) {
sshModuleClass = getUniqueGuiceModule(sshModuleBaseClass, sshModuleClass, clazz);
} else if (httpModuleBaseClass.isAssignableFrom(clazz)) {
httpModuleClass = getUniqueGuiceModule(httpModuleBaseClass, httpModuleClass, clazz);
} else if (sysModuleBaseClass.isAssignableFrom(clazz)) {
sysModuleClass = getUniqueGuiceModule(sysModuleBaseClass, sysModuleClass, clazz);
}
}
} catch (ClassNotFoundException e) {
throw new IOException("Cannot find base Gerrit classes for Guice Plugin Modules", e);
}
}
private Class<?> getUniqueGuiceModule(
Class<?> guiceModuleBaseClass,
Class<?> existingGuiceModuleName,
Class<?> newGuiceModuleClass) {
checkState(
existingGuiceModuleName == null,
"Multiple %s implementations: %s, %s",
guiceModuleBaseClass,
existingGuiceModuleName,
newGuiceModuleClass);
return newGuiceModuleClass;
}
private String getExportAnnotationValue(
Class<?> scriptClass, Class<? extends Annotation> annotation) {
return annotation == Export.class ? scriptClass.getAnnotation(Export.class).value() : "";
}
}