/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.model;
import hudson.ExtensionList;
import hudson.Util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.ModelObjectWithContextMenu;
import jenkins.model.TransientActionFactory;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
/**
* {@link ModelObject} that can have additional {@link Action}s.
*
* @author Kohsuke Kawaguchi
*/
@ExportedBean
public abstract class Actionable extends AbstractModelObject implements ModelObjectWithContextMenu {
/**
* Actions contributed to this model object.
*
* Typed more strongly than it should to improve the serialization signature.
*/
private volatile CopyOnWriteArrayList<Action> actions;
/**
* Gets actions contributed to this object.
*
* <p>
* A new {@link Action} can be added by {@link #addAction}.
*
* <p>If you are <em>reading</em> the list, rather than <em>modifying</em> it,
* use {@link #getAllActions} instead.
* This method by default returns only <em>persistent</em> actions
* (though some subclasses override it to return an extended unmodifiable list).
*
* @return
* may be empty but never null.
* @deprecated Normally outside code should not call this method any more.
* Use {@link #getAllActions}, or {@link #addAction}, or {@link #replaceAction}.
* May still be called for compatibility reasons from subclasses predating {@link TransientActionFactory}.
*/
@Deprecated
public List<Action> getActions() {
if(actions == null) {
synchronized (this) {
if(actions == null) {
actions = new CopyOnWriteArrayList<Action>();
}
}
}
return actions;
}
/**
* Gets all actions, transient or persistent.
* {@link #getActions} is supplemented with anything contributed by {@link TransientActionFactory}.
* @return an unmodifiable, possible empty list
* @since 1.548
*/
@Exported(name="actions")
public final List<? extends Action> getAllActions() {
List<Action> _actions = new ArrayList<Action>(getActions());
for (TransientActionFactory<?> taf : ExtensionList.lookup(TransientActionFactory.class)) {
if (taf.type().isInstance(this)) {
try {
_actions.addAll(createFor(taf));
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Could not load actions from " + taf + " for " + this, e);
}
}
}
return Collections.unmodifiableList(_actions);
}
private <T> Collection<? extends Action> createFor(TransientActionFactory<T> taf) {
return taf.createFor(taf.type().cast(this));
}
/**
* Gets all actions of a specified type that contributed to this object.
*
* @param type The type of action to return.
* @return
* may be empty but never null.
* @see #getAction(Class)
*/
public <T extends Action> List<T> getActions(Class<T> type) {
return Util.filter(getAllActions(), type);
}
/**
* Adds a new action.
*
* The default implementation calls {@code getActions().add(a)}.
*/
public void addAction(Action a) {
if(a==null) throw new IllegalArgumentException();
getActions().add(a);
}
/**
* Add an action, replacing any existing action of the (exact) same class.
* @param a an action to add/replace
* @since 1.548
*/
public void replaceAction(Action a) {
// CopyOnWriteArrayList does not support Iterator.remove, so need to do it this way:
List<Action> old = new ArrayList<Action>(1);
List<Action> current = getActions();
for (Action a2 : current) {
if (a2.getClass() == a.getClass()) {
old.add(a2);
}
}
current.removeAll(old);
addAction(a);
}
/** @deprecated No clear purpose, since subclasses may have overridden {@link #getActions}, and does not consider {@link TransientActionFactory}. */
@Deprecated
public Action getAction(int index) {
if(actions==null) return null;
return actions.get(index);
}
/**
* Gets the action (first instance to be found) of a specified type that contributed to this build.
*
* @param type
* @return The action or <code>null</code> if no such actions exist.
* @see #getActions(Class)
*/
public <T extends Action> T getAction(Class<T> type) {
for (Action a : getAllActions())
if (type.isInstance(a))
return type.cast(a);
return null;
}
public Object getDynamic(String token, StaplerRequest req, StaplerResponse rsp) {
for (Action a : getAllActions()) {
if(a==null)
continue; // be defensive
String urlName = a.getUrlName();
if(urlName==null)
continue;
if(urlName.equals(token))
return a;
}
return null;
}
@Override public ContextMenu doContextMenu(StaplerRequest request, StaplerResponse response) throws Exception {
return new ContextMenu().from(this,request,response);
}
private static final Logger LOGGER = Logger.getLogger(Actionable.class.getName());
}