/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.core.transaction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreEList;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.command.SetCommand;
import org.eclipse.emf.edit.provider.ChangeNotifier;
import org.teiid.core.designer.ModelerCoreException;
import org.teiid.core.designer.ModelerCoreRuntimeException;
import org.teiid.core.designer.TeiidDesignerRuntimeException;
import org.teiid.core.designer.util.CoreArgCheck;
import org.teiid.core.designer.util.CoreStringUtil;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.core.container.Container;
import org.teiid.designer.core.resource.EmfResourceSet;
import org.teiid.designer.core.util.ExternalResourceImportsHelper;
import org.teiid.designer.core.util.ProcessedNotificationResult;
/**
* @author lphillips
* @since 8.0
*/
public class UnitOfWorkImpl implements UnitOfWork {
private final TxnNotificationFilter filter;
private final Set resourcesChanged;
private final Collection removedEObjects;
private Object id;
private int state;
private CompoundCommand txnCommand;
private Container container;
private boolean significant;
private boolean isUndoable;
private String description;
private Object source;
private boolean overrideRollback;
private final Set processedNotificationResults;
// ==================================================================================
// C O N S T R U C T O R S
// ==================================================================================
/**
* Constructor for UnitOfWorkImpl.
*/
public UnitOfWorkImpl( final ResourceSet resources ) {
this(resources, Integer.MAX_VALUE);
}
/**
* Constructor for UnitOfWorkImpl.
*
* @param The resourceSet to use for editing domain creation
* @param The max wait time for commit and rollback blocking.
*/
public UnitOfWorkImpl( final ResourceSet resources,
final int waitTimeouts ) {
if (resources == null) {
final String msg = ModelerCore.Util.getString("UnitOfWorkImpl.The_ResourceSet_reference_may_not_be_null"); //$NON-NLS-1$
throw new IllegalArgumentException(msg);
}
// if ( !(resources instanceof EmfResourceSet )) {
// final String msg = ModelerCore.Util.getString("UnitOfWorkImpl.The_supplied_ResourceSet_must_be_an_implementation_of_EmfResourceSet"); //$NON-NLS-1$
// throw new IllegalArgumentException(msg);
// }
this.resourcesChanged = new HashSet();
this.removedEObjects = new HashSet();
if (resources instanceof EmfResourceSet) {
this.container = ((EmfResourceSet)resources).getContainer();
} else {
try {
this.container = ModelerCore.getModelContainer();
} catch (CoreException err) {
throw new TeiidDesignerRuntimeException(err);
}
}
this.filter = new TxnNotificationFilter(resources);
setState(TransactionStateConstants.UNINITIALIZED);
this.isUndoable = true;
processedNotificationResults = new HashSet();
}
// ==================================================================================
// P U B L I C M E T H O D S
// ==================================================================================
public Collection getRemovedEObjects() {
return new HashSet(this.removedEObjects);
}
/**
* Begin the transaction.
*/
@Override
public void begin() {
if (isStarted()) {
CoreArgCheck.isTrue(false, ModelerCore.Util.getString("UnitOfWorkImpl.Transaction_already_started_1")); //$NON-NLS-1$
}
if (state != TransactionStateConstants.COMPLETE && state != TransactionStateConstants.UNINITIALIZED
&& state != TransactionStateConstants.FAILED) {
throw new ModelerCoreRuntimeException(
ModelerCore.Util.getString("UnitOfWorkImpl.Invalid_transaction_state_prior_to_begin", TransactionStateConstants.getDisplayValue(this.state))); //$NON-NLS-1$
}
this.description = null;
this.id = UnitOfWorkProviderImpl.getIdFactory().create();
this.significant = true;
this.isUndoable = true;
this.filter.clear();
this.resourcesChanged.clear();
this.removedEObjects.clear();
setState(TransactionStateConstants.STARTED);
overrideRollback = false;
this.processedNotificationResults.clear();
}
/**
* @return
*/
@Override
public Object getId() {
return this.id;
}
/**
* Call back method for the EmfAdapter to notify the transaction of an event notification Add the notification to the
* EventStack to be processed on a commit.
*
* @param notification
*/
@Override
public void processNotification( final Notification notification ) throws ModelerCoreException {
if (!isStarted() && !isRollingBack() && !isCommitting()) {
int preState = getState();
setState(TransactionStateConstants.FAILED);
throw new ModelerCoreException(
ModelerCore.Util.getString("UnitOfWorkImpl.Invalid_transaction_state_prior_to_processing_notification", TransactionStateConstants.getDisplayValue(preState))); //$NON-NLS-1$
}
CoreArgCheck.isNotNull(notification);
// keep track of resources whose modified property has been modified.
if (notification.getNotifier() instanceof Resource && notification.getEventType() == Notification.SET
&& notification.getFeatureID(Resource.class) == Resource.RESOURCE__IS_MODIFIED) {
// add if resource has been modified, remove if it is not modified
if (notification.getNewBooleanValue()) {
this.resourcesChanged.add(notification.getNotifier());
} else {
this.resourcesChanged.remove(notification.getNotifier());
}
}
this.filter.addNotification(notification);
}
/**
* @return the UoW source attribute
*/
@Override
public Object getSource() {
return this.source;
}
/**
* Set the UoW source attribute
*
* @param object
*/
@Override
public void setSource( Object object ) {
this.source = object;
}
/**
* Process all the notifications on the event queue, flush the command stack and create the undoable edit
*/
@Override
public void commit() throws ModelerCoreException {
boolean success = false;
try {
if (!isStarted() && !isRollingBack()) {
int preState = getState();
throw new ModelerCoreException(
ModelerCore.Util.getString("UnitOfWorkImpl.Invalid_transaction_state_prior_to_commit", TransactionStateConstants.getDisplayValue(preState))); //$NON-NLS-1$
}
postProcessNotificationResults();
setState(TransactionStateConstants.COMMITTING);
// Create and fire the Undoable Edit event
if (!overrideRollback && this.txnCommand != null && this.isUndoable) {
final UnitOfWorkProviderImpl uowp = (UnitOfWorkProviderImpl)this.container.getEmfTransactionProvider();
final Undoable undoable = new UndoableImpl(this.container, this.txnCommand, new ArrayList(this.resourcesChanged),
this.id, this.source);
undoable.setSignificant(this.significant);
if (this.description != null) {
undoable.setDescription(this.description);
}
this.txnCommand = null;
uowp.processUndoable(undoable);
}
success = true;
} catch (Throwable e) {
success = false;
setState(TransactionStateConstants.FAILED);
rollback();
throw new ModelerCoreException(e, ModelerCore.Util.getString("UnitOfWorkImpl.Error_committing_transaction")); //$NON-NLS-1$
} finally {
if (success) {
setState(TransactionStateConstants.COMPLETE);
try {
// Get the filtered and compressed set of notifications to fire
final List notifications = this.filter.getSourcedNotifications(this.source);
if (ModelerCore.DEBUG_NOTIFICATIONS) {
helpPrintFilterResults(notifications, this.description);
}
if (!notifications.isEmpty()) {
final ChangeNotifier notifier = this.container.getChangeNotifier();
try {
for (Iterator i = notifications.iterator(); i.hasNext();) {
Notification n = (Notification)i.next();
notifier.fireNotifyChanged(n);
}
} catch (Throwable e1) {
ModelerCore.Util.log(IStatus.ERROR,
e1,
ModelerCore.Util.getString("UnitOfWorkImpl.Error_processing_notification_1")); //$NON-NLS-1$
}
}
} catch (Throwable e1) {
ModelerCore.Util.log(IStatus.ERROR,
e1,
ModelerCore.Util.getString("UnitOfWorkImpl.Error_processing_notifications____1") + e1.getMessage()); //$NON-NLS-1$
}
} else {
setState(TransactionStateConstants.FAILED);
}
// Clear the event manager
this.filter.clear();
// remove this UOW from the list of current UOWs
cleanup();
}
}
public void helpPrintFilterResults( final List sns,
final String description ) {
if (sns == null || sns.isEmpty()) {
return;
}
if (!CoreStringUtil.isEmpty(description)) {
System.out.println("\n" + description); //$NON-NLS-1$
System.out.println("TxnNotificationFilter result:"); //$NON-NLS-1$
} else {
System.out.println("\nTxnNotificationFilter result:"); //$NON-NLS-1$
}
for (Iterator i = sns.iterator(); i.hasNext();) {
SourcedNotificationImpl sn = (SourcedNotificationImpl)i.next();
System.out.println(sn.getNotifier());
System.out.println(" primary notification: " + sn.getPrimaryNotification()); //$NON-NLS-1$
for (Iterator j = sn.getNotifications().iterator(); j.hasNext();) {
Notification n = (Notification)j.next();
System.out.println(" notification: " + n);//$NON-NLS-1$
}
}
}
/**
* Setter for description attribute. Used when creating the undoable.
*
* @param description
*/
@Override
public void setDescription( String description ) {
this.description = description;
}
/**
* Undo all the txnCommand, flush the command stack and event queue and create and undoable edit
*
* @see MtkTransaction#rollback()
*/
@Override
public void rollback() {
boolean success = false;
try {
if (!isStarted() && !isFailed()) {
int preState = getState();
throw new ModelerCoreException(
ModelerCore.Util.getString("UnitOfWorkImpl.Invalid_transaction_state_prior_to_rollback", TransactionStateConstants.getDisplayValue(preState))); //$NON-NLS-1$
}
setState(TransactionStateConstants.ROLLING_BACK);
if (!overrideRollback && this.txnCommand != null && !this.txnCommand.isEmpty()) {
// Capture the txn cmd info and reset the attribute as this txn is reused for the rollback.
// If you don't you will get concurrent modifications when rolling back this txnCommand as
// you add to it via the rollback.
CompoundCommand tmp = this.txnCommand;
this.txnCommand = null;
tmp.undo();
}
this.filter.clear();
this.txnCommand = null;
// remove this UOW from the list of current UOWs
cleanup();
success = true;
} catch (Exception e) {
success = false;
ModelerCore.Util.log(IStatus.ERROR, e, ModelerCore.Util.getString("UnitOfWorkImpl.Error_rolling_back_transaction")); //$NON-NLS-1$
} finally {
if (success) {
setState(TransactionStateConstants.COMPLETE);
} else {
setState(TransactionStateConstants.FAILED);
}
}
}
/**
* Pass through to the editing domain / command stack to execute the command
*
* @return true if the command was executed, or false if it could not be executed
* @see UnitOfWork#executeCommand(Command)
*/
@Override
public boolean executeCommand( final Command command ) throws ModelerCoreException {
if (!isStarted() && !isRollingBack()) {
int preState = getState();
setState(TransactionStateConstants.FAILED);
throw new ModelerCoreException(
ModelerCore.Util.getString("UnitOfWorkImpl.Transaction_must_be_started_before_you_can_execute_commands", TransactionStateConstants.getDisplayValue(preState))); //$NON-NLS-1$
}
if (!command.canExecute()) {
final String msg = ModelerCore.Util.getString("UnitOfWorkImpl.Command_object_not_executable", command); //$NON-NLS-1$
if (ModelerCore.DEBUG) {
ModelerCore.Util.log(IStatus.ERROR, msg);
}
command.dispose();
throw new ModelerCoreException(msg);
}
// Ensure txnCommand != null
if (!overrideRollback) {
if (this.txnCommand == null) {
this.txnCommand = new CompoundCommand(command.getLabel());
}
// Add new Command to txnCommand
this.txnCommand.append(command);
}
try {
command.execute();
} catch (RuntimeException exception) {
final Object[] params = new Object[] {command, exception.getMessage()};
final String msg = ModelerCore.Util.getString("UnitOfWorkImpl.error_executing_command", params); //$NON-NLS-1$
command.dispose();
throw new ModelerCoreException(exception, msg);
}
updateRemovedEObjects(command);
return true;
}
/**
* @see MtkTransaction#isStarted()
*/
@Override
public boolean isStarted() {
return getState() == TransactionStateConstants.STARTED;
}
/**
* @see MtkTransaction#isCommitting()
*/
@Override
public boolean isCommitting() {
return getState() == TransactionStateConstants.COMMITTING;
}
/**
* @see MtkTransaction#isCommitting()
*/
@Override
public boolean isComplete() {
return getState() == TransactionStateConstants.COMPLETE;
}
/**
* @see MtkTransaction#isRollingBack()
*/
@Override
public boolean isRollingBack() {
return getState() == TransactionStateConstants.ROLLING_BACK;
}
/**
* @see MtkTransaction#isFailed()
*/
@Override
public boolean isFailed() {
return getState() == TransactionStateConstants.FAILED;
}
@Override
public boolean requiresStart() {
switch (this.state) {
case TransactionStateConstants.COMMITTING:
return false;
case TransactionStateConstants.STARTED:
return false;
case TransactionStateConstants.ROLLING_BACK:
return false;
default:
return true;
}
}
/**
* Sets this significant flag which is passed to the undoable edit;
*
* @param b
*/
@Override
public void setSignificant( boolean b ) throws ModelerCoreException {
if (!isStarted()) {
int preState = getState();
throw new ModelerCoreException(
ModelerCore.Util.getString("UnitOfWorkImpl.Invalid_Unit_of_Work_State___May_only_set_isSignificant_on_started_Unit_of_Work_3", TransactionStateConstants.getDisplayValue(preState))); //$NON-NLS-1$
}
this.significant = b;
}
/**
* @return the isUndoable flag
*/
@Override
public boolean isUndoable() {
return this.isUndoable;
}
/**
* Set the isUndoable flag
*
* @param b
*/
@Override
public void setUndoable( boolean b ) {
this.isUndoable = b;
}
/**
* overrideRollback is a class variable which, when true, suppresses some of the txn commands needed by the UNDO framework.
* See setOverrideRollback(boolean b)
*
* @return
* @since 5.0.2
*/
public boolean isOverrideRollback() {
return this.overrideRollback;
}
/**
* Method to override the rollback functionality of this class and place it on the Source of the transaction. Even if a txn
* was NOT undoable, we were still treating it under the hood as undoable and this was potentially causing memory issues for
* txn's where a large amount of work was being done (i.e. New Model Wizards, XML Document model builder, etc.) If set to
* TRUE, then the txn Command will NOT be created and txn's will NOT be cached. This is implemented to allow actions like
* NewModelWizard to NOT care about the undoablity of any of the add/remove/change commands created when building the model.
*
* @return
* @since 5.0.2
*/
public void setOverrideRollback( boolean b ) {
this.overrideRollback = b;
}
@Override
public String toString() {
if (this.description == null) {
return this.getClass().getName() + " : " + TransactionStateConstants.getDisplayValue(getState()); //$NON-NLS-1$
}
return this.description + " : " + TransactionStateConstants.getDisplayValue(getState()); //$NON-NLS-1$
}
/**
* Returns the state.
*
* @return int
*/
public int getState() {
return this.state;
}
/**
* Method add a new ProcessedNotificationResult for post-commit processing. Method looks for existing result referencing the
* same resource, then appends the dereferenced resources to it's list, else it adds a new result to the cache for use in
* final processing.
*
* @return
* @since 5.0.2
*/
public void addProcessedNotificationResult( ProcessedNotificationResult result ) {
if (processedNotificationResults.isEmpty()) {
processedNotificationResults.add(result);
} else {
boolean foundMatchingResource = false;
ProcessedNotificationResult nextResult = null;
for (Iterator iter = processedNotificationResults.iterator(); iter.hasNext();) {
nextResult = (ProcessedNotificationResult)iter.next();
if (nextResult.getTargetResource() == result.getTargetResource()) {
foundMatchingResource = true;
// Add the externalResources to the existing result
nextResult.addDereferencedResources(result.getDereferencedResources());
}
if (foundMatchingResource) {
break;
}
}
if (!foundMatchingResource) {
processedNotificationResults.add(result);
}
}
}
private void postProcessNotificationResults() {
if (!processedNotificationResults.isEmpty()) {
ExternalResourceImportsHelper.processNotificationResults(processedNotificationResults);
}
// Always clear
processedNotificationResults.clear();
}
// ==================================================================================
// P R I V A T E M E T H O D S
// ==================================================================================
/**
* Sets the state.
*
* @param state The state to set
*/
private void setState( int state ) {
this.state = state;
if (ModelerCore.DEBUG_TRANSACTION) {
switch (state) {
case TransactionStateConstants.FAILED:
ModelerCore.Util.log(IStatus.INFO, ModelerCore.Util.getString("UnitOfWorkImpl.Setting_state_to_FAILED_12")); //$NON-NLS-1$
Thread.dumpStack();
break;
case TransactionStateConstants.STARTED:
ModelerCore.Util.log(IStatus.INFO, ModelerCore.Util.getString("UnitOfWorkImpl.Setting_state_to_STARTED_13")); //$NON-NLS-1$
break;
case TransactionStateConstants.COMPLETE:
ModelerCore.Util.log(IStatus.INFO, ModelerCore.Util.getString("UnitOfWorkImpl.Setting_state_to_COMPLETE_14")); //$NON-NLS-1$
break;
case TransactionStateConstants.COMMITTING:
ModelerCore.Util.log(IStatus.INFO,
ModelerCore.Util.getString("UnitOfWorkImpl.Setting_state_to_COMMITTING_15")); //$NON-NLS-1$
break;
case TransactionStateConstants.ROLLING_BACK:
ModelerCore.Util.log(IStatus.INFO,
ModelerCore.Util.getString("UnitOfWorkImpl.Setting_state_to_ROLLING_BACK_16")); //$NON-NLS-1$
break;
default:
break;
}
}
}
/**
* Try to remove this UOW from the UOW Provider and cleanup instance variables.
*/
private void cleanup() {
this.id = null;
this.significant = true;
this.isUndoable = true;
this.source = null;
this.removedEObjects.clear();
this.resourcesChanged.clear();
this.filter.clear();
this.container.getEmfTransactionProvider().cleanup(Thread.currentThread());
}
private void updateRemovedEObjects( final Command cmd ) {
if (cmd instanceof RemoveCommand) {
final RemoveCommand remove = (RemoveCommand)cmd;
final Collection list = remove.getCollection();
final EStructuralFeature sf = remove.getFeature();
if (sf != null && sf instanceof EReference) {
final EReference ref = (EReference)sf;
if (ref.isContainment()) {
this.removedEObjects.addAll(list);
} else if (ref.isContainer()) {
this.removedEObjects.addAll(list);
}
} else if (list != null && !list.isEmpty()) {
final EList ownerList = remove.getOwnerList();
if (ownerList instanceof EcoreEList) {
final EcoreEList eList = (EcoreEList)ownerList;
final EStructuralFeature listSf = eList.getEStructuralFeature();
final Iterator vals = list.iterator();
while (vals.hasNext()) {
final Object next = vals.next();
if (next instanceof EObject) {
final EStructuralFeature ownerSf = ((EObject)next).eContainmentFeature();
if (ownerSf == listSf && ownerSf != null) {
this.removedEObjects.add(next);
}
}
}
}
}
} else if (cmd instanceof SetCommand) {
final SetCommand set = (SetCommand)cmd;
final EStructuralFeature sf = set.getFeature();
if (sf instanceof EReference) {
final EReference ref = (EReference)sf;
if (ref.isContainment() || ref.isContainer()) {
Object oldVal = set.getOldValue();
if (oldVal != null && set.getValue() == null) {
if (oldVal instanceof Collection) {
this.removedEObjects.addAll((Collection)oldVal);
} else if (oldVal instanceof EObject) {
this.removedEObjects.add(oldVal);
}
} else if (oldVal == null && set.getValue() != null) {
final Object val = set.getValue();
if (val instanceof Collection) {
this.removedEObjects.removeAll((Collection)val);
} else if (val instanceof EObject) {
this.removedEObjects.remove(val);
}
}
} else if (ref.isContainer()) {
Object oldVal = set.getOldValue();
if (oldVal != null && set.getValue() == null) {
this.removedEObjects.add(set.getOwner());
} else if (oldVal == null && set.getValue() != null) {
this.removedEObjects.remove(set.getOwner());
}
}
}
} else if (cmd instanceof AddCommand) {
this.removedEObjects.removeAll(((AddCommand)cmd).getCollection());
}
}
}