package org.sigmah.client.dispatch;
/*
* #%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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.sigmah.client.ui.widget.Loadable;
import org.sigmah.client.util.ClientUtils;
import org.sigmah.shared.command.base.Command;
import org.sigmah.shared.command.result.Result;
import org.sigmah.shared.dispatch.FunctionalException;
import org.sigmah.shared.validation.ValidationException;
/**
* <p>
* Dispatch queue.
* </p>
* <p>
* Enables parallel dispatch actions calls in an easy way.<br>
* Client-side order can be preserved if necessary (attention, not server-side).
* </p>
* <p>
* How does it work ?
*
* <pre>
* final DispatchQueue queue = new DispatchQueue(DispatchAsync) {
*
* public void onComplete() {
* // Do something... or not !
* }
*
* };
*
* queue.add(command1, new CommandResultHandler<R>() {
* // Result handler implementation...
* }
* .add(command2, new CommandResultHandler<R>() {
* // Result handler implementation...
* }
* .add(command3, new CommandResultHandler<R>() {
* // Result handler implementation...
* };
*
* queue.start();
* </pre>
*
* </p>
*
* @author Denis Colliot (dcolliot@ideia.fr)
*/
public class DispatchQueue {
/**
* Inner private class wrapping a dispatch command execution.
*
* @author Denis Colliot (dcolliot@ideia.fr)
* @param <C>
* The dispatch command type.
* @param <R>
* The dispatch command result type.
*/
private final class CommandWrapper<C extends Command<R>, R extends Result> {
private final int order;
private final C command;
private final CommandResultHandler<R> commandResultHandler;
private final Collection<Loadable> loadables;
/**
* Has already returned to the client-side and is waiting to be processed.
*/
private boolean ready;
// Result data (success or failure).
private R result;
private Throwable caught;
private CommandWrapper(final int order, final C command, final CommandResultHandler<R> commandResultHandler, final Collection<Loadable> loadables) {
this.order = order;
this.command = command;
this.commandResultHandler = commandResultHandler;
this.loadables = loadables;
}
/**
* Executes the current wrapped action.
*/
private void execute() {
if (dispatch == null) {
handleExecutionComplete();
return;
}
dispatch.execute(command, new CommandResultHandler<R>() {
@Override
protected void onCommandSuccess(final R result) {
CommandWrapper.this.result = result;
ready = true;
if (!preserveClientOrder || nextOrder.equals(order)) {
handleSuccessResult();
}
}
@Override
protected void onCommandFailure(final Throwable caught) {
CommandWrapper.this.caught = caught;
ready = true;
if (!preserveClientOrder || nextOrder.equals(order)) {
handleFailureResult();
}
}
@Override
protected void onCommandViolation(final ValidationException caught) {
CommandWrapper.this.caught = caught;
ready = true;
if (!preserveClientOrder || nextOrder.equals(order)) {
handleViolationResult();
}
}
@Override
protected void onFunctionalException(final FunctionalException caught) {
CommandWrapper.this.caught = caught;
ready = true;
if (!preserveClientOrder || nextOrder.equals(order)) {
handleFunctionalException();
}
}
}, loadables);
}
// Result type "1".
private void handleSuccessResult() {
try {
commandResultHandler.onCommandSuccess(result);
} finally {
handleExecutionComplete();
handleNextOrder(1);
}
}
// Result type "2".
private void handleFailureResult() {
try {
commandResultHandler.onCommandFailure(caught);
} finally {
handleExecutionComplete();
handleNextOrder(2);
}
}
// Result type "3".
private void handleViolationResult() {
try {
commandResultHandler.onCommandViolation((ValidationException) caught);
} finally {
handleExecutionComplete();
handleNextOrder(3);
}
}
// Result type "4".
private void handleFunctionalException() {
try {
commandResultHandler.onFunctionalException((FunctionalException) caught);
} finally {
handleExecutionComplete();
handleNextOrder(4);
}
}
private void handleNextOrder(final int resultType) {
if (!preserveClientOrder || nextOrder == null) {
return;
}
nextOrder++;
if (nextOrder < commands.size() && commands.get(nextOrder).ready) {
switch (resultType) {
case 1:
commands.get(nextOrder).handleSuccessResult();
break;
case 2:
commands.get(nextOrder).handleFailureResult();
break;
case 3:
commands.get(nextOrder).handleViolationResult();
break;
case 4:
commands.get(nextOrder).handleFunctionalException();
break;
default:
break;
}
}
}
}
private final DispatchAsync dispatch;
private final ArrayList<CommandWrapper<?, ?>> commands;
private int commandsCount;
private boolean running;
private final boolean preserveClientOrder;
private Integer nextOrder;
/**
* Initializes a new {@code DispatchQueue}.
*
* @param dispatch
* The dispatch service (required).
*/
public DispatchQueue(final DispatchAsync dispatch) {
this(dispatch, false);
}
/**
* Initializes a new {@code DispatchQueue}.
*
* @param dispatch
* The dispatch service (required).
* @param preserveClientOrder
* Set to {@code true} to preserve <b>client</b> callback execution order (Note that <b>server</b> command
* execution order cannot be guaranteed).<br>
* Set to {@code false} to ignore order.
*/
public DispatchQueue(final DispatchAsync dispatch, final boolean preserveClientOrder) {
this.dispatch = dispatch;
this.commands = new ArrayList<CommandWrapper<?, ?>>(); // Should be ordered.
this.preserveClientOrder = preserveClientOrder;
}
/**
* Adds a new {@code command} with its {@code commandResultHandler} to the current queue.
*
* @param command
* The dispatch command.
* @param commandResultHandler
* The dispatch command result handler.
* @param loadables
* (optional) The {@code Loadable} elements (may be {@code null}).
* @return the current queue.
*/
public final <C extends Command<R>, R extends Result> DispatchQueue add(final C command, final CommandResultHandler<R> commandResultHandler,
final Loadable... loadables) {
return add(command, commandResultHandler, ClientUtils.isEmpty(loadables) ? null : Arrays.asList(loadables));
}
/**
* Adds a new {@code command} with its {@code commandResultHandler} to the current queue.
*
* @param command
* The dispatch command.
* @param commandResultHandler
* The dispatch command result handler.
* @param loadables
* (optional) The {@code Loadable} elements collection (may be {@code null}).
* @return the current queue.
*/
public final <C extends Command<R>, R extends Result> DispatchQueue add(final C command, final CommandResultHandler<R> commandResultHandler,
final Collection<Loadable> loadables) {
if (command == null || commandResultHandler == null || running) {
return this;
}
commands.add(new CommandWrapper<C, R>(commands.size(), command, commandResultHandler, loadables));
return this;
}
/**
* Starts the queue commands (no new command should be added after this call).
*/
public final void start() {
if (running) {
return;
}
running = true;
commandsCount = commands.size();
if (commandsCount == 0) {
commandsCount++;
handleExecutionComplete();
return;
}
nextOrder = commands.get(0).order;
for (final CommandWrapper<?, ?> commandResultHandler : commands) {
commandResultHandler.execute();
}
return;
}
/**
* Handled command execution complete.
*/
private final void handleExecutionComplete() {
commandsCount--;
if (commandsCount != 0) {
return;
}
// "try/finally" in case custom implementation fails.
try {
onComplete();
} finally {
running = false;
nextOrder = null;
}
}
/**
* Method executed once <b>all</b> executed commands are complete (success or error).
* <em>Can be overridden by custom implementation (default implementation does nothing).</em>
*/
protected void onComplete() {
// Default implementation does nothing.
}
}