/** * Copyright (c) 2010 Yahoo! Inc. All rights reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. See accompanying LICENSE file. */ package org.apache.oozie.workflow.lite; import org.apache.hadoop.io.Writable; import org.apache.hadoop.util.ReflectionUtils; import org.apache.oozie.workflow.WorkflowApp; import org.apache.oozie.workflow.WorkflowException; import org.apache.oozie.util.ParamChecker; import org.apache.oozie.util.XLog; import org.apache.oozie.ErrorCode; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; //TODO javadoc public class LiteWorkflowApp implements Writable, WorkflowApp { private String name; private String definition; private Map<String, NodeDef> nodesMap = new LinkedHashMap<String, NodeDef>(); private boolean complete = false; LiteWorkflowApp() { } public LiteWorkflowApp(String name, String definition, StartNodeDef startNode) { this.name = ParamChecker.notEmpty(name, "name"); this.definition = ParamChecker.notEmpty(definition, "definition"); nodesMap.put(StartNodeDef.START, startNode); } public boolean equals(LiteWorkflowApp other) { return !(other == null || getClass() != other.getClass() || !getName().equals(other.getName())); } public int hashCode() { return name.hashCode(); } public LiteWorkflowApp addNode(NodeDef node) throws WorkflowException { ParamChecker.notNull(node, "node"); if (complete) { throw new WorkflowException(ErrorCode.E0704, XLog.format("Definition [{0}] already complete", name)); } if (nodesMap.containsKey(node.getName())) { throw new WorkflowException(ErrorCode.E0705, XLog.format("Node [{0}] already defined", node.getName())); } if (node.getTransitions().contains(node.getName())) { throw new WorkflowException(ErrorCode.E0706, XLog.format("Node [{0}] cannot transition to itself", node.getName())); } nodesMap.put(node.getName(), node); if (node instanceof EndNodeDef) { complete = true; } return this; } public String getName() { return name; } public String getDefinition() { return definition; } public Collection<NodeDef> getNodeDefs() { return Collections.unmodifiableCollection(nodesMap.values()); } public NodeDef getNode(String name) { return nodesMap.get(name); } public void validateWorkflowIntegrity() { //TODO traverse wf, ensure there are not cycles, no open paths, and one END } public void validateTransition(String name, String transition) { ParamChecker.notEmpty(name, "name"); ParamChecker.notEmpty(transition, "transition"); NodeDef node = getNode(name); if (!node.getTransitions().contains(transition)) { throw new IllegalArgumentException("invalid transition"); } } @Override public void write(DataOutput dataOutput) throws IOException { dataOutput.writeUTF(name); //dataOutput.writeUTF(definition); //writeUTF() has limit 65535, so split long string to multiple short strings List<String> defList = divideStr(definition); dataOutput.writeInt(defList.size()); for (String d : defList) { dataOutput.writeUTF(d); } dataOutput.writeInt(nodesMap.size()); for (NodeDef n : getNodeDefs()) { dataOutput.writeUTF(n.getClass().getName()); n.write(dataOutput); } } /** * To split long string to a list of smaller strings. * * @param str * @return List */ private List<String> divideStr(String str) { List<String> list = new ArrayList<String>(); int len = 20000; int strlen = str.length(); int start = 0; int end = len; while (end < strlen) { list.add(str.substring(start, end)); start = end; end += len; } if (strlen <= end) { list.add(str.substring(start, strlen)); } return list; } @Override public void readFields(DataInput dataInput) throws IOException { name = dataInput.readUTF(); //definition = dataInput.readUTF(); //read the full definition back int defListSize = dataInput.readInt(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < defListSize; i++) { sb.append(dataInput.readUTF()); } definition = sb.toString(); int numNodes = dataInput.readInt(); for (int x = 0; x < numNodes; x++) { try { String nodeDefClass = dataInput.readUTF(); NodeDef node = (NodeDef) ReflectionUtils.newInstance(Class.forName(nodeDefClass), null); node.readFields(dataInput); addNode(node); } catch (WorkflowException ex) { throw new IOException(ex); } catch (ClassNotFoundException e) { throw new IOException(e); } } } }