/* * 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.core.client.canvas; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; import com.google.gwt.logging.client.LogConfiguration; import org.kie.workbench.common.stunner.core.api.DefinitionManager; import org.kie.workbench.common.stunner.core.client.ShapeSet; import org.kie.workbench.common.stunner.core.client.api.ShapeManager; import org.kie.workbench.common.stunner.core.client.canvas.util.CanvasLayoutUtils; import org.kie.workbench.common.stunner.core.client.service.ClientRuntimeError; import org.kie.workbench.common.stunner.core.client.shape.ElementShape; import org.kie.workbench.common.stunner.core.client.shape.Lifecycle; import org.kie.workbench.common.stunner.core.client.shape.MutationContext; import org.kie.workbench.common.stunner.core.client.shape.Shape; import org.kie.workbench.common.stunner.core.client.shape.factory.ShapeFactory; import org.kie.workbench.common.stunner.core.command.CommandResult; import org.kie.workbench.common.stunner.core.definition.property.PropertyMetaTypes; import org.kie.workbench.common.stunner.core.diagram.Diagram; import org.kie.workbench.common.stunner.core.graph.Edge; import org.kie.workbench.common.stunner.core.graph.Element; import org.kie.workbench.common.stunner.core.graph.Node; import org.kie.workbench.common.stunner.core.graph.content.definition.Definition; import org.kie.workbench.common.stunner.core.graph.content.view.View; import org.kie.workbench.common.stunner.core.graph.util.GraphUtils; import org.kie.workbench.common.stunner.core.rule.RuleSet; import org.uberfire.mvp.Command; import org.uberfire.mvp.ParameterizedCommand; /** * A base canvas handler type that provides implementations for most of the public API methods * for the <code>AbstractCanvasHandler</code> super-type. * You can use this type if: * - You need a custom graph index or graph index builder types. * - You need custom rule loading or do not want support for rules and their evaluations. * - You need custom draw logics. * @param <D> The diagram type. * @param <C> The handled canvas type. */ public abstract class BaseCanvasHandler<D extends Diagram, C extends AbstractCanvas> extends AbstractCanvasHandler<D, C> { private static Logger LOGGER = Logger.getLogger(BaseCanvasHandler.class.getName()); private final DefinitionManager definitionManager; private final GraphUtils graphUtils; private final ShapeManager shapeManager; private C canvas; private D diagram; private RuleSet ruleSet; public BaseCanvasHandler(final DefinitionManager definitionManager, final GraphUtils graphUtils, final ShapeManager shapeManager) { this.definitionManager = definitionManager; this.graphUtils = graphUtils; this.shapeManager = shapeManager; } /** * Build the graph index instance using any concrete index/builder types. * This abstract implementation expects a not null instance for the graph index. * @param loadCallback Callback to run once load finishes. This kind of indexes could be loaded or * cached in/from server side as well. */ protected abstract void buildGraphIndex(final Command loadCallback); /** * Delegates the draw behavior to the subtypes. * @param loadCallback Callback to run once draw has finished. It must provide a result for * the draw operation/s. */ public abstract void draw(final ParameterizedCommand<CommandResult<?>> loadCallback); /** * Destroys this instance' graph index. * @param loadCallback Callback to run once index has been destroyed. */ protected abstract void destroyGraphIndex(final Command loadCallback); @Override public CanvasHandler<D, C> handle(final C canvas) { this.canvas = canvas; return this; } @Override @SuppressWarnings("unchecked") public void draw(final D diagram, final ParameterizedCommand<CommandResult<?>> loadCallback) { if (null == this.canvas) { throw new IllegalStateException("No handled canvas instance."); } this.diagram = diagram; // Initialize the graph handler that provides processing and querying operations over the graph. buildGraphIndex(() -> loadRuleSet(() -> draw(loadCallback))); } @Override public RuleSet getRuleSet() { return ruleSet; } @Override public C getCanvas() { return canvas; } @Override public D getDiagram() { return diagram; } protected void loadRuleSet(final Command callback) { final String id = getDiagram().getMetadata().getDefinitionSetId(); final Object defSet = getDefinitionManager().definitionSets().getDefinitionSetById(id); this.ruleSet = definitionManager.adapters().forRules().getRuleSet(defSet); callback.execute(); } @Override @SuppressWarnings("unchecked") public ShapeFactory<Object, AbstractCanvasHandler, Shape> getShapeFactory(final String shapeSetId) { ShapeSet<?> shapeSet = shapeManager.getShapeSet(shapeSetId); if (null == shapeSet) { LOGGER.log(Level.SEVERE, "ShapeSet [" + shapeSetId + "] not found. Using the default one,."); } shapeSet = shapeManager.getDefaultShapeSet(diagram.getMetadata().getDefinitionSetId()); return shapeSet.getShapeFactory(); } @Override public void register(final Shape shape, final Element<View<?>> candidate, final boolean fireEvents) { // Add the shapes on canvas and fire events. addShape(shape); getCanvas().draw(); if (fireEvents) { // Fire listeners. notifyCanvasElementAdded(candidate); // Fire updates. afterElementAdded(candidate, shape); } } @Override public void deregister(final Shape shape, final Element element, final boolean fireEvents) { if (fireEvents) { // Fire listeners. notifyCanvasElementRemoved(element); // Fire events. beforeElementDeleted(element, shape); } removeShape(shape); getCanvas().draw(); if (fireEvents) { afterElementDeleted(element, shape); } } public void addShape(final Shape shape) { getCanvas().addShape(shape); } public void removeShape(final Shape shape) { getCanvas().deleteShape(shape); } @Override @SuppressWarnings("unchecked") public void applyElementMutation(final Shape shape, final Element candidate, final boolean applyPosition, final boolean applyProperties, final MutationContext mutationContext) { if (shape instanceof ElementShape) { final ElementShape graphShape = (ElementShape) shape; this.applyElementMutation(graphShape, candidate, applyPosition, applyProperties, mutationContext); } else { LOGGER.log(Level.WARNING, "The shape to handle must be type of [" + ElementShape.class.getName() + "]"); } } @SuppressWarnings("unchecked") protected void applyElementMutation(final ElementShape graphShape, final Element candidate, final boolean applyPosition, final boolean applyProperties, final MutationContext mutationContext) { if (applyPosition) { graphShape.applyPosition(candidate, mutationContext); } if (applyProperties) { applyElementTitle(graphShape, candidate, mutationContext); graphShape.applyProperties(candidate, mutationContext); } beforeDraw(candidate, graphShape); beforeElementUpdated(candidate, graphShape); getCanvas().draw(); afterDraw(candidate, graphShape); notifyCanvasElementUpdated(candidate); afterElementUpdated(candidate, graphShape); } @SuppressWarnings("unchecked") protected void applyElementTitle(final ElementShape shape, final Element candidate, final MutationContext mutationContext) { final Definition<Object> content = (Definition<Object>) candidate.getContent(); final Object definition = content.getDefinition(); final Object nameProperty = getDefinitionManager().adapters().forDefinition().getMetaProperty(PropertyMetaTypes.NAME, definition); if (null != nameProperty) { final String name = (String) getDefinitionManager().adapters().forProperty().getValue(nameProperty); shape.applyTitle(name, candidate, mutationContext); } } protected void beforeDraw(final Element element, final Shape shape) { if (shape instanceof Lifecycle) { final Lifecycle lifecycle = (Lifecycle) shape; lifecycle.beforeDraw(); } } protected void afterDraw(final Element element, final Shape shape) { if (shape instanceof Lifecycle) { final Lifecycle lifecycle = (Lifecycle) shape; lifecycle.afterDraw(); } } @Override @SuppressWarnings("unchecked") public void addChild(final Element parent, final Element child) { final Shape childShape = getCanvas().getShape(child.getUUID()); if (!isCanvasRoot(parent)) { final Shape parentShape = getCanvas().getShape(parent.getUUID()); getCanvas().addChildShape(parentShape, childShape); } else { // -- Special case when parent is the canvas root -- // Ensure the shape is added into the layer, but no need to register it again and generate new // handlers ( f.i. using canvas#addShape() method ). getCanvas().getLayer().addShape(childShape.getShapeView()); } } @Override public void addChild(final Element parent, final Element child, final int index) { throw new UnsupportedOperationException(); } @Override @SuppressWarnings("unchecked") public void removeChild(final Element parent, final Element child) { final String parentUUID = parent.getUUID(); final String childUUID = child.getUUID(); final Shape childShape = getCanvas().getShape(childUUID); if (!isCanvasRoot(parentUUID)) { final Shape parentShape = getCanvas().getShape(parentUUID); getCanvas().deleteChildShape(parentShape, childShape); } else { // -- Special case when parent is the canvas root -- // Ensure the shape is removed from the layer, but no need to deregister any // handlers ( f.i. using canvas#removeShape() method ). getCanvas().getLayer().removeShape(childShape.getShapeView()); } } @Override @SuppressWarnings("unchecked") public Optional<Element> getElementAt(final double x, final double y) { final Optional<Shape> shape = getCanvas().getShapeAt(x, y); return shape.flatMap(s -> Optional.of(getGraphExecutionContext().getGraphIndex().getNode(s.getUUID()))); } protected boolean isCanvasRoot(final String pUUID) { return CanvasLayoutUtils.isCanvasRoot(getDiagram(), pUUID); } @Override public void dock(final Element parent, final Element child) { if (!isCanvasRoot(parent)) { final Shape parentShape = getCanvas().getShape(parent.getUUID()); final Shape childShape = getCanvas().getShape(child.getUUID()); getCanvas().dock(parentShape, childShape); } } @Override @SuppressWarnings("unchecked") public void undock(final Element target, final Element child) { final String targetUUID = target.getUUID(); final String childUUID = child.getUUID(); if (!isCanvasRoot(targetUUID)) { final Shape targetShape = getCanvas().getShape(targetUUID); final Shape childShape = getCanvas().getShape(childUUID); final Element<?> childParent = GraphUtils.getParent((Node<?, Edge>) child); final String childParentUUID = null != childParent ? childParent.getUUID() : null; final Shape newParentShape = null != childParentUUID && !isCanvasRoot(childParentUUID) ? getCanvas().getShape(childParentUUID) : null; getCanvas().undock(targetShape, newParentShape, childShape); } } protected void afterElementAdded(final Element element, final Shape shape) { // Implementations can do post operations here. } protected void beforeElementDeleted(final Element element, final Shape shape) { // Implementations can do post operations here. } protected void afterElementDeleted(final Element element, final Shape shape) { // Implementations can do post operations here. } protected void beforeElementUpdated(final Element element, final Shape shape) { // Implementations can do post operations here. } protected void afterElementUpdated(final Element element, final Shape shape) { // Implementations can do post operations here. } @Override public CanvasHandler<D, C> doClear() { destroyGraphIndex(() -> { diagram = null; }); return this; } @Override public void doDestroy() { destroyGraphIndex(() -> { canvas = null; diagram = null; }); } protected void showError(final ClientRuntimeError error) { final String message = error.getThrowable() != null ? error.getThrowable().getMessage() : error.getMessage(); log(Level.SEVERE, message); } @Override public DefinitionManager getDefinitionManager() { return definitionManager; } public GraphUtils getGraphUtils() { return graphUtils; } public ShapeManager getShapeManager() { return shapeManager; } protected String getDefinitionId(final Object definition) { return getDefinitionManager().adapters().forDefinition().getId(definition); } private void log(final Level level, final String message) { if (LogConfiguration.loggingIsEnabled()) { LOGGER.log(level, message); } } }