/*
* 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.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Stack;
import org.codehaus.jackson.Base64Variant;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.JsonStreamContext;
import org.codehaus.jackson.ObjectCodec;
import org.kie.workbench.common.stunner.bpmn.backend.marshall.json.oryx.OryxManager;
import org.kie.workbench.common.stunner.bpmn.definition.BPMNDefinition;
import org.kie.workbench.common.stunner.bpmn.definition.BPMNDiagram;
import org.kie.workbench.common.stunner.bpmn.factory.BPMNGraphFactory;
import org.kie.workbench.common.stunner.core.api.DefinitionManager;
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.CommandManager;
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.Graph;
import org.kie.workbench.common.stunner.core.graph.Node;
import org.kie.workbench.common.stunner.core.graph.command.EmptyRulesCommandExecutionContext;
import org.kie.workbench.common.stunner.core.graph.command.GraphCommandExecutionContext;
import org.kie.workbench.common.stunner.core.graph.command.impl.GraphCommandFactory;
import org.kie.workbench.common.stunner.core.graph.content.definition.DefinitionSet;
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.processing.index.GraphIndexBuilder;
import org.kie.workbench.common.stunner.core.graph.processing.index.Index;
import org.kie.workbench.common.stunner.core.rule.RuleManager;
import org.kie.workbench.common.stunner.core.rule.RuleViolation;
import org.kie.workbench.common.stunner.core.util.UUID;
/**
* Support for a basic single process hierarchy
*/
public class BPMNGraphGenerator extends JsonGenerator {
private final GraphObjectBuilderFactory bpmnGraphBuilderFactory;
private final DefinitionManager definitionManager;
private final FactoryManager factoryManager;
private final RuleManager ruleManager;
private final OryxManager oryxManager;
private final CommandManager<GraphCommandExecutionContext, RuleViolation> commandManager;
private final GraphCommandFactory commandFactory;
private final GraphIndexBuilder<?> indexBuilder;
private final Class<?> diagramDefinitionSetClass;
private final Class<? extends BPMNDefinition> diagramDefinitionClass;
private final Stack<GraphObjectBuilder> nodeBuilders = new Stack<>();
private final Stack<GraphObjectParser> parsers = new Stack<GraphObjectParser>();
private final Collection<GraphObjectBuilder<?, ?>> builders = new LinkedList<GraphObjectBuilder<?, ?>>();
Graph<DefinitionSet, Node> graph;
boolean isClosed;
public BPMNGraphGenerator(final GraphObjectBuilderFactory bpmnGraphBuilderFactory,
final DefinitionManager definitionManager,
final FactoryManager factoryManager,
final RuleManager ruleManager,
final OryxManager oryxManager,
final CommandManager<GraphCommandExecutionContext, RuleViolation> commandManager,
final GraphCommandFactory commandFactory,
final GraphIndexBuilder<?> indexBuilder,
final Class<?> diagramDefinitionSetClass,
final Class<? extends BPMNDiagram> diagramDefinitionClass) {
this.bpmnGraphBuilderFactory = bpmnGraphBuilderFactory;
this.definitionManager = definitionManager;
this.factoryManager = factoryManager;
this.ruleManager = ruleManager;
this.oryxManager = oryxManager;
this.commandManager = commandManager;
this.commandFactory = commandFactory;
this.indexBuilder = indexBuilder;
this.diagramDefinitionSetClass = diagramDefinitionSetClass;
this.diagramDefinitionClass = diagramDefinitionClass;
this.parsers.push(new RootObjectParser(null));
this.isClosed = false;
}
@Override
public void writeStartObject() throws IOException, JsonGenerationException {
parsers.peek().writeStartObject();
}
@Override
public void writeEndObject() throws IOException, JsonGenerationException {
parsers.peek().writeEndObject();
}
@Override
public void writeFieldName(final String s) throws IOException, JsonGenerationException {
parsers.peek().writeFieldName(s);
}
@Override
public void writeObject(final Object o) throws IOException, JsonProcessingException {
parsers.peek().writeObject(o);
}
@Override
public void writeStartArray() throws IOException, JsonGenerationException {
parsers.peek().writeStartArray();
}
@Override
public void writeEndArray() throws IOException, JsonGenerationException {
parsers.peek().writeEndArray();
}
@Override
public boolean isClosed() {
return this.isClosed;
}
@Override
@SuppressWarnings("unchecked")
public void close() throws IOException {
logBuilders();
this.graph = (Graph<DefinitionSet, Node>) factoryManager.newElement(UUID.uuid(),
diagramDefinitionSetClass);
// TODO: Where are the BPMN diagram bounds in the Oryx json structure? Exist?
if (null == graph.getContent().getBounds()) {
graph.getContent().setBounds(new BoundsImpl(
new BoundImpl(0d,
0d),
new BoundImpl(BPMNGraphFactory.GRAPH_DEFAULT_WIDTH,
BPMNGraphFactory.GRAPH_DEFAULT_HEIGHT)
));
}
builderContext
// Initialize the builder context.
.init(graph)
// Clears the nodes present, if any, on the recently new graph instance for BPMN. This generator
// provides the adf for the complete graph structure and nodes.
.execute(builderContext.getCommandFactory().clearGraph());
NodeObjectBuilder diagramBuilder = getDiagramBuilder(builderContext);
if (diagramBuilder == null) {
throw new RuntimeException("No diagrams found!");
}
Node<View<BPMNDefinition>, Edge> diagramNode = (Node<View<BPMNDefinition>, Edge>) diagramBuilder.build(builderContext);
graph.addNode(diagramNode);
this.isClosed = true;
}
@SuppressWarnings("unchecked")
protected NodeObjectBuilder getDiagramBuilder(final GraphObjectBuilder.BuilderContext context) {
Collection<GraphObjectBuilder<?, ?>> builders = context.getBuilders();
if (builders != null && !builders.isEmpty()) {
for (GraphObjectBuilder<?, ?> builder : builders) {
try {
NodeObjectBuilder nodeBuilder = (NodeObjectBuilder) builder;
if (diagramDefinitionClass.equals(nodeBuilder.getDefinitionClass())) {
return nodeBuilder;
}
} catch (ClassCastException e) {
// Not a node. Continue with the search...
}
}
}
return null;
}
public Graph<DefinitionSet, Node> getGraph() {
assert isClosed();
return this.graph;
}
final GraphObjectBuilder.BuilderContext builderContext = new GraphObjectBuilder.BuilderContext() {
Graph<DefinitionSet, Node> graph;
Index<?, ?> index;
@Override
public GraphObjectBuilder.BuilderContext init(final Graph<DefinitionSet, Node> graph) {
this.graph = graph;
this.index = indexBuilder.build(graph);
return this;
}
@Override
public Index<?, ?> getIndex() {
return index;
}
@Override
public Collection<GraphObjectBuilder<?, ?>> getBuilders() {
return builders;
}
@Override
public DefinitionManager getDefinitionManager() {
return definitionManager;
}
@Override
public FactoryManager getFactoryManager() {
return factoryManager;
}
@Override
public OryxManager getOryxManager() {
return oryxManager;
}
@SuppressWarnings("unchecked")
public CommandResult<RuleViolation> execute(final Command<GraphCommandExecutionContext, RuleViolation> command) {
GraphCommandExecutionContext executionContext =
new EmptyRulesCommandExecutionContext(definitionManager,
factoryManager,
ruleManager,
index);
return commandManager.execute(executionContext,
command);
}
public GraphCommandFactory getCommandFactory() {
return commandFactory;
}
};
// For local testing...
private void logBuilders() {
log("Logging builders at close time...");
for (GraphObjectBuilder<?, ?> builder : builders) {
log(builder.toString());
}
}
private interface GraphObjectParser {
void writeStartObject();
void writeEndObject();
void writeFieldName(String s);
void writeObject(Object o);
void writeStartArray();
void writeEndArray();
}
/*
Handles these fields:
- resourceId -> The node identifier.
- properties -> Delegates to properties object parser
- stencil -> Delegates to stencil object parser
- childShapes -> Delegates to other root object parsers
- outgoing -> Delegates to outgoing object parser
- bounds -> Delegates to bounds object parser
*/
final class RootObjectParser implements GraphObjectParser {
String fieldName;
NodeObjectBuilder parentNodeBuilder;
public RootObjectParser(final NodeObjectBuilder parentNodeBuilder) {
this.parentNodeBuilder = parentNodeBuilder;
}
@Override
public void writeStartObject() {
if (fieldName == null) {
nodeBuilders.push(bpmnGraphBuilderFactory.bootstrapBuilder());
} else if ("properties".equals(fieldName)) {
parsers.push(new PropertiesObjectParser());
} else if ("stencil".equals(fieldName)) {
parsers.push(new StencilObjectParser());
} else if ("childShapes".equals(fieldName)) {
RootObjectParser rootObjectParser = nodeBuilders.empty() ? null :
new RootObjectParser((NodeObjectBuilder) nodeBuilders.peek());
parsers.push(rootObjectParser);
nodeBuilders.push(bpmnGraphBuilderFactory.bootstrapBuilder());
} else if ("outgoing".equals(fieldName)) {
parsers.push(new OutgoingObjectParser());
} else if ("bounds".equals(fieldName)) {
parsers.push(new BoundsObjectParser());
} else if ("dockers".equals(fieldName)) {
parsers.push(new DockersObjectParser());
} else {
parsers.push(new DummyObjectParser());
}
}
@Override
public void writeEndObject() {
GraphObjectBuilder builder = nodeBuilders.pop();
builders.add(builder);
parsers.pop();
}
@Override
public void writeFieldName(final String s) {
this.fieldName = s;
}
@Override
public void writeObject(final Object o) {
String value = o.toString();
if ("resourceId".equals(fieldName)) {
nodeBuilders.peek().nodeId(value);
if (null != parentNodeBuilder) {
parentNodeBuilder.child(value);
}
}
}
@Override
public void writeStartArray() {
}
@Override
public void writeEndArray() {
}
}
;
final class PropertiesObjectParser implements GraphObjectParser {
String fieldName;
@Override
public void writeStartObject() {
}
@Override
public void writeEndObject() {
parsers.pop();
}
@Override
public void writeFieldName(final String s) {
this.fieldName = s;
}
@Override
public void writeObject(final Object o) {
nodeBuilders.peek().property(fieldName,
o.toString());
}
@Override
public void writeStartArray() {
}
@Override
public void writeEndArray() {
}
}
final class StencilObjectParser implements GraphObjectParser {
String fieldName;
@Override
public void writeStartObject() {
}
@Override
public void writeEndObject() {
parsers.pop();
}
@Override
public void writeFieldName(final String s) {
this.fieldName = s;
}
@Override
public void writeObject(final Object o) {
if ("id".equals(fieldName)) {
// Replace the current node builder by the implementation for the specific stencil identifier.
GraphObjectBuilder builder = nodeBuilders.pop().stencil(o.toString());
nodeBuilders.push(builder);
}
}
@Override
public void writeStartArray() {
}
@Override
public void writeEndArray() {
}
}
final class OutgoingObjectParser implements GraphObjectParser {
String fieldName;
@Override
public void writeStartObject() {
}
@Override
public void writeEndObject() {
parsers.pop();
}
@Override
public void writeFieldName(final String s) {
this.fieldName = s;
}
@Override
public void writeObject(final Object o) {
if ("resourceId".equals(fieldName)) {
nodeBuilders.peek().out(o.toString());
}
}
@Override
public void writeStartArray() {
}
@Override
public void writeEndArray() {
}
}
final class BoundsObjectParser implements GraphObjectParser {
String fieldName;
boolean isLR = false;
boolean isUL = false;
boolean end = false;
Double ulX;
Double ulY;
Double lrX;
Double lrY;
@Override
public void writeStartObject() {
if ("lowerRight".equals(fieldName)) {
isLR = true;
}
if ("upperLeft".equals(fieldName)) {
isUL = true;
}
}
@Override
public void writeEndObject() {
if (end) {
nodeBuilders.peek().boundUL(ulX,
ulY);
nodeBuilders.peek().boundLR(lrX,
lrY);
parsers.pop();
}
if (isLR && isUL) {
end = true;
}
}
@Override
public void writeFieldName(final String s) {
this.fieldName = s;
}
@Override
public void writeObject(final Object o) {
String value = o.toString();
Double d = Double.valueOf(value);
if ("x".equals(fieldName)) {
if (isUL && ulX == null) {
ulX = d;
}
if (isLR && lrX == null) {
lrX = d;
}
}
if ("y".equals(fieldName)) {
if (isUL && ulY == null) {
ulY = d;
}
if (isLR && lrY == null) {
lrY = d;
}
}
}
@Override
public void writeStartArray() {
}
@Override
public void writeEndArray() {
}
}
final class DockersObjectParser implements GraphObjectParser {
String fieldName;
Double x = 0d;
Double y = 0d;
@Override
public void writeStartObject() {
}
@Override
public void writeEndObject() {
nodeBuilders.peek().docker(x,
y);
}
@Override
public void writeFieldName(final String s) {
this.fieldName = s;
}
@Override
public void writeObject(final Object o) {
String value = o.toString();
Double d = Double.valueOf(value);
if ("x".equals(fieldName)) {
this.x = d;
}
if ("y".equals(fieldName)) {
this.y = d;
}
}
@Override
public void writeStartArray() {
}
@Override
public void writeEndArray() {
parsers.pop();
}
}
final class DummyObjectParser implements GraphObjectParser {
@Override
public void writeStartObject() {
parsers.push(new DummyObjectParser());
}
@Override
public void writeEndObject() {
parsers.pop();
}
@Override
public void writeFieldName(final String s) {
}
@Override
public void writeObject(final Object o) {
}
@Override
public void writeStartArray() {
}
@Override
public void writeEndArray() {
}
}
private void log(final String message) {
System.out.println(message);
}
/***********************************************************************************
* NOT IMPLEMENTED METHODS.
***********************************************************************************/
@Override
public void flush() throws IOException {
// Not called...
}
@Override
public JsonGenerator enable(final Feature feature) {
return null;
}
@Override
public JsonGenerator disable(final Feature feature) {
return null;
}
@Override
public boolean isEnabled(final Feature feature) {
return false;
}
@Override
public JsonGenerator setCodec(final ObjectCodec objectCodec) {
return null;
}
@Override
public ObjectCodec getCodec() {
return null;
}
@Override
public JsonGenerator useDefaultPrettyPrinter() {
return null;
}
@Override
public void writeString(final String s) throws IOException, JsonGenerationException {
}
@Override
public void writeString(final char[] chars,
final int i,
final int i1) throws IOException, JsonGenerationException {
}
@Override
public void writeRawUTF8String(final byte[] bytes,
final int i,
final int i1) throws IOException, JsonGenerationException {
}
@Override
public void writeUTF8String(final byte[] bytes,
final int i,
final int i1) throws IOException, JsonGenerationException {
}
@Override
public void writeRaw(final String s) throws IOException, JsonGenerationException {
}
@Override
public void writeRaw(final String s,
final int i,
final int i1) throws IOException, JsonGenerationException {
}
@Override
public void writeRaw(final char[] chars,
final int i,
final int i1) throws IOException, JsonGenerationException {
}
@Override
public void writeRaw(final char c) throws IOException, JsonGenerationException {
}
@Override
public void writeRawValue(final String s) throws IOException, JsonGenerationException {
}
@Override
public void writeRawValue(final String s,
final int i,
final int i1) throws IOException, JsonGenerationException {
}
@Override
public void writeRawValue(char[] chars,
int i,
int i1) throws IOException, JsonGenerationException {
}
@Override
public void writeBinary(final Base64Variant base64Variant,
final byte[] bytes,
final int i,
final int i1) throws IOException, JsonGenerationException {
}
@Override
public void writeNumber(final int i) throws IOException, JsonGenerationException {
}
@Override
public void writeNumber(final long l) throws IOException, JsonGenerationException {
}
@Override
public void writeNumber(final BigInteger bigInteger) throws IOException, JsonGenerationException {
}
@Override
public void writeNumber(final double v) throws IOException, JsonGenerationException {
}
@Override
public void writeNumber(final float v) throws IOException, JsonGenerationException {
}
@Override
public void writeNumber(final BigDecimal bigDecimal) throws IOException, JsonGenerationException {
}
@Override
public void writeNumber(final String s) throws IOException, JsonGenerationException, UnsupportedOperationException {
}
@Override
public void writeBoolean(final boolean b) throws IOException, JsonGenerationException {
}
@Override
public void writeNull() throws IOException, JsonGenerationException {
}
@Override
public void writeTree(final JsonNode jsonNode) throws IOException, JsonProcessingException {
}
@Override
public void copyCurrentEvent(final JsonParser jsonParser) throws IOException, JsonProcessingException {
}
@Override
public void copyCurrentStructure(final JsonParser jsonParser) throws IOException, JsonProcessingException {
}
@Override
public JsonStreamContext getOutputContext() {
return null;
}
}