/*******************************************************************************
*
* 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());
}