Line data Source code
1 : // Copyright (C) 2014 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.plugins; 16 : 17 : import static com.google.common.base.Preconditions.checkState; 18 : import static java.nio.charset.StandardCharsets.UTF_8; 19 : 20 : import com.google.common.collect.ImmutableMap; 21 : import com.google.gerrit.extensions.annotations.Export; 22 : import com.google.gerrit.server.plugins.Plugin.ApiType; 23 : import com.google.inject.Module; 24 : import com.google.inject.servlet.ServletModule; 25 : import java.io.ByteArrayInputStream; 26 : import java.io.IOException; 27 : import java.lang.annotation.Annotation; 28 : import java.lang.reflect.Modifier; 29 : import java.util.HashSet; 30 : import java.util.Map; 31 : import java.util.Set; 32 : import java.util.jar.Manifest; 33 : 34 : /** 35 : * Base plugin scanner for a set of pre-loaded classes. 36 : * 37 : * <p>Utility base class for simplifying the development of Server plugin scanner based on a set of 38 : * externally pre-loaded classes. 39 : * 40 : * <p>Extending this class you can implement very easily a PluginContentScanner from a set of 41 : * pre-loaded Java Classes and an API Type. The convention used by this class is: - there is at most 42 : * one Guice module per Gerrit module type (SysModule, HttpModule, SshModule) - plugin is set to be 43 : * restartable in Gerrit Plugin MANIFEST - only Export and Listen annotated classes can be 44 : * self-discovered 45 : */ 46 : public abstract class AbstractPreloadedPluginScanner implements PluginContentScanner { 47 : protected final String pluginName; 48 : protected final String pluginVersion; 49 : protected final Set<Class<?>> preloadedClasses; 50 : protected final ApiType apiType; 51 : 52 : private Class<?> sshModuleClass; 53 : private Class<?> httpModuleClass; 54 : private Class<?> sysModuleClass; 55 : 56 : public AbstractPreloadedPluginScanner( 57 : String pluginName, 58 : String pluginVersion, 59 : Set<Class<?>> preloadedClasses, 60 0 : Plugin.ApiType apiType) { 61 0 : this.pluginName = pluginName; 62 0 : this.pluginVersion = pluginVersion; 63 0 : this.preloadedClasses = preloadedClasses; 64 0 : this.apiType = apiType; 65 0 : } 66 : 67 : @Override 68 : public Manifest getManifest() throws IOException { 69 0 : scanGuiceModules(preloadedClasses); 70 0 : StringBuilder manifestString = 71 : new StringBuilder( 72 : "PluginName: " 73 : + pluginName 74 : + "\n" 75 : + "Implementation-Version: " 76 : + pluginVersion 77 : + "\n" 78 : + "Gerrit-ReloadMode: restart\n" 79 : + "Gerrit-ApiType: " 80 : + apiType 81 : + "\n"); 82 0 : appendIfNotNull(manifestString, "Gerrit-SshModule: ", sshModuleClass); 83 0 : appendIfNotNull(manifestString, "Gerrit-HttpModule: ", httpModuleClass); 84 0 : appendIfNotNull(manifestString, "Gerrit-Module: ", sysModuleClass); 85 0 : return new Manifest(new ByteArrayInputStream(manifestString.toString().getBytes(UTF_8))); 86 : } 87 : 88 : @Override 89 : public Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> scan( 90 : String pluginName, Iterable<Class<? extends Annotation>> annotations) 91 : throws InvalidPluginException { 92 : ImmutableMap.Builder<Class<? extends Annotation>, Iterable<ExtensionMetaData>> result = 93 0 : ImmutableMap.builder(); 94 : 95 0 : for (Class<? extends Annotation> annotation : annotations) { 96 0 : Set<ExtensionMetaData> classMetaDataSet = new HashSet<>(); 97 0 : result.put(annotation, classMetaDataSet); 98 : 99 0 : for (Class<?> clazz : preloadedClasses) { 100 0 : if (!Modifier.isAbstract(clazz.getModifiers()) && clazz.getAnnotation(annotation) != null) { 101 0 : classMetaDataSet.add( 102 0 : new ExtensionMetaData(clazz.getName(), getExportAnnotationValue(clazz, annotation))); 103 : } 104 0 : } 105 0 : } 106 0 : return result.build(); 107 : } 108 : 109 : private void appendIfNotNull(StringBuilder string, String header, Class<?> guiceModuleClass) { 110 0 : if (guiceModuleClass != null) { 111 0 : string.append(header); 112 0 : string.append(guiceModuleClass.getName()); 113 0 : string.append("\n"); 114 : } 115 0 : } 116 : 117 : private void scanGuiceModules(Set<Class<?>> classes) throws IOException { 118 : try { 119 0 : Class<?> sysModuleBaseClass = Module.class; 120 0 : Class<?> httpModuleBaseClass = ServletModule.class; 121 0 : Class<?> sshModuleBaseClass = Class.forName("com.google.gerrit.sshd.CommandModule"); 122 0 : sshModuleClass = null; 123 0 : httpModuleClass = null; 124 0 : sysModuleClass = null; 125 : 126 0 : for (Class<?> clazz : classes) { 127 0 : if (clazz.isLocalClass()) { 128 0 : continue; 129 : } 130 : 131 0 : if (sshModuleBaseClass.isAssignableFrom(clazz)) { 132 0 : sshModuleClass = getUniqueGuiceModule(sshModuleBaseClass, sshModuleClass, clazz); 133 0 : } else if (httpModuleBaseClass.isAssignableFrom(clazz)) { 134 0 : httpModuleClass = getUniqueGuiceModule(httpModuleBaseClass, httpModuleClass, clazz); 135 0 : } else if (sysModuleBaseClass.isAssignableFrom(clazz)) { 136 0 : sysModuleClass = getUniqueGuiceModule(sysModuleBaseClass, sysModuleClass, clazz); 137 : } 138 0 : } 139 0 : } catch (ClassNotFoundException e) { 140 0 : throw new IOException("Cannot find base Gerrit classes for Guice Plugin Modules", e); 141 0 : } 142 0 : } 143 : 144 : private Class<?> getUniqueGuiceModule( 145 : Class<?> guiceModuleBaseClass, 146 : Class<?> existingGuiceModuleName, 147 : Class<?> newGuiceModuleClass) { 148 0 : checkState( 149 : existingGuiceModuleName == null, 150 : "Multiple %s implementations: %s, %s", 151 : guiceModuleBaseClass, 152 : existingGuiceModuleName, 153 : newGuiceModuleClass); 154 0 : return newGuiceModuleClass; 155 : } 156 : 157 : private String getExportAnnotationValue( 158 : Class<?> scriptClass, Class<? extends Annotation> annotation) { 159 0 : return annotation == Export.class ? scriptClass.getAnnotation(Export.class).value() : ""; 160 : } 161 : }