package org.sigmah.server.dispatch.impl; /* * #%L * Sigmah * %% * Copyright (C) 2010 - 2016 URD * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.time.StopWatch; import org.sigmah.client.page.Page; import org.sigmah.client.page.PageRequest; import org.sigmah.client.page.RequestParameter; import org.sigmah.client.security.SecureDispatchAsync.CommandExecution; import org.sigmah.server.dispatch.CommandHandler; import org.sigmah.server.dispatch.CommandHandlerRegistry; import org.sigmah.server.dispatch.Dispatch; import org.sigmah.server.dispatch.ExecutionContext; import org.sigmah.server.domain.User; import org.sigmah.server.servlet.base.ServletExecutionContext; import org.sigmah.shared.command.base.Command; import org.sigmah.shared.command.result.Result; import org.sigmah.shared.dispatch.CommandException; import org.sigmah.shared.dispatch.DispatchException; import org.sigmah.shared.dispatch.UnsupportedCommandException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Inject; /** * Dispatch custom implementation. * * @author Denis Colliot (dcolliot@ideia.fr) */ public class UserDispatch implements Dispatch { /** * Execution context provided to handlers in order to execute sub-commands. * * @author Denis Colliot (dcolliot@ideia.fr) */ public static final class UserExecutionContext extends ServletExecutionContext implements ExecutionContext { /** * The dispatch instance. */ private final UserDispatch dispatch; /** * Sub-commands. */ private final List<CommandResult<?, ?>> commandResults; /** * The application URL. */ private final String applicationUrl; /** * Initializes a new user execution context. * * @param {@link #dispatch} * @param {@link #user} * @param {@link #request} * @param {@link #originPageToken} */ private UserExecutionContext(final UserDispatch dispatch, final User user, final HttpServletRequest request, final String originPageToken) { super(user, request, originPageToken); this.dispatch = dispatch; this.commandResults = new java.util.ArrayList<CommandResult<?, ?>>(); this.applicationUrl = request != null ? request.getHeader("Referer").split(PageRequest.URL_TOKEN)[0] : null; } /** * <p> * Initializes a new user execution context from the given {@code servletContext}. * </p> * <p> * <b>Warning: this context cannot allow sub-command execution or provide application url.</b> * </p> * * @param servletContext * The servlet execution context. */ public UserExecutionContext(final ServletExecutionContext servletContext) { super(servletContext.getUser(), servletContext.getRequest(), servletContext.getOriginPageToken()); this.dispatch = null; this.commandResults = null; this.applicationUrl = null; } /** * {@inheritDoc} */ @Override public <C extends Command<R>, R extends Result> R execute(final C command) throws CommandException { return execute(command, true); } /** * {@inheritDoc} */ @Override public <C extends Command<R>, R extends Result> R execute(final C command, final boolean allowRollback) throws CommandException { // Executes the sub-action. final R result = dispatch.doExecute(command, this); // Registers it and its result. if (allowRollback) { commandResults.add(new CommandResult<C, R>(command, result)); } return result; } /** * Cancels the memorized commands or results. * * @throws DispatchException */ private void rollback() throws DispatchException { for (int i = commandResults.size() - 1; i >= 0; i--) { final CommandResult<?, ?> actionResult = commandResults.get(i); rollback(actionResult); } } /** * Rollbacks a command execution. * * @param <C> * The command type. * @param <R> * The result type. * @param commandResult * The command and the command result. * @throws DispatchException * If the roll back failed. */ private <C extends Command<R>, R extends Result> void rollback(final CommandResult<C, R> commandResult) throws DispatchException { dispatch.doRollback(commandResult.getCommand(), commandResult.getResult(), this); } /** * The application URL. * * @return The application URL. */ public final String getApplicationUrl() { return getApplicationUrl(null, null); } /** * The application URL. * * @param page * The specific page to include into URL. * @param parameters * The page parameters to include into URL. * @return The application URL. */ public final String getApplicationUrl(final Page page, final Map<RequestParameter, String> parameters) { if (page == null) { return applicationUrl; } return applicationUrl + PageRequest.toUrl(page.getToken(), parameters); } } /** * Logger. */ private static final Logger LOG = LoggerFactory.getLogger(UserDispatch.class); /** * The command-handler registry instance used to find commands handler. */ private final CommandHandlerRegistry handlerRegistry; @Inject public UserDispatch(final CommandHandlerRegistry handlerRegistry) { this.handlerRegistry = handlerRegistry; } /** * Find the given {@code command} corresponding handler. * * @param <C> * The command type. * @param <R> * The command result type. * @param command * The command. * @return the given {@code command} corresponding handler. * @throws UnsupportedCommandException * If no handler cannot be found for the command. */ private <C extends Command<R>, R extends Result> CommandHandler<C, R> findHandler(final C command) throws UnsupportedCommandException { // Asks the handler to the registry. final CommandHandler<C, R> handler = handlerRegistry.findHandler(command); // If there is no handler, throws an exception. if (handler == null) { throw new UnsupportedCommandException(command); } return handler; } /** * {@inheritDoc} */ @Override public <C extends Command<R>, R extends Result> R execute(final CommandExecution<C, R> commandExecution, final User user, final HttpServletRequest request) throws DispatchException { // Builds a new user execution context. final UserExecutionContext context = createContext(user, request, commandExecution.getCurrentPageToken()); try { // Tries to execute the action. return doExecute(commandExecution.getCommand(), context); } catch (final CommandException e) { // Rollback if necessary. context.rollback(); throw e; } } /** * Executes the given command from server side. * * @param <C> * Command type. * @param <R> * Result type. * @param command * Command to execute. * @param executionContext * Execution context of the servlet. * @return Execution result. * @throws DispatchException * If the command handler execution fails. */ public <C extends Command<R>, R extends Result> R execute(final C command, final ServletExecutionContext executionContext) throws DispatchException { // Builds a new user execution context. final UserExecutionContext context = createContext(executionContext.getUser(), executionContext.getRequest(), null); try { // Tries to execute the action. return doExecute(command, context); } catch (final CommandException e) { // Rollback if necessary. context.rollback(); throw e; } } /** * Creates a new <code>UserExecutionContext</code> for the given request and * user. * * @param user * The user executing the command. * @param request * The servlet HTTP request. * @param originPageToken * Token of the page. * @return A new <code>UserExecutionContext</code>. */ public UserExecutionContext createContext(final User user, final HttpServletRequest request, final String originPageToken) { return new UserExecutionContext(this, user, request, originPageToken); } /** * Executes a command. * * @param <C> * The command type. * @param <R> * The command result type. * @param context * The execution context. * @return The command execution result. * @throws CommandException * If the command handler execution fails. */ private <C extends Command<R>, R extends Result> R doExecute(final C command, final UserExecutionContext context) throws CommandException { // Retrieves the handler. final CommandHandler<C, R> handler = findHandler(command); if (LOG.isDebugEnabled()) { LOG.debug("EXECUTING COMMAND - Command: '{}' ; Handler: '{}' ; User: '{}'.", command, handler, context.getUser()); } final StopWatch chrono; if (LOG.isDebugEnabled()) { chrono = new StopWatch(); chrono.start(); } else { chrono = null; } // Asks for the action execution. final R executionResult = handler.execute(command, context); if (LOG.isDebugEnabled() && chrono != null) { chrono.stop(); LOG.debug("COMMAND '{}' EXECUTED IN {} MS.", command, chrono.getTime()); } return executionResult; } /** * Rollbacks a command. * * @param <C> * The command type. * @param <R> * The command result type. * @param command * The command. * @param result * The command result. * @param ctx * The execution context. * @throws DispatchException * If execution fails. */ private <C extends Command<R>, R extends Result> void doRollback(final C command, final R result, final UserExecutionContext context) throws DispatchException { // Retrieves the handler. final CommandHandler<C, R> handler = findHandler(command); if (LOG.isInfoEnabled()) { LOG.info("ROLLBACKING COMMAND - Action: '{}' ; Handler: '{}' ; User: '{}'.", command, handler, context.getUser()); } // Asks for the action rollback. handler.rollback(command, result, context); } }