/*******************************************************************************
* Copyright (c) 2009 Fraunhofer IWU and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Fraunhofer IWU - initial API and implementation
*******************************************************************************/
package net.enilink.komma.edit.command;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import net.enilink.komma.common.command.AbstractCommand;
import net.enilink.komma.common.command.AbstractCommand.INoChangeRecording;
import net.enilink.komma.common.command.CommandResult;
import net.enilink.komma.common.command.ICommand;
import net.enilink.komma.core.IEntityManager;
import net.enilink.komma.core.ITransaction;
import net.enilink.komma.edit.domain.IEditingDomain;
import net.enilink.komma.model.IModel;
import net.enilink.komma.model.change.IChangeDescription;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
/**
* A partial command implementation that records the changes made by a
* subclass's direct manipulation of objects via the metamodel's API. This
* simplifies the programming model for complex commands (not requiring
* composition of set/add/remove commands) while still providing automatic
* undo/redo support.
* <p>
* Subclasses are simply required to implement the {@link #doExecute()} method
* to make the desired changes to the model. Note that, because changes are
* recorded for automatic undo/redo, the concrete command must not make any
* changes that cannot be recorded (unless it does not matter that they will not
* be undone).
* </p>
*
*/
public class RecordingWrapperCommand extends AbstractCommand implements
INoChangeRecording {
private IChangeDescription change;
private ICommand command;
private IEditingDomain domain;
/**
* Initializes me with the editing domain in which I am to be executed.
*
* @param domain
* my domain
*/
public RecordingWrapperCommand(IEditingDomain domain, ICommand command) {
this.domain = domain;
this.command = command;
}
private boolean canApplyChange() {
return change == null || change.canUndo();
}
/**
* I can be redone if I successfully recorded the changes that I executed.
* Subclasses would not normally need to override this method.
*/
public boolean canRedo() {
return canApplyChange() || command.canRedo();
}
/**
* I can be undone if I successfully recorded the changes that I executed.
* Subclasses would not normally need to override this method.
*/
@Override
public boolean canUndo() {
return canApplyChange() || command.canUndo();
}
/**
* Extends the inherited implementation by disposing my change description,
* if any.
*/
@Override
public void dispose() {
super.dispose();
change = null;
}
/**
* Implements the execution with automatic recording of undo information.
* Delegates the actual model changes to the subclass's implementation of
* the {@link #doExecute()} method.
*
* @see #doExecute()
*/
@Override
public CommandResult doExecuteWithResult(IProgressMonitor progressMonitor,
IAdaptable info) throws ExecutionException {
IEditingDomain.Internal internalDomain = (IEditingDomain.Internal) domain;
Collection<?> affectedModels = new HashSet<Object>(
command.getAffectedResources(IModel.class));
List<ITransaction> startedTransactions = new ArrayList<ITransaction>(
affectedModels.size());
for (Object model : affectedModels) {
IEntityManager modelManager = ((IModel) model).getManager();
ITransaction transaction = modelManager.getTransaction();
if (!transaction.isActive()) {
// TODO check if next Sesame version supports nested
// transactions
// TODO check if transaction can be executed in READ_UNCOMMITED
// mode
// transaction.begin();
// startedTransactions.add(transaction);
}
}
boolean rollback = true;
try {
internalDomain.getChangeRecorder().beginRecording();
IStatus status = command.execute(progressMonitor, info);
if (status.isOK()) {
rollback = false;
for (ITransaction transaction : startedTransactions) {
if (transaction.isActive()) {
transaction.commit();
}
}
}
return command.getCommandResult();
} finally {
change = internalDomain.getChangeRecorder().endRecording();
if (rollback) {
for (ITransaction transaction : startedTransactions) {
if (transaction.isActive()) {
transaction.rollback();
}
}
}
}
}
/**
* Redoes the changes that I recorded. Subclasses would not normally need to
* override this method.
*
* @throws IllegalStateException
* if I am not {@linkplain #canRedo() redoable}
*
* @see #canRedo()
*/
@Override
public CommandResult doRedoWithResult(IProgressMonitor progressMonitor,
IAdaptable info) throws ExecutionException {
IStatus status = command.redo(progressMonitor, info);
if (status.isOK() && change != null) {
status = change.redo(progressMonitor, info);
}
return CommandResult.newCommandResult(status, command
.getCommandResult().getReturnValue());
}
/**
* Undoes the changes that I recorded. Subclasses would not normally need to
* override this method.
*
* @throws IllegalStateException
* if I am not {@linkplain #canUndo() undoable}
*
* @see #canUndo()
*/
@Override
public CommandResult doUndoWithResult(IProgressMonitor progressMonitor,
IAdaptable info) throws ExecutionException {
IStatus status = command.undo(progressMonitor, info);
if (status.isOK() && change != null) {
status = change.undo(progressMonitor, info);
}
return CommandResult.newCommandResult(status, command
.getCommandResult().getReturnValue());
}
@Override
public Collection<?> getAffectedObjects() {
return command.getAffectedObjects();
}
@Override
public Collection<?> getAffectedResources(Object type) {
return command.getAffectedResources(type);
}
@Override
public String getDescription() {
return command.getDescription();
}
@Override
public String getLabel() {
return command.getLabel();
}
/**
* Subclasses should override this if they have more preparation to do. By
* default, the result is just <code>true</code>.
*/
@Override
protected boolean prepare() {
return command.canExecute();
}
}