/******************************************************************************* * * Copyright (c) 2004-2009, Oracle Corporation * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * * * *******************************************************************************/ package hudson; import hudson.init.InitMilestone; import hudson.model.Hudson; import hudson.util.AdaptedIterator; import hudson.util.DescriptorList; import hudson.util.Memoizer; import hudson.util.Iterators; import hudson.ExtensionPoint.LegacyInstancesAreScopedToHudson; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Vector; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; import java.util.logging.Logger; /** * Retains the known extension instances for the given type 'T'. * * <p> Extensions are loaded lazily on demand and automatically by using * {@link ExtensionFinder}, but this class also provides a mechanism to provide * compatibility with the older {@link DescriptorList}-based manual * registration, * * <p> All {@link ExtensionList} instances should be owned by {@link Hudson}, * even though extension points can be defined by anyone on any type. Use * {@link Hudson#getExtensionList(Class)} and * {@link Hudson#getDescriptorList(Class)} to obtain the instances. * * @param <T> Type of the extension point. This class holds instances of the * subtypes of 'T'. * * @author Kohsuke Kawaguchi * @since 1.286 * @see Hudson#getExtensionList(Class) * @see Hudson#getDescriptorList(Class) */ public class ExtensionList<T> extends AbstractList<T> { //TODO: review and check whether we can do it private public final Hudson hudson; public final Class<T> extensionType; /** * Once discovered, extensions are retained here. */ @CopyOnWrite private volatile List<ExtensionComponent<T>> extensions; /** * Place to store manually registered instances with the per-Hudson scope. * {@link CopyOnWriteArrayList} is used here to support concurrent * iterations and mutation. */ private final CopyOnWriteArrayList<ExtensionComponent<T>> legacyInstances; protected ExtensionList(Hudson hudson, Class<T> extensionType) { this(hudson, extensionType, new CopyOnWriteArrayList<ExtensionComponent<T>>()); } /** * * @param legacyStore Place to store manually registered instances. The * version of the constructor that omits this uses a new {@link Vector}, * making the storage lifespan tied to the life of {@link ExtensionList}. If * the manually registered instances are scoped to VM level, the caller * should pass in a static list. */ protected ExtensionList(Hudson hudson, Class<T> extensionType, CopyOnWriteArrayList<ExtensionComponent<T>> legacyStore) { this.hudson = hudson; this.extensionType = extensionType; this.legacyInstances = legacyStore; } /** * Looks for the extension instance of the given type (subclasses excluded), * or return null. */ public <U extends T> U get(Class<U> type) { for (T ext : this) { if (ext.getClass() == type) { return type.cast(ext); } } return null; } @Override public Iterator<T> iterator() { // we need to intercept mutation, so for now don't allow Iterator.remove return new AdaptedIterator<ExtensionComponent<T>, T>(Iterators.readOnly(ensureLoaded().iterator())) { protected T adapt(ExtensionComponent<T> item) { return item.getInstance(); } }; } /** * Gets the same thing as the 'this' list represents, except as * {@link ExtensionComponent}s. */ public List<ExtensionComponent<T>> getComponents() { return Collections.unmodifiableList(ensureLoaded()); } public T get(int index) { return ensureLoaded().get(index).getInstance(); } public int size() { return ensureLoaded().size(); } @Override public synchronized boolean remove(Object o) { removeComponent(legacyInstances, o); if (extensions != null) { List<ExtensionComponent<T>> r = new ArrayList<ExtensionComponent<T>>(extensions); removeComponent(r, o); extensions = sort(r); } return true; } private <T> void removeComponent(Collection<ExtensionComponent<T>> collection, Object t) { for (Iterator<ExtensionComponent<T>> itr = collection.iterator(); itr.hasNext();) { ExtensionComponent<T> c = itr.next(); if (c.getInstance().equals(t)) { collection.remove(c); return; } } } @Override public synchronized T remove(int index) { T t = get(index); remove(t); return t; } /** * Write access will put the instance into a legacy store. * * @deprecated since 2009-02-23. Prefer automatic registration. */ @Override public synchronized boolean add(T t) { legacyInstances.add(new ExtensionComponent<T>(t)); // if we've already filled extensions, add it if (extensions != null) { List<ExtensionComponent<T>> r = new ArrayList<ExtensionComponent<T>>(extensions); r.add(new ExtensionComponent<T>(t)); extensions = sort(r); } return true; } @Override public void add(int index, T element) { add(element); } /** * Used to bind extension to URLs by their class names. * * @since 1.349 */ public T getDynamic(String className) { for (T t : this) { if (t.getClass().getName().equals(className)) { return t; } } return null; } public Hudson getHudson() { return hudson; } public Class<T> getExtensionType() { return extensionType; } private List<ExtensionComponent<T>> ensureLoaded() { if (extensions != null) { return extensions; // already loaded } if (Hudson.getInstance().getInitLevel().compareTo(InitMilestone.PLUGINS_PREPARED) < 0) { return legacyInstances; // can't perform the auto discovery until all plugins are loaded, so just make the legacy instances visible } synchronized (getLoadLock()) { if (extensions == null) { List<ExtensionComponent<T>> r = load(); r.addAll(legacyInstances); extensions = sort(r); } return extensions; } } /** * Chooses the object that locks the loading of the extension instances. */ protected Object getLoadLock() { return hudson.lookup.setIfNull(Lock.class, new Lock()); } /** * Loading an {@link ExtensionList} can result in a nested loading of * another {@link ExtensionList}. What that means is that we need a single * lock that spans across all the {@link ExtensionList}s, or else we can end * up in a dead lock. */ private static final class Lock { } /** * Loads all the extensions. */ protected List<ExtensionComponent<T>> load() { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Loading ExtensionList: " + extensionType); } return hudson.getPluginManager().getPluginStrategy().findComponents(extensionType, hudson); } /** * If the {@link ExtensionList} implementation requires sorting extensions, * override this method to do so. * * <p> The implementation should copy a list, do a sort, and return the new * instance. */ protected List<ExtensionComponent<T>> sort(List<ExtensionComponent<T>> r) { r = new ArrayList<ExtensionComponent<T>>(r); Collections.sort(r); return r; } public static <T> ExtensionList<T> create(Hudson hudson, Class<T> type) { if (type.getAnnotation(LegacyInstancesAreScopedToHudson.class) != null) { return new ExtensionList<T>(hudson, type); } else { return new ExtensionList<T>(hudson, type, staticLegacyInstances.get(type)); } } /** * Places to store static-scope legacy instances. */ private static final Memoizer<Class, CopyOnWriteArrayList> staticLegacyInstances = new Memoizer<Class, CopyOnWriteArrayList>() { public CopyOnWriteArrayList compute(Class key) { return new CopyOnWriteArrayList(); } }; /** * Exposed for the test harness to clear all legacy extension instances. */ public static void clearLegacyInstances() { staticLegacyInstances.clear(); } private static final Logger LOGGER = Logger.getLogger(ExtensionList.class.getName()); }