Package org.jenkinsci.plugins.workflow.graph

Source Code of org.jenkinsci.plugins.workflow.graph.FlowNode

/*
* The MIT License
*
* Copyright (c) 2013-2014, CloudBees, Inc.
*
* 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 org.jenkinsci.plugins.workflow.graph;

import com.google.common.collect.ImmutableList;
import hudson.model.Action;
import hudson.model.Actionable;
import hudson.model.BallColor;
import hudson.model.Saveable;
import hudson.search.SearchItem;
import java.io.IOException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import static java.util.logging.Level.*;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.jenkinsci.plugins.workflow.actions.ErrorAction;
import org.jenkinsci.plugins.workflow.actions.LabelAction;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

/**
* One node in a flow graph.
*/
@ExportedBean
public abstract class FlowNode extends Actionable implements Saveable {
    private final List<FlowNode> parents;

    private final String id;

    // this is a copy-on-write array so synchronization isn't needed between reader & writer.
    @edu.umd.cs.findbugs.annotations.SuppressWarnings("IS2_INCONSISTENT_SYNC")
    private transient CopyOnWriteArrayList<Action> actions;

    private transient final FlowExecution exec;

    protected FlowNode(FlowExecution exec, String id, List<FlowNode> parents) {
        this.id = id;
        this.exec = exec;
        this.parents = ImmutableList.copyOf(parents);
    }

    protected FlowNode(FlowExecution exec, String id, FlowNode... parents) {
        this.id = id;
        this.exec = exec;
        this.parents = ImmutableList.copyOf(parents);
    }

    /**
     * Transient flag that indicates if this node is currently actively executing something.
     * <p>It will be false for a node which still has active children, like a step with a running body.
     * It will also be false for something that has finished but is pending child node creation,
     * such as a completed fork branch which is waiting for the join node to be created.
     * <p>This can only go from true to false and is a shortcut for {@link FlowExecution#isCurrentHead}.
     */
    @Exported
    public final boolean isRunning() {
        return getExecution().isCurrentHead(this);
    }

    /**
     * If this node has terminated with an error, return an object that indicates that.
     * This is just a convenience method.
     */
    public final @CheckForNull ErrorAction getError() {
        return getAction(ErrorAction.class);
    }

    public @Nonnull FlowExecution getExecution() {
        return exec;
    }

    /**
     * Returns a read-only view of parents.
     */
    public List<FlowNode> getParents() {
        return parents;
    }

    @Restricted(DoNotUse.class)
    @Exported(name="parents")
    public List<String> getParentIds() {
        List<String> ids = new ArrayList<String>(2);
        for (FlowNode parent : getParents()) {
            ids.add(parent.getId());
        }
        return ids;
    }

    /**
     * Has to be unique within a {@link FlowExecution}.
     *
     * Needs to remain stable across serialization and JVM restarts.
     *
     * @see FlowExecution#getNode(String)
     */
    @Exported
    public String getId() {
        return id;
    }

    /**
     * Reference from the parent {@link SearchItem} is through {@link FlowExecution#getNode(String)}
     */
    public final String getSearchUrl() {
        return getId();
    }

    @Exported
    public String getDisplayName() {
        LabelAction a = getAction(LabelAction.class);
        if (a!=null)    return a.getDisplayName();
        else            return getTypeDisplayName();
    }

    /**
     * Returns colored orb that represents the current state of this node.
     *
     * TODO: this makes me wonder if we should support other colored states,
     * like unstable and aborted --- seems useful.
     */
    @Exported
    public BallColor getIconColor() {
        BallColor c = getError()!=null ? BallColor.RED : BallColor.BLUE;
        // TODO this should probably also be _anime in case this is a step node with a body and the body is still running (try FlowGraphTable for example)
        if (isRunning())        c = c.anime();
        return c;
    }

    /**
     * Gets a human readable name for this type of the node.
     *
     * This is used to implement {@link #getDisplayName()} as a fallback in case {@link LabelAction} doesnt exist.
     */
    protected abstract String getTypeDisplayName();

    /**
     * Returns the URL of this {@link FlowNode}, relative to the context root of Jenkins.
     *
     * @return
     *      String like "job/foo/32/execution/node/abcde/" with no leading slash but trailing slash.
     */
    @Exported
    public String getUrl() throws IOException {
        return getExecution().getUrl()+"node/"+getId()+'/';
    }


    /**
     * SPI for subtypes to directly manipulate the actions field.
     *
     * When a brand new {@link FlowNode} is created, or when {@link FlowNode} and actions are
     * stored in close proximity, it is convenient to be able to set the {@link #actions}
     * so as to eliminate the separate call to {@link FlowActionStorage#loadActions(FlowNode)}.
     *
     * This method provides such an opportunity for subtypes.
     */
    protected void setActions(List<Action> actions) {
        this.actions = new CopyOnWriteArrayList<Action>(actions);
    }

/*
    We can't use Actionable#actions to store actions because they aren't transient,
    and we need to store actions elsewhere because this is the only mutable pat of FlowNode.

    So we create a separate transient field and store List of them there, and intercept every mutation.
*/
    @Exported
    @Override
    public synchronized List<Action> getActions() {
                if (actions==null) {
                    try {
                        actions = new CopyOnWriteArrayList<Action>(exec.loadActions(this));
                    } catch (IOException e) {
                        LOGGER.log(WARNING, "Failed to load actions for FlowNode id=" + id, e);
                        actions = new CopyOnWriteArrayList<Action>();
                    }
                }

        return new AbstractList<Action>() {
            @Override
            public Action get(int index) {
                return actions.get(index);
            }

            @Override
            public void add(int index, Action element) {
                actions.add(index, element);
                persist();
            }

            @Override
            public Action remove(int index) {
                Action old = actions.remove(index);
                persist();
                return old;
            }

            @Override
            public Action set(int index, Action element) {
                Action old = actions.set(index, element);
                persist();
                return old;
            }

            @Override
            public int size() {
                return actions.size();
            }

            private void persist() {
                try {
                    save();
                } catch (IOException e) {
                    LOGGER.log(WARNING, "failed to save actions for FlowNode id=" + id, e);
                }
            }
        };
    }

    /**
     * Explicitly save all the actions in this {@link FlowNode}.
     * Useful when an existing {@link Action} gets updated.
     */
    public void save() throws IOException {
        exec.saveActions(this, actions);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof FlowNode) {
            FlowNode that = (FlowNode) obj;
            return this.id.equals(that.id);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return this.id.hashCode();
    }

    @Override
    public String toString() {
        return getClass().getName() + "[id=" + id + "]";
    }

    private static final Logger LOGGER = Logger.getLogger(FlowNode.class.getName());
}
TOP

Related Classes of org.jenkinsci.plugins.workflow.graph.FlowNode

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.