/** * Copyright 2010 Red Hat, Inc. and/or its affiliates. * * 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. */ package org.jbpm.workflow.core.node; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.kie.api.definition.process.Connection; import org.kie.api.definition.process.Node; import org.jbpm.workflow.core.NodeContainer; import org.jbpm.workflow.core.impl.ConnectionImpl; import org.jbpm.workflow.core.impl.NodeContainerImpl; import org.jbpm.workflow.core.impl.NodeImpl; /** * */ public class CompositeNode extends StateBasedNode implements NodeContainer, EventNodeInterface { private static final long serialVersionUID = 510l; private org.jbpm.workflow.core.NodeContainer nodeContainer; private Map<String, CompositeNode.NodeAndType> inConnectionMap = new HashMap<String, CompositeNode.NodeAndType>(); private Map<String, CompositeNode.NodeAndType> outConnectionMap = new HashMap<String, CompositeNode.NodeAndType>(); private boolean cancelRemainingInstances = true; private boolean autoComplete = true; public CompositeNode() { this.nodeContainer = new NodeContainerImpl(); } public Node getNode(long id) { return nodeContainer.getNode(id); } public Node internalGetNode(long id) { return getNode(id); } public Node[] getNodes() { List<Node> subNodes = new ArrayList<Node>(); for (Node node: nodeContainer.getNodes()) { if (!(node instanceof CompositeNode.CompositeNodeStart) && !(node instanceof CompositeNode.CompositeNodeEnd)) { subNodes.add(node); } } return subNodes.toArray(new Node[subNodes.size()]); } public Node[] internalGetNodes() { return getNodes(); } public void addNode(Node node) { // TODO find a more elegant solution for this // preferrable remove id setting from this class // and delegate to GUI command that drops node if (node.getId() <= 0) { long id = 0; for (Node n: nodeContainer.getNodes()) { if (n.getId() > id) { id = n.getId(); } } ((org.jbpm.workflow.core.Node) node).setId(++id); } nodeContainer.addNode(node); ((org.jbpm.workflow.core.Node) node).setNodeContainer(this); } protected void internalAddNode(Node node) { addNode(node); } public void removeNode(Node node) { nodeContainer.removeNode(node); ((org.jbpm.workflow.core.Node) node).setNodeContainer(null); } protected void internalRemoveNode(Node node) { removeNode(node); } public boolean acceptsEvent(String type, Object event) { for (Node node: internalGetNodes()) { if (node instanceof EventNodeInterface) { if (((EventNodeInterface) node).acceptsEvent(type, event)) { return true; } } } return false; } public void linkIncomingConnections(String inType, long inNodeId, String inNodeType) { linkIncomingConnections(inType, new NodeAndType(nodeContainer, inNodeId, inNodeType)); } public void linkIncomingConnections(String inType, CompositeNode.NodeAndType inNode) { CompositeNode.NodeAndType oldNodeAndType = inConnectionMap.get(inType); if (oldNodeAndType != null) { if (oldNodeAndType.equals(inNode)) { return; } else { // remove old start nodes + connections List<Connection> oldInConnections = oldNodeAndType.getNode().getIncomingConnections(oldNodeAndType.getType()); if (oldInConnections != null) { for (Connection connection: new ArrayList<Connection>(oldInConnections)) { if (connection.getFrom() instanceof CompositeNodeStart) { removeNode(connection.getFrom()); ((ConnectionImpl) connection).terminate(); } } } } } inConnectionMap.put(inType, inNode); if (inNode != null) { List<Connection> connections = getIncomingConnections(inType); for (Connection connection: connections) { CompositeNodeStart start = new CompositeNodeStart(this, connection.getFrom(), inType); internalAddNode(start); if (inNode.getNode() != null) { new ConnectionImpl( start, org.jbpm.workflow.core.Node.CONNECTION_DEFAULT_TYPE, inNode.getNode(), inNode.getType()); } } } } public void linkOutgoingConnections(long outNodeId, String outNodeType, String outType) { linkOutgoingConnections(new NodeAndType(this, outNodeId, outNodeType), outType); } public void linkOutgoingConnections(CompositeNode.NodeAndType outNode, String outType) { CompositeNode.NodeAndType oldNodeAndType = outConnectionMap.get(outType); if (oldNodeAndType != null) { if (oldNodeAndType.equals(outNode)) { return; } else { // remove old end nodes + connections List<Connection> oldOutConnections = oldNodeAndType.getNode().getOutgoingConnections(oldNodeAndType.getType()); for (Connection connection: new ArrayList<Connection>(oldOutConnections)) { if (connection.getTo() instanceof CompositeNodeEnd) { removeNode(connection.getTo()); ((ConnectionImpl) connection).terminate(); } } } } outConnectionMap.put(outType, outNode); if (outNode != null) { List<Connection> connections = getOutgoingConnections(outType); for (Connection connection: connections) { CompositeNodeEnd end = new CompositeNodeEnd(this, connection.getTo(), outType); internalAddNode(end); if (outNode.getNode() != null) { new ConnectionImpl( outNode.getNode(), outNode.getType(), end, org.jbpm.workflow.core.Node.CONNECTION_DEFAULT_TYPE); } } } } public CompositeNode.NodeAndType getLinkedIncomingNode(String inType) { return inConnectionMap.get(inType); } public CompositeNode.NodeAndType internalGetLinkedIncomingNode(String inType) { return inConnectionMap.get(inType); } public CompositeNode.NodeAndType getLinkedOutgoingNode(String outType) { return outConnectionMap.get(outType); } public CompositeNode.NodeAndType internalGetLinkedOutgoingNode(String outType) { return outConnectionMap.get(outType); } public Map<String, CompositeNode.NodeAndType> getLinkedIncomingNodes() { return inConnectionMap; } public Map<String, CompositeNode.NodeAndType> getLinkedOutgoingNodes() { return outConnectionMap; } public void validateAddIncomingConnection(final String type, final Connection connection) { CompositeNode.NodeAndType nodeAndType = internalGetLinkedIncomingNode(type); if (connection.getFrom().getNodeContainer() == this) { if (nodeAndType != null) { throw new IllegalArgumentException("Cannot link incoming connection type more than once: " + type); } } else { if (nodeAndType != null) { NodeImpl node = (NodeImpl) nodeAndType.getNode(); if (node != null) { node.validateAddIncomingConnection(nodeAndType.getType(), connection); } } } } public void addIncomingConnection(String type, Connection connection) { if (connection.getFrom().getNodeContainer() == this) { linkOutgoingConnections(connection.getFrom().getId(), connection.getFromType(), org.jbpm.workflow.core.Node.CONNECTION_DEFAULT_TYPE); } else { super.addIncomingConnection(type, connection); CompositeNode.NodeAndType inNode = internalGetLinkedIncomingNode(type); if (inNode != null) { CompositeNodeStart start = new CompositeNodeStart(this, connection.getFrom(), type); internalAddNode(start); NodeImpl node = (NodeImpl) inNode.getNode(); if (node != null) { new ConnectionImpl( start, org.jbpm.workflow.core.Node.CONNECTION_DEFAULT_TYPE, inNode.getNode(), inNode.getType()); } } } } public void validateAddOutgoingConnection(final String type, final Connection connection) { CompositeNode.NodeAndType nodeAndType = internalGetLinkedOutgoingNode(type); if (connection.getTo().getNodeContainer() == this) { if (nodeAndType != null) { throw new IllegalArgumentException("Cannot link outgoing connection type more than once: " + type); } } else { if (nodeAndType != null) { NodeImpl node = (NodeImpl) nodeAndType.getNode(); if (node != null) { ((NodeImpl) nodeAndType.getNode()).validateAddOutgoingConnection(nodeAndType.getType(), connection); } } } } public void addOutgoingConnection(String type, Connection connection) { if (connection.getTo().getNodeContainer() == this) { linkIncomingConnections( org.jbpm.workflow.core.Node.CONNECTION_DEFAULT_TYPE, connection.getTo().getId(), connection.getToType()); } else { super.addOutgoingConnection(type, connection); CompositeNode.NodeAndType outNode = internalGetLinkedOutgoingNode(type); if (outNode != null) { CompositeNodeEnd end = new CompositeNodeEnd(this, connection.getTo(), type); internalAddNode(end); NodeImpl node = (NodeImpl) outNode.getNode(); if (node != null) { new ConnectionImpl( outNode.getNode(), outNode.getType(), end, org.jbpm.workflow.core.Node.CONNECTION_DEFAULT_TYPE); } } } } public void validateRemoveIncomingConnection(final String type, final Connection connection) { CompositeNode.NodeAndType nodeAndType = internalGetLinkedIncomingNode(type); if (nodeAndType != null) { for (Connection inConnection: nodeAndType.getNode().getIncomingConnections(nodeAndType.getType())) { if (((CompositeNodeStart) inConnection.getFrom()).getInNodeId() == connection.getFrom().getId()) { ((NodeImpl) nodeAndType.getNode()).validateRemoveIncomingConnection(nodeAndType.getType(), inConnection); return; } } throw new IllegalArgumentException( "Could not find internal incoming connection for node"); } } public void removeIncomingConnection(String type, Connection connection) { super.removeIncomingConnection(type, connection); CompositeNode.NodeAndType nodeAndType = internalGetLinkedIncomingNode(type); if (nodeAndType != null) { for (Connection inConnection: nodeAndType.getNode().getIncomingConnections(nodeAndType.getType())) { if (((CompositeNodeStart) inConnection.getFrom()).getInNodeId() == connection.getFrom().getId()) { Node compositeNodeStart = inConnection.getFrom(); ((ConnectionImpl) inConnection).terminate(); internalRemoveNode(compositeNodeStart); return; } } throw new IllegalArgumentException( "Could not find internal incoming connection for node"); } } public void validateRemoveOutgoingConnection(final String type, final Connection connection) { CompositeNode.NodeAndType nodeAndType = internalGetLinkedOutgoingNode(type); if (nodeAndType != null) { for (Connection outConnection: nodeAndType.getNode().getOutgoingConnections(nodeAndType.getType())) { if (((CompositeNodeEnd) outConnection.getTo()).getOutNodeId() == connection.getTo().getId()) { ((NodeImpl) nodeAndType.getNode()).validateRemoveOutgoingConnection(nodeAndType.getType(), outConnection); return; } } throw new IllegalArgumentException( "Could not find internal outgoing connection for node"); } } public void removeOutgoingConnection(String type, Connection connection) { super.removeOutgoingConnection(type, connection); CompositeNode.NodeAndType nodeAndType = internalGetLinkedOutgoingNode(type); if (nodeAndType != null) { for (Connection outConnection: nodeAndType.getNode().getOutgoingConnections(nodeAndType.getType())) { if (((CompositeNodeEnd) outConnection.getTo()).getOutNodeId() == connection.getTo().getId()) { Node compositeNodeEnd = outConnection.getTo(); ((ConnectionImpl) outConnection).terminate(); internalRemoveNode(compositeNodeEnd); return; } } throw new IllegalArgumentException( "Could not find internal outgoing connection for node"); } } public boolean isCancelRemainingInstances() { return cancelRemainingInstances; } public void setCancelRemainingInstances(boolean cancelRemainingInstances) { this.cancelRemainingInstances = cancelRemainingInstances; } public boolean isAutoComplete() { return autoComplete; } public void setAutoComplete(boolean autoComplete) { this.autoComplete = autoComplete; } public static class NodeAndType implements Serializable { private static final long serialVersionUID = 510l; private NodeContainer nodeContainer; private long nodeId; private String type; private transient Node node; public NodeAndType(NodeContainer nodeContainer, long nodeId, String type) { if (type == null) { throw new IllegalArgumentException( "Node or type may not be null!"); } this.nodeId = nodeId; this.type = type; this.nodeContainer = nodeContainer; } public NodeAndType(Node node, String type) { if (node == null || type == null) { throw new IllegalArgumentException( "Node or type may not be null!"); } this.nodeId = node.getId(); this.node = node; this.type = type; } public Node getNode() { if (node == null) { try { node = nodeContainer.getNode(nodeId); } catch (IllegalArgumentException e) { // unknown node id, returning null } } return node; } public long getNodeId() { return nodeId; } public String getType() { return type; } public boolean equals(Object o) { if (o instanceof NodeAndType) { return nodeId == ((NodeAndType) o).nodeId && type.equals(((NodeAndType) o).type); } return false; } public int hashCode() { return 7*(int)nodeId + 13*type.hashCode(); } } public static class CompositeNodeStart extends NodeImpl { private static final long serialVersionUID = 510l; private CompositeNode parentNode; private long inNodeId; private transient Node inNode; private String inType; public CompositeNodeStart(CompositeNode parentNode, Node outNode, String outType) { setName("Composite node start"); this.inNodeId = outNode.getId(); this.inNode = outNode; this.inType = outType; this.parentNode = parentNode; setMetaData("hidden", true); } public Node getInNode() { if (inNode == null) { inNode = ((NodeContainer) parentNode.getNodeContainer()).internalGetNode(inNodeId); } return inNode; } public long getInNodeId() { return inNodeId; } public String getInType() { return inType; } } public static class CompositeNodeEnd extends NodeImpl { private static final long serialVersionUID = 510l; private CompositeNode parentNode; private long outNodeId; private transient Node outNode; private String outType; public CompositeNodeEnd(CompositeNode parentNode, Node outNode, String outType) { setName("Composite node end"); this.outNodeId = outNode.getId(); this.outNode = outNode; this.outType = outType; this.parentNode = parentNode; setMetaData("hidden", true); } public Node getOutNode() { if (outNode == null) { outNode = ((NodeContainer) parentNode.getNodeContainer()).internalGetNode(outNodeId); } return outNode; } public long getOutNodeId() { return outNodeId; } public String getOutType() { return outType; } } }