/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java

https://code.google.com/
Java | 393 lines | 333 code | 46 blank | 14 comment | 70 complexity | 9a42ef39c55d722d5d5def62c2284481 MD5 | raw file
  1. // Copyright (C) 2012 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. package com.google.gerrit.server.plugins;
  15. import static com.google.gerrit.server.plugins.PluginGuiceEnvironment.is;
  16. import com.google.common.collect.LinkedListMultimap;
  17. import com.google.common.collect.Multimap;
  18. import com.google.common.collect.Sets;
  19. import com.google.gerrit.extensions.annotations.Export;
  20. import com.google.gerrit.extensions.annotations.ExtensionPoint;
  21. import com.google.gerrit.extensions.annotations.Listen;
  22. import com.google.inject.AbstractModule;
  23. import com.google.inject.Module;
  24. import com.google.inject.Scopes;
  25. import com.google.inject.TypeLiteral;
  26. import com.google.inject.internal.UniqueAnnotations;
  27. import org.eclipse.jgit.util.IO;
  28. import org.objectweb.asm.AnnotationVisitor;
  29. import org.objectweb.asm.Attribute;
  30. import org.objectweb.asm.ClassReader;
  31. import org.objectweb.asm.ClassVisitor;
  32. import org.objectweb.asm.FieldVisitor;
  33. import org.objectweb.asm.MethodVisitor;
  34. import org.objectweb.asm.Opcodes;
  35. import org.objectweb.asm.Type;
  36. import java.io.IOException;
  37. import java.io.InputStream;
  38. import java.lang.annotation.Annotation;
  39. import java.lang.reflect.ParameterizedType;
  40. import java.util.Enumeration;
  41. import java.util.Map;
  42. import java.util.Set;
  43. import java.util.jar.JarEntry;
  44. import java.util.jar.JarFile;
  45. class AutoRegisterModules {
  46. private static final int SKIP_ALL = ClassReader.SKIP_CODE
  47. | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
  48. private final String pluginName;
  49. private final PluginGuiceEnvironment env;
  50. private final JarFile jarFile;
  51. private final ClassLoader classLoader;
  52. private final ModuleGenerator sshGen;
  53. private final ModuleGenerator httpGen;
  54. private Set<Class<?>> sysSingletons;
  55. private Multimap<TypeLiteral<?>, Class<?>> sysListen;
  56. Module sysModule;
  57. Module sshModule;
  58. Module httpModule;
  59. AutoRegisterModules(String pluginName,
  60. PluginGuiceEnvironment env,
  61. JarFile jarFile,
  62. ClassLoader classLoader) {
  63. this.pluginName = pluginName;
  64. this.env = env;
  65. this.jarFile = jarFile;
  66. this.classLoader = classLoader;
  67. this.sshGen = env.hasSshModule() ? env.newSshModuleGenerator() : null;
  68. this.httpGen = env.hasHttpModule() ? env.newHttpModuleGenerator() : null;
  69. }
  70. AutoRegisterModules discover() throws InvalidPluginException {
  71. sysSingletons = Sets.newHashSet();
  72. sysListen = LinkedListMultimap.create();
  73. if (sshGen != null) {
  74. sshGen.setPluginName(pluginName);
  75. }
  76. if (httpGen != null) {
  77. httpGen.setPluginName(pluginName);
  78. }
  79. scan();
  80. if (!sysSingletons.isEmpty() || !sysListen.isEmpty()) {
  81. sysModule = makeSystemModule();
  82. }
  83. if (sshGen != null) {
  84. sshModule = sshGen.create();
  85. }
  86. if (httpGen != null) {
  87. httpModule = httpGen.create();
  88. }
  89. return this;
  90. }
  91. private Module makeSystemModule() {
  92. return new AbstractModule() {
  93. @Override
  94. protected void configure() {
  95. for (Class<?> clazz : sysSingletons) {
  96. bind(clazz).in(Scopes.SINGLETON);
  97. }
  98. for (Map.Entry<TypeLiteral<?>, Class<?>> e : sysListen.entries()) {
  99. @SuppressWarnings("unchecked")
  100. TypeLiteral<Object> type = (TypeLiteral<Object>) e.getKey();
  101. @SuppressWarnings("unchecked")
  102. Class<Object> impl = (Class<Object>) e.getValue();
  103. Annotation n = impl.getAnnotation(Export.class);
  104. if (n == null) {
  105. n = impl.getAnnotation(javax.inject.Named.class);
  106. }
  107. if (n == null) {
  108. n = impl.getAnnotation(com.google.inject.name.Named.class);
  109. }
  110. if (n == null) {
  111. n = UniqueAnnotations.create();
  112. }
  113. bind(type).annotatedWith(n).to(impl);
  114. }
  115. }
  116. };
  117. }
  118. private void scan() throws InvalidPluginException {
  119. Enumeration<JarEntry> e = jarFile.entries();
  120. while (e.hasMoreElements()) {
  121. JarEntry entry = e.nextElement();
  122. if (skip(entry)) {
  123. continue;
  124. }
  125. ClassData def = new ClassData();
  126. try {
  127. new ClassReader(read(entry)).accept(def, SKIP_ALL);
  128. } catch (IOException err) {
  129. throw new InvalidPluginException("Cannot auto-register", err);
  130. } catch (RuntimeException err) {
  131. PluginLoader.log.warn(String.format(
  132. "Plugin %s has invaild class file %s inside of %s",
  133. pluginName, entry.getName(), jarFile.getName()), err);
  134. continue;
  135. }
  136. if (def.exportedAsName != null) {
  137. if (def.isConcrete()) {
  138. export(def);
  139. } else {
  140. PluginLoader.log.warn(String.format(
  141. "Plugin %s tries to @Export(\"%s\") abstract class %s",
  142. pluginName, def.exportedAsName, def.className));
  143. }
  144. } else if (def.listen) {
  145. if (def.isConcrete()) {
  146. listen(def);
  147. } else {
  148. PluginLoader.log.warn(String.format(
  149. "Plugin %s tries to @Listen abstract class %s",
  150. pluginName, def.className));
  151. }
  152. }
  153. }
  154. }
  155. private void export(ClassData def) throws InvalidPluginException {
  156. Class<?> clazz;
  157. try {
  158. clazz = Class.forName(def.className, false, classLoader);
  159. } catch (ClassNotFoundException err) {
  160. throw new InvalidPluginException(String.format(
  161. "Cannot load %s with @Export(\"%s\")",
  162. def.className, def.exportedAsName), err);
  163. }
  164. Export export = clazz.getAnnotation(Export.class);
  165. if (export == null) {
  166. PluginLoader.log.warn(String.format(
  167. "In plugin %s asm incorrectly parsed %s with @Export(\"%s\")",
  168. pluginName, clazz.getName(), def.exportedAsName));
  169. return;
  170. }
  171. if (is("org.apache.sshd.server.Command", clazz)) {
  172. if (sshGen != null) {
  173. sshGen.export(export, clazz);
  174. }
  175. } else if (is("javax.servlet.http.HttpServlet", clazz)) {
  176. if (httpGen != null) {
  177. httpGen.export(export, clazz);
  178. listen(clazz, clazz);
  179. }
  180. } else {
  181. int cnt = sysListen.size();
  182. listen(clazz, clazz);
  183. if (cnt == sysListen.size()) {
  184. // If no bindings were recorded, the extension isn't recognized.
  185. throw new InvalidPluginException(String.format(
  186. "Class %s with @Export(\"%s\") not supported",
  187. clazz.getName(), export.value()));
  188. }
  189. }
  190. }
  191. private void listen(ClassData def) throws InvalidPluginException {
  192. Class<?> clazz;
  193. try {
  194. clazz = Class.forName(def.className, false, classLoader);
  195. } catch (ClassNotFoundException err) {
  196. throw new InvalidPluginException(String.format(
  197. "Cannot load %s with @Listen",
  198. def.className), err);
  199. }
  200. Listen listen = clazz.getAnnotation(Listen.class);
  201. if (listen != null) {
  202. listen(clazz, clazz);
  203. } else {
  204. PluginLoader.log.warn(String.format(
  205. "In plugin %s asm incorrectly parsed %s with @Listen",
  206. pluginName, clazz.getName()));
  207. }
  208. }
  209. private void listen(java.lang.reflect.Type type, Class<?> clazz)
  210. throws InvalidPluginException {
  211. while (type != null) {
  212. Class<?> rawType;
  213. if (type instanceof ParameterizedType) {
  214. rawType = (Class<?>) ((ParameterizedType) type).getRawType();
  215. } else if (type instanceof Class) {
  216. rawType = (Class<?>) type;
  217. } else {
  218. return;
  219. }
  220. if (rawType.getAnnotation(ExtensionPoint.class) != null) {
  221. TypeLiteral<?> tl = TypeLiteral.get(type);
  222. if (env.hasDynamicSet(tl)) {
  223. sysSingletons.add(clazz);
  224. sysListen.put(tl, clazz);
  225. } else if (env.hasDynamicMap(tl)) {
  226. if (clazz.getAnnotation(Export.class) == null) {
  227. throw new InvalidPluginException(String.format(
  228. "Class %s requires @Export(\"name\") annotation for %s",
  229. clazz.getName(), rawType.getName()));
  230. }
  231. sysSingletons.add(clazz);
  232. sysListen.put(tl, clazz);
  233. } else {
  234. throw new InvalidPluginException(String.format(
  235. "Cannot register %s, server does not accept %s",
  236. clazz.getName(), rawType.getName()));
  237. }
  238. return;
  239. }
  240. java.lang.reflect.Type[] interfaces = rawType.getGenericInterfaces();
  241. if (interfaces != null) {
  242. for (java.lang.reflect.Type i : interfaces) {
  243. listen(i, clazz);
  244. }
  245. }
  246. type = rawType.getGenericSuperclass();
  247. }
  248. }
  249. private static boolean skip(JarEntry entry) {
  250. if (!entry.getName().endsWith(".class")) {
  251. return true; // Avoid non-class resources.
  252. }
  253. if (entry.getSize() <= 0) {
  254. return true; // Directories have 0 size.
  255. }
  256. if (entry.getSize() >= 1024 * 1024) {
  257. return true; // Do not scan huge class files.
  258. }
  259. return false;
  260. }
  261. private byte[] read(JarEntry entry) throws IOException {
  262. byte[] data = new byte[(int) entry.getSize()];
  263. InputStream in = jarFile.getInputStream(entry);
  264. try {
  265. IO.readFully(in, data, 0, data.length);
  266. } finally {
  267. in.close();
  268. }
  269. return data;
  270. }
  271. private static class ClassData implements ClassVisitor {
  272. private static final String EXPORT = Type.getType(Export.class).getDescriptor();
  273. private static final String LISTEN = Type.getType(Listen.class).getDescriptor();
  274. String className;
  275. int access;
  276. String exportedAsName;
  277. boolean listen;
  278. boolean isConcrete() {
  279. return (access & Opcodes.ACC_ABSTRACT) == 0
  280. && (access & Opcodes.ACC_INTERFACE) == 0;
  281. }
  282. @Override
  283. public void visit(int version, int access, String name, String signature,
  284. String superName, String[] interfaces) {
  285. this.className = Type.getObjectType(name).getClassName();
  286. this.access = access;
  287. }
  288. @Override
  289. public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
  290. if (visible && EXPORT.equals(desc)) {
  291. return new AbstractAnnotationVisitor() {
  292. @Override
  293. public void visit(String name, Object value) {
  294. exportedAsName = (String) value;
  295. }
  296. };
  297. }
  298. if (visible && LISTEN.equals(desc)) {
  299. listen = true;
  300. return null;
  301. }
  302. return null;
  303. }
  304. @Override
  305. public void visitSource(String arg0, String arg1) {
  306. }
  307. @Override
  308. public void visitOuterClass(String arg0, String arg1, String arg2) {
  309. }
  310. @Override
  311. public MethodVisitor visitMethod(int arg0, String arg1, String arg2,
  312. String arg3, String[] arg4) {
  313. return null;
  314. }
  315. @Override
  316. public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
  317. }
  318. @Override
  319. public FieldVisitor visitField(int arg0, String arg1, String arg2,
  320. String arg3, Object arg4) {
  321. return null;
  322. }
  323. @Override
  324. public void visitEnd() {
  325. }
  326. @Override
  327. public void visitAttribute(Attribute arg0) {
  328. }
  329. }
  330. private static abstract class AbstractAnnotationVisitor implements
  331. AnnotationVisitor {
  332. @Override
  333. public AnnotationVisitor visitAnnotation(String arg0, String arg1) {
  334. return null;
  335. }
  336. @Override
  337. public AnnotationVisitor visitArray(String arg0) {
  338. return null;
  339. }
  340. @Override
  341. public void visitEnum(String arg0, String arg1, String arg2) {
  342. }
  343. @Override
  344. public void visitEnd() {
  345. }
  346. }
  347. }