PageRenderTime 73ms CodeModel.GetById 44ms RepoModel.GetById 0ms app.codeStats 0ms

/core/src/main/java/hudson/ExtensionFinder.java

https://github.com/justinedelson/jenkins
Java | 454 lines | 256 code | 42 blank | 156 comment | 21 complexity | 19a4880df73e52290f792f89393a9e5c MD5 | raw file
  1. /*
  2. * The MIT License
  3. *
  4. * Copyright (c) 2004-2010, Sun Microsystems, Inc., InfraDNA, Inc., CloudBees, Inc.
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. */
  24. package hudson;
  25. import com.google.inject.AbstractModule;
  26. import com.google.inject.Binding;
  27. import com.google.inject.CreationException;
  28. import com.google.inject.Guice;
  29. import com.google.inject.Injector;
  30. import com.google.inject.Key;
  31. import com.google.inject.Module;
  32. import com.google.inject.Provider;
  33. import com.google.inject.Scope;
  34. import com.google.inject.Scopes;
  35. import com.google.inject.name.Names;
  36. import com.google.common.collect.ImmutableList;
  37. import hudson.init.InitMilestone;
  38. import hudson.model.Descriptor;
  39. import hudson.model.Hudson;
  40. import jenkins.model.Jenkins;
  41. import net.java.sezpoz.Index;
  42. import net.java.sezpoz.IndexItem;
  43. import org.kohsuke.accmod.Restricted;
  44. import org.kohsuke.accmod.restrictions.NoExternalUse;
  45. import java.lang.annotation.Annotation;
  46. import java.util.Collections;
  47. import java.util.HashMap;
  48. import java.util.Map;
  49. import java.util.Map.Entry;
  50. import java.util.logging.Logger;
  51. import java.util.logging.Level;
  52. import java.util.List;
  53. import java.util.ArrayList;
  54. import java.util.Collection;
  55. import java.lang.reflect.AnnotatedElement;
  56. import java.lang.reflect.Field;
  57. import java.lang.reflect.Method;
  58. /**
  59. * Discovers the implementations of an extension point.
  60. *
  61. * <p>
  62. * This extension point allows you to write your implementations of {@link ExtensionPoint}s
  63. * in arbitrary DI containers, and have Hudson discover them.
  64. *
  65. * <p>
  66. * {@link ExtensionFinder} itself is an extension point, but to avoid infinite recursion,
  67. * Hudson discovers {@link ExtensionFinder}s through {@link Sezpoz} and that alone.
  68. *
  69. * @author Kohsuke Kawaguchi
  70. * @since 1.286
  71. */
  72. public abstract class ExtensionFinder implements ExtensionPoint {
  73. /**
  74. * @deprecated as of 1.356
  75. * Use and implement {@link #find(Class,Hudson)} that allows us to put some metadata.
  76. */
  77. @Restricted(NoExternalUse.class)
  78. public <T> Collection<T> findExtensions(Class<T> type, Hudson hudson) {
  79. return Collections.emptyList();
  80. }
  81. /**
  82. * Discover extensions of the given type.
  83. *
  84. * <p>
  85. * This method is called only once per the given type after all the plugins are loaded,
  86. * so implementations need not worry about caching.
  87. *
  88. * @param <T>
  89. * The type of the extension points. This is not bound to {@link ExtensionPoint} because
  90. * of {@link Descriptor}, which by itself doesn't implement {@link ExtensionPoint} for
  91. * a historical reason.
  92. * @param hudson
  93. * Hudson whose behalf this extension finder is performing lookup.
  94. * @return
  95. * Can be empty but never null.
  96. * @since 1.356
  97. * Older implementations provide {@link #findExtensions(Class,Hudson)}
  98. */
  99. public abstract <T> Collection<ExtensionComponent<T>> find(Class<T> type, Hudson hudson);
  100. /**
  101. * A pointless function to work around what appears to be a HotSpot problem. See JENKINS-5756 and bug 6933067
  102. * on BugParade for more details.
  103. */
  104. public <T> Collection<ExtensionComponent<T>> _find(Class<T> type, Hudson hudson) {
  105. return find(type,hudson);
  106. }
  107. /**
  108. * Performs class initializations without creating instances.
  109. *
  110. * If two threads try to initialize classes in the opposite order, a dead lock will ensue,
  111. * and we can get into a similar situation with {@link ExtensionFinder}s.
  112. *
  113. * <p>
  114. * That is, one thread can try to list extensions, which results in {@link ExtensionFinder}
  115. * loading and initializing classes. This happens inside a context of a lock, so that
  116. * another thread that tries to list the same extensions don't end up creating different
  117. * extension instances. So this activity locks extension list first, then class initialization next.
  118. *
  119. * <p>
  120. * In the mean time, another thread can load and initialize a class, and that initialization
  121. * can eventually results in listing up extensions, for example through static initializer.
  122. * Such activity locks class initialization first, then locks extension list.
  123. *
  124. * <p>
  125. * This inconsistent locking order results in a dead lock, you see.
  126. *
  127. * <p>
  128. * So to reduce the likelihood, this method is called in prior to {@link #find(Class,Hudson)} invocation,
  129. * but from outside the lock. The implementation is expected to perform all the class initialization activities
  130. * from here.
  131. *
  132. * <p>
  133. * See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6459208 for how to force a class initialization.
  134. * Also see http://kohsuke.org/2010/09/01/deadlock-that-you-cant-avoid/ for how class initialization
  135. * can results in a dead lock.
  136. */
  137. public void scout(Class extensionType, Hudson hudson) {
  138. }
  139. @Extension
  140. public static final class GuiceFinder extends AbstractGuiceFinder<Extension> {
  141. public GuiceFinder() {
  142. super(Extension.class);
  143. }
  144. @Override
  145. protected boolean isOptional(Extension annotation) {
  146. return annotation.optional();
  147. }
  148. @Override
  149. protected double getOrdinal(Extension annotation) {
  150. return annotation.ordinal();
  151. }
  152. }
  153. /**
  154. * Discovers components via sezpoz but instantiates them by using Guice.
  155. */
  156. public static abstract class AbstractGuiceFinder<T extends Annotation> extends ExtensionFinder {
  157. private Injector container;
  158. private final Map<Key,T> annotations = new HashMap<Key,T>();
  159. public AbstractGuiceFinder(final Class<T> annotationType) {
  160. List<Module> modules = new ArrayList<Module>();
  161. modules.add(new SezpozModule(annotationType,Jenkins.getInstance().getPluginManager().uberClassLoader));
  162. for (ExtensionComponent<Module> ec : new Sezpoz().find(Module.class, Hudson.getInstance())) {
  163. modules.add(ec.getInstance());
  164. }
  165. try {
  166. container = Guice.createInjector(modules);
  167. } catch (CreationException e) {
  168. LOGGER.log(Level.SEVERE, "Failed to create Guice container from all the plugins",e);
  169. // failing to load all bindings are disastrous, so recover by creating minimum that works
  170. // by just including the core
  171. container = Guice.createInjector(new SezpozModule(annotationType,Jenkins.class.getClassLoader()));
  172. }
  173. }
  174. protected abstract double getOrdinal(T annotation);
  175. /**
  176. * Hook to enable subtypes to control which ones to pick up and which ones to ignore.
  177. */
  178. protected boolean isActive(AnnotatedElement e) {
  179. return true;
  180. }
  181. protected abstract boolean isOptional(T annotation);
  182. private Object instantiate(IndexItem<T,Object> item) {
  183. try {
  184. return item.instance();
  185. } catch (LinkageError e) {
  186. // sometimes the instantiation fails in an indirect classloading failure,
  187. // which results in a LinkageError
  188. LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING,
  189. "Failed to load "+item.className(), e);
  190. } catch (InstantiationException e) {
  191. LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING,
  192. "Failed to load "+item.className(), e);
  193. }
  194. return null;
  195. }
  196. public <U> Collection<ExtensionComponent<U>> find(Class<U> type, Hudson hudson) {
  197. List<ExtensionComponent<U>> result = new ArrayList<ExtensionComponent<U>>();
  198. for (Entry<Key<?>, Binding<?>> e : container.getBindings().entrySet()) {
  199. if (type.isAssignableFrom(e.getKey().getTypeLiteral().getRawType())) {
  200. T a = annotations.get(e.getKey());
  201. Object o = e.getValue().getProvider().get();
  202. if (o!=null)
  203. result.add(new ExtensionComponent<U>(type.cast(o),a!=null?getOrdinal(a):0));
  204. }
  205. }
  206. return result;
  207. }
  208. /**
  209. * TODO: need to learn more about concurrent access to {@link Injector} and how it interacts
  210. * with classloading.
  211. */
  212. @Override
  213. public void scout(Class extensionType, Hudson hudson) {
  214. }
  215. /**
  216. * {@link Scope} that allows a failure to create a component,
  217. * and change the value to null.
  218. *
  219. * <p>
  220. * This is necessary as a failure to load one plugin shouldn't fail the startup of the entire Jenkins.
  221. * Instead, we should just drop the failing plugins.
  222. */
  223. public static final Scope FAULT_TOLERANT_SCOPE = new Scope() {
  224. public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
  225. final Provider<T> base = Scopes.SINGLETON.scope(key,unscoped);
  226. return new Provider<T>() {
  227. public T get() {
  228. try {
  229. return base.get();
  230. } catch (Exception e) {
  231. LOGGER.log(Level.WARNING,"Failed to instantiate",e);
  232. return null;
  233. }
  234. }
  235. };
  236. }
  237. };
  238. private static final Logger LOGGER = Logger.getLogger(GuiceFinder.class.getName());
  239. private class SezpozModule extends AbstractModule {
  240. private final Class<T> annotationType;
  241. private final ClassLoader cl;
  242. public SezpozModule(Class<T> annotationType, ClassLoader cl) {
  243. this.annotationType = annotationType;
  244. this.cl = cl;
  245. }
  246. /**
  247. * Guice performs various reflection operations on the class to figure out the dependency graph,
  248. * and that process can cause additional classloading problems, which will fail the injector creation,
  249. * which in turn has disastrous effect on the startup.
  250. *
  251. * <p>
  252. * Ultimately I'd like to isolate problems to plugins and selectively disable them, allowing
  253. * Jenkins to start with plugins that work, but I haven't figured out how.
  254. *
  255. * So this is an attempt to detect subset of problems eagerly, by invoking various reflection
  256. * operations and try to find non-existent classes early.
  257. */
  258. private void resolve(Class c) {
  259. try {
  260. c.getGenericSuperclass();
  261. c.getGenericInterfaces();
  262. ClassLoader ecl = c.getClassLoader();
  263. Method m = ClassLoader.class.getDeclaredMethod("resolveClass", Class.class);
  264. m.setAccessible(true);
  265. m.invoke(ecl, c);
  266. } catch (Exception x) {
  267. throw (LinkageError)new LinkageError("Failed to resolve "+c).initCause(x);
  268. }
  269. }
  270. @SuppressWarnings({"unchecked", "ChainOfInstanceofChecks"})
  271. @Override
  272. protected void configure() {
  273. int id=0;
  274. for (final IndexItem<T,Object> item : Index.load(annotationType, Object.class, cl)) {
  275. id++;
  276. try {
  277. AnnotatedElement e = item.element();
  278. if (!isActive(e)) continue;
  279. T a = item.annotation();
  280. if (e instanceof Class) {
  281. Key key = Key.get((Class)e);
  282. resolve((Class)e);
  283. annotations.put(key,a);
  284. bind(key).in(FAULT_TOLERANT_SCOPE);
  285. } else {
  286. Class extType;
  287. if (e instanceof Field) {
  288. extType = ((Field)e).getType();
  289. } else
  290. if (e instanceof Method) {
  291. extType = ((Method)e).getReturnType();
  292. } else
  293. throw new AssertionError();
  294. resolve(extType);
  295. // use arbitrary id to make unique key, because Guice wants that.
  296. Key key = Key.get(extType, Names.named(String.valueOf(id)));
  297. annotations.put(key,a);
  298. bind(key).toProvider(new Provider() {
  299. public Object get() {
  300. return instantiate(item);
  301. }
  302. }).in(FAULT_TOLERANT_SCOPE);
  303. }
  304. } catch (LinkageError e) {
  305. // sometimes the instantiation fails in an indirect classloading failure,
  306. // which results in a LinkageError
  307. LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING,
  308. "Failed to load "+item.className(), e);
  309. } catch (InstantiationException e) {
  310. LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING,
  311. "Failed to load "+item.className(), e);
  312. }
  313. }
  314. }
  315. }
  316. }
  317. /**
  318. * The bootstrap implementation that looks for the {@link Extension} marker.
  319. *
  320. * <p>
  321. * Uses Sezpoz as the underlying mechanism.
  322. */
  323. public static final class Sezpoz extends ExtensionFinder {
  324. private volatile List<IndexItem<Extension,Object>> indices;
  325. /**
  326. * Loads indices (ideally once but as few times as possible), then reuse them later.
  327. * {@link ExtensionList#ensureLoaded()} guarantees that this method won't be called until
  328. * {@link InitMilestone#PLUGINS_PREPARED} is attained, so this method is guaranteed to
  329. * see all the classes and indices.
  330. */
  331. private List<IndexItem<Extension,Object>> getIndices() {
  332. // this method cannot be synchronized because of a dead lock possibility in the following order of events:
  333. // 1. thread X can start listing indices, locking this object 'SZ'
  334. // 2. thread Y starts loading a class, locking a classloader 'CL'
  335. // 3. thread X needs to load a class, now blocked on CL
  336. // 4. thread Y decides to load extensions, now blocked on SZ.
  337. // 5. dead lock
  338. if (indices==null) {
  339. ClassLoader cl = Jenkins.getInstance().getPluginManager().uberClassLoader;
  340. indices = ImmutableList.copyOf(Index.load(Extension.class, Object.class, cl));
  341. }
  342. return indices;
  343. }
  344. public <T> Collection<ExtensionComponent<T>> find(Class<T> type, Hudson hudson) {
  345. List<ExtensionComponent<T>> result = new ArrayList<ExtensionComponent<T>>();
  346. for (IndexItem<Extension,Object> item : getIndices()) {
  347. try {
  348. AnnotatedElement e = item.element();
  349. Class<?> extType;
  350. if (e instanceof Class) {
  351. extType = (Class) e;
  352. } else
  353. if (e instanceof Field) {
  354. extType = ((Field)e).getType();
  355. } else
  356. if (e instanceof Method) {
  357. extType = ((Method)e).getReturnType();
  358. } else
  359. throw new AssertionError();
  360. if(type.isAssignableFrom(extType)) {
  361. Object instance = item.instance();
  362. if(instance!=null)
  363. result.add(new ExtensionComponent<T>(type.cast(instance),item.annotation()));
  364. }
  365. } catch (LinkageError e) {
  366. // sometimes the instantiation fails in an indirect classloading failure,
  367. // which results in a LinkageError
  368. LOGGER.log(logLevel(item), "Failed to load "+item.className(), e);
  369. } catch (InstantiationException e) {
  370. LOGGER.log(logLevel(item), "Failed to load "+item.className(), e);
  371. }
  372. }
  373. return result;
  374. }
  375. @Override
  376. public void scout(Class extensionType, Hudson hudson) {
  377. for (IndexItem<Extension,Object> item : getIndices()) {
  378. try {
  379. // we might end up having multiple threads concurrently calling into element(),
  380. // but we can't synchronize this --- if we do, the one thread that's supposed to load a class
  381. // can block while other threads wait for the entry into the element call().
  382. // looking at the sezpoz code, it should be safe to do so
  383. AnnotatedElement e = item.element();
  384. Class<?> extType;
  385. if (e instanceof Class) {
  386. extType = (Class) e;
  387. } else
  388. if (e instanceof Field) {
  389. extType = ((Field)e).getType();
  390. } else
  391. if (e instanceof Method) {
  392. extType = ((Method)e).getReturnType();
  393. } else
  394. throw new AssertionError();
  395. // according to http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6459208
  396. // this appears to be the only way to force a class initialization
  397. Class.forName(extType.getName(),true,extType.getClassLoader());
  398. } catch (InstantiationException e) {
  399. LOGGER.log(logLevel(item), "Failed to scout "+item.className(), e);
  400. } catch (ClassNotFoundException e) {
  401. LOGGER.log(logLevel(item), "Failed to scout "+item.className(), e);
  402. } catch (LinkageError e) {
  403. LOGGER.log(logLevel(item), "Failed to scout "+item.className(), e);
  404. }
  405. }
  406. }
  407. private Level logLevel(IndexItem<Extension, Object> item) {
  408. return item.annotation().optional() ? Level.FINE : Level.WARNING;
  409. }
  410. }
  411. private static final Logger LOGGER = Logger.getLogger(ExtensionFinder.class.getName());
  412. }