Package org.jenkinsci.plugins.workflow.stm

Source Code of org.jenkinsci.plugins.workflow.stm.STMExecution$Frame

/*
* 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.stm;

import com.google.common.util.concurrent.ListenableFuture;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
import org.jenkinsci.plugins.workflow.flow.GraphListener;
import org.jenkinsci.plugins.workflow.graph.BlockEndNode;
import org.jenkinsci.plugins.workflow.graph.BlockStartNode;
import org.jenkinsci.plugins.workflow.graph.FlowEndNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.graph.FlowStartNode;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.jenkinsci.plugins.workflow.support.storage.FlowNodeStorage;
import org.jenkinsci.plugins.workflow.support.storage.SimpleXStreamFlowNodeStorage;
import org.jenkinsci.plugins.workflow.pickles.Pickle;
import org.jenkinsci.plugins.workflow.pickles.PickleFactory;
import com.google.common.util.concurrent.FutureCallback;
import hudson.model.Action;
import hudson.model.Result;
import hudson.security.ACL;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.acegisecurity.Authentication;

final class STMExecution extends FlowExecution {
   
    static final Logger LOGGER = Logger.getLogger(STMExecution.class.getName());

    /** Special state for end of execution. */
    static final String END = "end";

    /** Main thread ID. */
    static final String MAIN = "main";

    /** All available value factories; made a field for test injection. */
    static Collection<? extends PickleFactory> valueFactories;

    private final List<State> states;
    private final FlowExecutionOwner owner;
    private final FlowNodeStorage nodeStorage;

    @Override public Authentication getAuthentication() {
        return ACL.SYSTEM; // TODO
    }

    static class Frame {
        String state;
        String id; // needed for blocks
        FutureCallback callback;
        @Override public String toString() {
            return "Frame[" + state + "," + id + "," + callback + "]";
        }
    }
   
    /** List of “program counters”, indexed by “thread name”, with the values being stacks of stack frames (where the top is the currently running block). */
    private final Map<String,Stack<Frame>> pcs = new LinkedHashMap<String,Stack<Frame>>();

    /** List of head node IDs, indexed by thread name. */
    private final Map<String,String> heads = new LinkedHashMap<String,String>();

    /** Generator of node IDs. */
    private int iota;

    private transient Map<String,State> statesByName;

    private transient List<GraphListener> listeners;

    STMExecution(List<State> states, FlowExecutionOwner owner, List<? extends Action> actions) throws IOException {
        this.states = states;
        this.owner = owner;
        nodeStorage = new SimpleXStreamFlowNodeStorage(this, owner.getRootDir());
        getStateMap();
        listeners = new CopyOnWriteArrayList<GraphListener>();
        // TODO what do we do with the initial actions?
    }

    synchronized Map<String,State> getStateMap() {
        if (statesByName == null) {
            statesByName = new HashMap<String,State>();
            for (State s : states) {
                State old = statesByName.put(s.getName(), s);
                if (old != null) {
                    throw new IllegalArgumentException(">1 state named " + s.getName());
                }
            }
        }
        return statesByName;
    }

    private synchronized String newID() {
        return String.valueOf(iota++);
    }

    /**
     * Called from a state to ask the engine to push the program counter forward and start the next step.
     * @param thread the thread name
     * @param next the next state to run
     */
    void next(String thread, String next) {
        Stack<Frame> stack = pcs.get(thread);
        if (stack == null) {
            LOGGER.log(Level.WARNING, "illegal attempt to continue finished thread {0}", thread);
            return;
        }
        LOGGER.log(Level.FINE, "next state is {0} on {1} with stack {2}", new Object[] {next, thread, stack});
        String priorID = heads.get(thread);
        assert priorID != null;
        FlowNode prior;
        try {
            prior = getNode(priorID);
        } catch (IOException x) {
            LOGGER.log(Level.WARNING, null, x);
            return;
        }
        if (next.equals(END)) {
            stack.pop();
            if (stack.isEmpty()) {
                LOGGER.log(Level.FINE, "finishing thread {0}", thread);
                pcs.remove(thread);
                heads.remove(thread);
                try {
                    addingHead(new BlockEndNode<BlockStartNode>(this, newID(), /*TODO*/null, prior) {
                        @Override protected String getTypeDisplayName() {
                            return "Thread end";
                        }
                    });
                    // TODO keep a list of all thread end nodes to collect into finals list in finish()
                    if (heads.isEmpty()) {
                        finish(Result.SUCCESS);
                    }
                } catch (Exception x) {
                    // What to do about it?
                    LOGGER.log(Level.WARNING, null, x);
                }
            } else {
                Frame caller = stack.peek();
                LOGGER.log(Level.FINE, "finishing subroutine from {0} on {1}", new Object[] {caller, thread});
                State s = getStateMap().get(caller.state);
                assert s instanceof BlockState : "found " + s + " rather than a BlockState on the stack for " + caller.state;
                FutureCallback callback = caller.callback;
                assert callback != null : "no callback defined for " + caller.state;
                caller.callback = null;
                callback.onSuccess(null); // TODO should there be a way of passing a return value from the block?
                heads.put(thread, caller.id);
                try {
                    addingHead(new BlockEndNode<BlockStartNode>(this, newID(), /*TODO*/null, /* TODO is this right? or should it be from caller.id? */prior) {
                        @Override protected String getTypeDisplayName() {
                            return "Block end";
                        }
                    });
                } catch (IOException x) {
                    LOGGER.log(Level.WARNING, null, x);
                }
            }
        } else {
            State s = getStateMap().get(next);
            if (s == null) {
                LOGGER.log(Level.WARNING, "no such state {0}", next);
                // How better to recover?
                next(thread, END);
                return;
            }
            String id = newID();
            FlowNode n = s.run(new STMContext(owner, id, next, thread), id, this, prior);
            stack.peek().state = next;
            stack.peek().id = id;
            heads.put(thread, id);
            try {
                addingHead(n);
            } catch (IOException x) {
                // What to do about it?
                LOGGER.log(Level.WARNING, null, x);
            }
        }
    }

    void beginBlock(String thread, FutureCallback callback) {
        Stack<Frame> stack = pcs.get(thread);
        if (stack == null) {
            LOGGER.log(Level.WARNING, "illegal attempt to continue finished thread {0}", thread);
            return;
        }
        LOGGER.log(Level.FINE, "begin block on {0} from {1}", new Object[] {thread, stack});
        stack.peek().callback = callback;
        stack.push(new Frame());
    }

    private void addingHead(FlowNode node) throws IOException {
        LOGGER.log(Level.FINE, "adding head: {0}", node/* NPE when Jenkins.instance == null: .getDisplayName()*/);
        nodeStorage.storeNode(node);
        for (GraphListener listener : listeners) {
            listener.onNewHead(node);
        }
    }

    @Override public void start() throws IOException {
        FlowStartNode start = new FlowStartNode(this, newID());
        heads.put(MAIN, start.getId());
        addingHead(start);
        // TODO factor this out into a general thread start function:
        BlockStartNode blockStart = new BlockStartNode(this, newID(), start) {
            @Override protected String getTypeDisplayName() {
                return "Thread start";
            }
        };
        heads.put(MAIN, blockStart.getId());
        addingHead(blockStart);
        LOGGER.fine("starting flow");
        if (states.isEmpty())  {
            // TODO is there a cleaner way of handling this?
            try {
                finish(Result.SUCCESS);
                return;
            } catch (InterruptedException x) {
                throw new IOException(x);
            }
        }
        Stack<Frame> stack = new Stack<Frame>();
        stack.push(new Frame());
        pcs.put(MAIN, stack);
        next(MAIN, states.get(0).getName());
    }
   
    void success(String id, Object returnValue) {
        for (Map.Entry<String,String> entry : heads.entrySet()) { // TODO synchronization
            if (id.equals(entry.getValue())) {
                String thread = entry.getKey();
                Stack<Frame> stack = pcs.get(thread);
                assert stack != null;
                String stateName = stack.peek().state;
                assert stateName != null;
                State s = getStateMap().get(stateName);
                assert s != null : "no such state " + stateName;
                LOGGER.log(Level.FINE, "success from state {0} returning {1}", new Object[] {stateName, returnValue});
                s.success(this, thread, returnValue);
                return;
            }
        }
        LOGGER.log(Level.WARNING, "{0} is not being run on any current thread", id);
    }

    @Override public void onLoad() {
        listeners = new CopyOnWriteArrayList<GraphListener>();
        // TODO
    }

    @Override public List<FlowNode> getCurrentHeads() {
        List<FlowNode> r = new ArrayList<FlowNode>();
        for (String head : heads.values()) {
            try {
                FlowNode node = getNode(head);
                if (node == null) {
                    LOGGER.log(Level.WARNING, "no such head node {0}", head);
                    continue;
                }
                r.add(node);
            } catch (IOException x) {
                LOGGER.log(Level.WARNING, null, x);
            }
        }
        return r;
    }

    @Override
    public boolean isCurrentHead(FlowNode n) {
        return heads.values().contains(n.getId());
    }

    @Override
    public ListenableFuture<List<StepExecution>> getCurrentExecutions() {
        // TODO
        throw new UnsupportedOperationException();
    }

    @Override public void addListener(GraphListener listener) {
        listeners.add(listener);
    }
   
    @Override public void finish(Result r) throws IOException, InterruptedException {
        LOGGER.log(Level.FINE, "finishing with {0}", r);
        List<FlowNode> finals = new ArrayList<FlowNode>();
        for (Map.Entry<String,String> entry : heads.entrySet()) {
            String thread = entry.getKey();
            String priorID = entry.getValue();
            assert priorID != null;
            FlowNode prior = getNode(priorID);
            BlockEndNode<BlockStartNode> blockEndNode = new BlockEndNode<BlockStartNode>(this, newID(), /*TODO*/ null, prior) {
                @Override protected String getTypeDisplayName() {
                    return "Thread end";
                }
            };
            addingHead(blockEndNode);
            finals.add(blockEndNode);
        }
        pcs.clear();
        heads.clear();
        String endID = newID();
        heads.put(MAIN, endID);
        addingHead(new FlowEndNode(this, endID, /*TODO*/null, r, finals.toArray(new FlowNode[finals.size()])));
    }

    private static Object perhapsConvertToValue(Object object) {
        // TODO this will not work, since various serializable objects (such as WorkspaceStep.Callback) hold references to objects that should be pickled
        for (PickleFactory f : (valueFactories == null ? PickleFactory.all() : valueFactories)) {
            Pickle v = f.writeReplace(object);
            if (v != null) {
                return v;
            }
        }
        return object;
    }

    @Override public FlowExecutionOwner getOwner() {
        return owner;
    }

    @Override public FlowNode getNode(String id) throws IOException {
        return nodeStorage.getNode(id);
    }

    public List<Action> loadActions(FlowNode node) throws IOException {
        return nodeStorage.loadActions(node);
    }

    public void saveActions(FlowNode node, List<Action> actions) throws IOException {
        nodeStorage.saveActions(node, actions);
    }

}
TOP

Related Classes of org.jenkinsci.plugins.workflow.stm.STMExecution$Frame

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.