/* * Copyright (c) 2009 The Regents of the University of California. * All rights reserved. * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the above * copyright notice and the following two paragraphs appear in all copies * of this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, * ENHANCEMENTS, OR MODIFICATIONS. */ package org.clothocore.api.actor.workflow; import java.util.List; import java.util.Set; import org.clothocore.api.actor.Actor; import org.clothocore.api.actor.RunStatus; import org.clothocore.api.actor.io.InputPort; import org.clothocore.api.actor.io.OutputPort; import org.jgrapht.DirectedGraph; import org.jgrapht.alg.CycleDetector; import org.jgrapht.graph.DirectedMultigraph; import org.jgrapht.traverse.TopologicalOrderIterator; /** * * @author Bing Xia <bxia@bxia.net> */ public class CompositeActor extends Actor { public CompositeActor() { _workflow = new DirectedMultigraph<Actor, IOLink>( IOLink.class ); _running = false; } public String getName() { return "Composite Algorithm"; } @Override public List<InputPort> getInputs() { return _inputs; } @Override public List<OutputPort> getOutputs() { return _outputs; } /** * Adds the given ClothoAlgorithm to the workflow. * @param alg * @return */ public boolean addAlgorithm( Actor alg ) { if ( _running ) { return false; } _inputs.addAll( alg.getInputs() ); _outputs.addAll( alg.getOutputs() ); return _workflow.addVertex( alg ); } public boolean removeAlgorithm( Actor alg, boolean breakLinks ) { if ( _running ) { return false; } if ( !_workflow.containsVertex( alg ) ) { return false; } Set<IOLink> edgesOf = _workflow.edgesOf( alg ); if ( !breakLinks && !edgesOf.isEmpty() ) { return false; } if ( breakLinks ) { for ( IOLink inLink : _workflow.incomingEdgesOf( alg ) ) { _outputs.add( inLink.out ); } for ( IOLink outLink : _workflow.outgoingEdgesOf( alg ) ) { _inputs.add( outLink.in ); } _workflow.removeAllEdges( edgesOf ); } return true; } /** * Links the given InputPort to the given OutputPort. The ports must be * compatible (in.compatible(out) should return true), and the link must * not create any cycles in the workflow. * @param in * @param out * @return */ public boolean link( OutputPort out, InputPort in ) { // TODO: think about returning what type of error occurred rather than // just true or false if ( _running ) { return false; } Actor start = out.getAlgorithm(); Actor end = in.getAlgorithm(); if ( (!_workflow.containsVertex( start ) || !_workflow.containsVertex( end )) ) { return false; } for ( IOLink link : _workflow.getAllEdges( start, end ) ) { if ( link.in.equals( in ) && link.out.equals( out ) ) { return false; } } for ( IOLink link : _workflow.incomingEdgesOf( end ) ) { if ( link.in.equals( in ) ) { return false; } } try { IOLink link = new IOLink( in, out ); if ( !_workflow.addEdge( start, end, link ) ) { return false; } else { CycleDetector<Actor, IOLink> cd = new CycleDetector<Actor, IOLink>( _workflow ); if ( cd.detectCycles() ) { _workflow.removeEdge( link ); return false; } else { _inputs.remove( in ); _outputs.remove( out ); return true; } } } catch ( IllegalArgumentException iae ) { return false; } } /** * Unlinks the given two ports. * @param out * @param in * @return */ public boolean unlink( OutputPort out, InputPort in ) { if ( _running ) { return false; } Actor start = out.getAlgorithm(); Actor end = in.getAlgorithm(); if ( !_workflow.containsVertex( start ) || !_workflow.containsVertex( end ) ) { return false; } if ( _workflow.removeEdge( start, end ) == null ) { return false; } _inputs.add( in ); _outputs.add( out ); return true; } /** * Removes the given IOLink from the CompositeAlgorithm * @param link * @return */ public boolean unlink( IOLink link ) { return unlink( link.out, link.in ); } /** * Gets the set of all algorithms in the workflow. * @return */ public Set<Actor> getAlgorithms() { return _workflow.vertexSet(); } /** * Gets all the links in the workflow. * @return */ public Set<IOLink> getAllLinks() { return _workflow.edgeSet(); } /** * Returns all the incoming links to the given algorithm. The links returned * will have their InputPorts belong to the given algorithm. * @param alg * @return */ public Set<IOLink> getIncomingLInks( Actor alg ) { if ( !_workflow.containsVertex( alg ) ) { return null; } return _workflow.incomingEdgesOf( alg ); } /** * Returns all the outgoing links of the given algorithm. The links * returned will have the OutputPorts belong to the given algorithm. * @param alg * @return */ public Set<IOLink> getOutgoingLinks( Actor alg ) { if ( !_workflow.containsVertex( alg ) ) { return null; } return _workflow.outgoingEdgesOf( alg ); } /** * Runs the workflow. Since a workflow is defined to not have any cycles, * it is a directed acyclic graph (DAG), and there exists a partial * ordering on the workflow. The algorithms in the workflow are run in * a topological order, which should ensure that the workflow runs to * completion, assuming that all the inputs have been linked to some output. * @return */ @Override public RunStatus run() { _running = true; if ( !isReady() ) { return RunStatus.NOT_READY_ERROR; } boolean success = runTopological(); _running = false; if ( success ) { return RunStatus.COMPLETE; } else { return RunStatus.RUN_ERROR; } } public RunStatus runOneStep() { if ( !_running ) { _running = true; _workflowIterator = new TopologicalOrderIterator<Actor, IOLink>( _workflow ); } if ( !_workflowIterator.hasNext() ) { _running = false; _workflowIterator = null; return RunStatus.COMPLETE; } _currentAlgorithm = _workflowIterator.next(); if ( !_currentAlgorithm.isReady() ) { return RunStatus.NOT_READY_ERROR; } RunStatus result = _currentAlgorithm.run(); if ( result == RunStatus.COMPLETE ) { return RunStatus.ONE_STEP_SUCCESS; } else { return RunStatus.RUN_ERROR; } } @SuppressWarnings (value="unchecked") protected boolean runTopological() { TopologicalOrderIterator<Actor, IOLink> iter = new TopologicalOrderIterator<Actor, IOLink>( _workflow ); while ( iter.hasNext() ) { _currentAlgorithm = iter.next(); if ( !_currentAlgorithm.isReady() ) { // TODO: signal error return false; } if ( _currentAlgorithm.run() != RunStatus.COMPLETE ) { // TODO: signal that alg failed at running return false; } for ( IOLink link : getOutgoingLinks( _currentAlgorithm ) ) { link.in.put( link.out.get() ); } } return true; } /******* Private Variables *******/ protected DirectedGraph<Actor, IOLink> _workflow; protected boolean _running; protected TopologicalOrderIterator<Actor, IOLink> _workflowIterator; protected Actor _currentAlgorithm; }