/* * Copyright 2017 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.kie.workbench.common.stunner.bpmn.backend.marshall.json.builder; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.kie.workbench.common.stunner.bpmn.definition.BPMNDefinition; import org.kie.workbench.common.stunner.bpmn.definition.property.dimensions.Height; import org.kie.workbench.common.stunner.bpmn.definition.property.dimensions.Radius; import org.kie.workbench.common.stunner.bpmn.definition.property.dimensions.Width; import org.kie.workbench.common.stunner.core.api.FactoryManager; import org.kie.workbench.common.stunner.core.command.Command; import org.kie.workbench.common.stunner.core.command.CommandResult; import org.kie.workbench.common.stunner.core.graph.Edge; import org.kie.workbench.common.stunner.core.graph.Node; import org.kie.workbench.common.stunner.core.graph.command.GraphCommandExecutionContext; import org.kie.workbench.common.stunner.core.graph.command.impl.AddNodeCommand; import org.kie.workbench.common.stunner.core.graph.content.Bounds; import org.kie.workbench.common.stunner.core.graph.content.view.BoundImpl; import org.kie.workbench.common.stunner.core.graph.content.view.BoundsImpl; import org.kie.workbench.common.stunner.core.graph.content.view.View; import org.kie.workbench.common.stunner.core.graph.content.view.ViewConnector; import org.kie.workbench.common.stunner.core.graph.util.GraphUtils; import org.kie.workbench.common.stunner.core.rule.RuleViolation; // TODO: Improve error handling. public abstract class AbstractNodeBuilder<W, T extends Node<View<W>, Edge>> extends AbstractObjectBuilder<W, T> implements NodeObjectBuilder<W, T> { protected final Class<?> definitionClass; protected Set<String> childNodeIds; public AbstractNodeBuilder(final Class<?> definitionClass) { this.definitionClass = definitionClass; this.childNodeIds = new LinkedHashSet<String>(); } @Override public Class<?> getDefinitionClass() { return definitionClass; } @Override public AbstractNodeBuilder<W, T> child(final String nodeId) { childNodeIds.add(nodeId); return this; } @Override @SuppressWarnings("unchecked") protected T doBuild(final BuilderContext context) { FactoryManager factoryManager = context.getFactoryManager(); // Build the graph node for the definition. String definitionId = getDefinitionToBuild(context); T result = (T) factoryManager.newElement(this.nodeId, definitionId); // Set the def properties. setProperties(context, (BPMNDefinition) result.getContent().getDefinition()); // View Bounds. setBounds(context, result); AddNodeCommand addNodeCommand = context.getCommandFactory().addNode(result); if (doExecuteCommand(context, addNodeCommand)) { // Post processing. afterNodeBuild(context, result); } else { // TODO: throw an exception and handle the error. } return result; } protected String getDefinitionToBuild(final BuilderContext context) { return context.getOryxManager().getMappingsManager().getDefinitionId(definitionClass); } protected void setBounds(BuilderContext context, T node) { if (null != boundUL && null != boundLR) { Bounds bounds = new BoundsImpl( new BoundImpl(boundUL[0], boundUL[1]), new BoundImpl(boundLR[0], boundLR[1])); node.getContent().setBounds(bounds); setSize(context, node); } } protected void setSize(final BuilderContext context, final T node) { final double[] size = GraphUtils.getNodeSize(node.getContent()); setSize(context, node, size[0], size[1]); } protected void setSize(final BuilderContext context, final T node, final double width, final double height) { Object definition = node.getContent().getDefinition(); Width w = null; Height h = null; Set<?> properties = context.getDefinitionManager().adapters().forDefinition().getProperties(definition); if (null != properties) { // Look for w/h or radius and set the values. for (Object property : properties) { if (property instanceof Radius) { Radius radius = (Radius) property; double r = getRadius(width, height); radius.setValue(r); break; } if (property instanceof Width) { w = (Width) property; w.setValue(width); if (h != null) { break; } } if (property instanceof Height) { h = (Height) property; h.setValue(height); if (w != null) { break; } } } } } private double getRadius(final double width, final double height) { return width / 2; } @SuppressWarnings("unchecked") protected void afterNodeBuild(final BuilderContext context, final T node) { // Outgoing connections. if (outgoingResourceIds != null && !outgoingResourceIds.isEmpty()) { for (String outgoingNodeId : outgoingResourceIds) { GraphObjectBuilder<?, ?> outgoingBuilder = getBuilder(context, outgoingNodeId); if (outgoingBuilder == null) { throw new RuntimeException("No outgoing edge builder for " + outgoingNodeId); } final List<Command<GraphCommandExecutionContext, RuleViolation>> commands = new LinkedList<>(); // If outgoing element it's a node means that it's docked. if (outgoingBuilder instanceof AbstractNodeBuilder) { // Command - Create the docked node. Node docked = (Node) outgoingBuilder.build(context); commands.add(context.getCommandFactory().addDockedNode(node, docked)); // Obtain docked position and use those for the docked node. final List<Double[]> dockers = ((AbstractNodeBuilder) outgoingBuilder).dockers; if (!dockers.isEmpty()) { // TODO: Use not only first docker coordinates? Double[] dCoords = dockers.get(0); double x = dCoords[0]; double y = dCoords[1]; commands.add(context.getCommandFactory().updatePosition(docked, x, y)); } } else { // Create the outgoing edge. Edge edge = (Edge) outgoingBuilder.build(context); // Command - Execute the graph command to set the node as the edge connection's source.. int magnetIdx = getSourceConnectionMagnetIndex(context, node, edge); commands.add(context.getCommandFactory().setSourceNode(node, edge, magnetIdx)); ; } if (!commands.isEmpty()) { for (Command<GraphCommandExecutionContext, RuleViolation> command : commands) { doExecuteCommand(context, command); } } } } // Children connections. if (childNodeIds != null && !childNodeIds.isEmpty()) { for (String childNodeId : childNodeIds) { GraphObjectBuilder<?, ?> childNodeBuilder = getBuilder(context, childNodeId); if (childNodeBuilder == null) { throw new RuntimeException("No child node builder for " + childNodeId); } Command<GraphCommandExecutionContext, RuleViolation> command = null; if (childNodeBuilder instanceof NodeObjectBuilder) { // Command - Create the child node and the parent-child relationship. Node childNode = (Node) childNodeBuilder.build(context); command = context.getCommandFactory().addChildNode(node, childNode); } if (null != command) { doExecuteCommand(context, command); } } } } private boolean doExecuteCommand(final BuilderContext context, final Command<GraphCommandExecutionContext, RuleViolation> command) { CommandResult<RuleViolation> results = context.execute(command); if (hasErrors(results)) { throw new RuntimeException("Error building BPMN graph. " + "Command = [" + command.toString() + "] " + " Resutls = [" + results.toString() + "]"); } return true; } public int getSourceConnectionMagnetIndex(final BuilderContext context, final T node, final Edge<ViewConnector<W>, Node> edge) { return 3; } public int getTargetConnectionMagnetIndex(final BuilderContext context, final T node, final Edge<ViewConnector<W>, Node> edge) { return 7; } @Override public String toString() { return super.toString() + " [defClass=" + definitionClass.getName() + "] [childrenIds=" + childNodeIds + "] "; } }