/* * 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.command; import java.util.logging.Level; import java.util.logging.Logger; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Observes; import javax.inject.Inject; import org.kie.workbench.common.stunner.core.client.api.AbstractClientSessionManager; import org.kie.workbench.common.stunner.core.client.canvas.AbstractCanvasHandler; import org.kie.workbench.common.stunner.core.client.canvas.event.mouse.CanvasMouseDownEvent; import org.kie.workbench.common.stunner.core.client.canvas.event.mouse.CanvasMouseUpEvent; import org.kie.workbench.common.stunner.core.client.session.event.SessionDestroyedEvent; import org.kie.workbench.common.stunner.core.client.session.event.SessionOpenedEvent; import org.kie.workbench.common.stunner.core.command.Command; import org.kie.workbench.common.stunner.core.command.CommandListener; import org.kie.workbench.common.stunner.core.command.CommandResult; import org.kie.workbench.common.stunner.core.command.impl.CommandRegistryListener; import org.kie.workbench.common.stunner.core.command.impl.CompositeCommandImpl; import org.kie.workbench.common.stunner.core.command.util.CommandUtils; import org.kie.workbench.common.stunner.core.registry.command.CommandRegistry; import static org.uberfire.commons.validation.PortablePreconditions.checkNotNull; /** * This is a concrete implementation for a SessionCommandManager, but instead * of adding each executed command in the session's registry, it adds only a single composite command, which is * composed by the commands executed on each client request. * This implementation considers a client request the time frame between mouse down and mouse up events are fired * on the canvas. * Using this implementation is useful for components that use commands that must be executed in an atomic operation, so * undo/redo will be done as a single execution as well. */ @ApplicationScoped @Request public class RequestCommandManager extends AbstractSessionCommandManager { private static Logger LOGGER = Logger.getLogger(RequestCommandManager.class.getName()); private final AbstractClientSessionManager clientSessionManager; protected RequestCommandManager() { this(null); } @Inject public RequestCommandManager(final AbstractClientSessionManager clientSessionManager) { this.clientSessionManager = clientSessionManager; } @Override protected AbstractClientSessionManager getClientSessionManager() { return clientSessionManager; } @Override protected CommandListener<AbstractCanvasHandler, CanvasViolation> getRegistryListener() { return registryListener; } /** * The current command builder instance for each client request. It aggregates the commands executed during the request. */ private CompositeCommandImpl.CompositeCommandBuilder<AbstractCanvasHandler, CanvasViolation> currentCommandBuilder; /** * The custom command registry listener implementation - instead of adding commands into session's registry * once successful executions, it aggregates the commands into the command builder instance to perform a single * execution once client request is completed. */ private CommandListener<AbstractCanvasHandler, CanvasViolation> registryListener = new CommandRegistryListener<AbstractCanvasHandler, CanvasViolation>() { @Override public void onAllow(final AbstractCanvasHandler context, final Command<AbstractCanvasHandler, CanvasViolation> command, final CommandResult<CanvasViolation> result) { // Nothing to do with the command registry for the allow operation. // Notify listener, if any. RequestCommandManager.this.postAllow(context, command, result); } @Override public void onExecute(final AbstractCanvasHandler context, final Command<AbstractCanvasHandler, CanvasViolation> command, final CommandResult<CanvasViolation> result) { if (!CommandUtils.isError(result)) { LOGGER.log(Level.FINE, "Adding command [" + command + "] into current request command builder."); currentCommandBuilder.addCommand(command); } // Notify listener, if any. RequestCommandManager.this.postExecute(context, command, result); } @Override public void onUndo(final AbstractCanvasHandler context, final Command<AbstractCanvasHandler, CanvasViolation> command, final CommandResult<CanvasViolation> result) { super.onUndo(context, command, result); // Notify listener, if any. RequestCommandManager.this.postUndo(context, command, result); } @Override protected CommandRegistry<Command<AbstractCanvasHandler, CanvasViolation>> getRegistry() { return RequestCommandManager.this.getRegistry(); } }; /** * Listens to canvas mouse down event. It produces a new client request to start. */ void onCanvasMouseDownEvent(final @Observes CanvasMouseDownEvent mouseDownEvent) { checkNotNull("mouseDownEvent", mouseDownEvent); start(); } /** * Listens to canvas up down event. It produces the current client request to complete. */ void onCanvasMouseUpEvent(final @Observes CanvasMouseUpEvent mouseUpEvent) { checkNotNull("mouseUpEvent", mouseUpEvent); complete(); } /** * Checks that once opening a new client session, no pending requests are present. */ void onCanvasSessionOpened(final @Observes SessionOpenedEvent sessionOpenedEvent) { checkNotNull("sessionOpenedEvent", sessionOpenedEvent); if (isRequestStarted()) { LOGGER.log(Level.WARNING, "New session opened but the request has not been completed. Unexpected behaviors can occur."); clear(); } } /** * Checks that once disposing a client session, no pending requests are present. */ void onCanvasSessionDestroyed(final @Observes SessionDestroyedEvent sessionDestroyedEvent) { checkNotNull("sessionDestroyedEvent", sessionDestroyedEvent); if (isRequestStarted()) { LOGGER.log(Level.WARNING, "Current client request has not been completed yet."); } } /** * Starts a new client request. */ private void start() { if (isRequestStarted()) { LOGGER.log(Level.WARNING, "Current client request has not been completed yet." + "A new client request cannot be started!"); clear(); } LOGGER.log(Level.FINE, "New client request started."); currentCommandBuilder = new CompositeCommandImpl .CompositeCommandBuilder<AbstractCanvasHandler, CanvasViolation>() .forward(); } /** * Completes the current client request. It registers the composite command into the * session's registry. */ private void complete() { LOGGER.log(Level.FINE, "Checking if current client request has been completed..."); if (isRequestStarted()) { // If any commands have been aggregated, let's execute those. if (currentCommandBuilder.size() > 0) { LOGGER.log(Level.FINE, "Adding commands for current request into registry [size=" + currentCommandBuilder.size() + "]"); getRegistry().register(currentCommandBuilder.build()); } clear(); LOGGER.log(Level.FINE, "Current client request completed."); } else { LOGGER.log(Level.WARNING, "Current client request has not been started."); } } private boolean isRequestStarted() { return null != currentCommandBuilder; } private void clear() { currentCommandBuilder = null; } }