package com.sap.runlet.abstractinterpreter;
import java.util.Iterator;
import org.eclipse.emf.ecore.EObject;
import com.sap.runlet.abstractinterpreter.objects.EntityObject;
import com.sap.runlet.abstractinterpreter.objects.Link;
import com.sap.runlet.abstractinterpreter.repository.ChangeSet;
import com.sap.runlet.abstractinterpreter.repository.ChangeSetImpl;
import com.sap.runlet.abstractinterpreter.repository.EntityCreation;
import com.sap.runlet.abstractinterpreter.repository.EntityDeletion;
import com.sap.runlet.abstractinterpreter.repository.MutableChangeSet;
import com.sap.runlet.abstractinterpreter.repository.Repository;
import com.sap.runlet.abstractinterpreter.repository.RepositoryChange;
import com.sap.runlet.abstractinterpreter.repository.RepositoryObject;
import com.sap.runlet.abstractinterpreter.repository.Snapshot;
import com.sap.runlet.abstractinterpreter.repository.SnapshotIdentifier;
import com.sap.runlet.abstractinterpreter.util.ModelAdapter;
/**
* A transaction buffer is a change set, thus keeping track of object and link creations and
* deletions and additionally maintains a {@link Snapshot} to which the changes can be applied. It
* starts out with a <tt>null</tt> snapshot, meaning the changes may be applied to any snapshot.
* As soon as a change is announced to this transaction buffer where the entity/link affected by the
* change pertains to a specific snapshot (and not <tt>null</tt>), this will set this buffer's
* snapshot. If this buffer's snapshot is already set to a non-<tt>null</tt> one, only changes
* to entities/links with a <tt>null</tt> snapshot or the same snapshot as that of this buffer
* can be accounced. Changes based on different snapshots will lead to an assertion error.
*
* FIXME: not Thread safe
*
* @author D046040
* @author Axel Uhl (D043530)
*
*/
public class TransactionBuffer<LinkMetaObject extends EObject, LinkEndMetaObject extends EObject,
MetaClass extends EObject, TypeUsage extends EObject, ClassUsage extends TypeUsage>
implements ChangeSet<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> {
/**
* Identifies the snapshot from which the {@link RepositoryObjetc}s that are deleted and
* created by the changes maintained by this buffer originate. If the buffer only contains
* creations, this identifier may not yet be bound to a particular {@link Snapshot}. In this
* case the identifier may be bound during the {@link #commit(Repository)} call. Otherwise,
* when committing, a successor of the snapshot to which this identifier is bound will be
* created. The {@link #commit(Repository)} method will then update this tag so that it
* points to the snapshot produced by the commit. If the snapshot produced by the commit
* is a direct successor of the snapshot identified by this tag before the commit, or if
* this identifier was not yet bound to a particular snapshot, all instances in memory
* are considered still to be consistent after the tag has moved to the successor snapshot.<p>
*
* If during the {@link #commit(Repository)} call the tag moves to a non-direct successor,
* this means that an auto-merge has taken place which potentially invalidates
* {@link RepositoryObject}s that are marked with this identifier. The {@link ChangeSet}s
* in between are then analyzed at the end of {@link #commit(Repository)} and lead to
* the necessary invalidations of in-memory content.
*/
private SnapshotIdentifier updatingTag;
private MutableChangeSet<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> changeSet;
private final ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter;
public TransactionBuffer(ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter) {
this.modelAdapter = modelAdapter;
changeSet = new ChangeSetImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(modelAdapter);
}
private ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getModelAdapter() {
return modelAdapter;
}
/**
* Records the entity for creation in the persistent repository and sets its
* {@link RepositoryObject#setOrigin(SnapshotIdentifier) snapshot identifier} to
* {@link #getUpdatingTag()} which will be initialized to the <tt>defaultSnapshot</tt> if
* <tt>null</tt> at the beginning of this call.
* @param at for ordered associations, specify the position at which the link was added
*/
public void entityCreated(EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> entity) {
makePersistent(entity);
changeSet.entityCreated(entity);
}
private void makePersistent(RepositoryObject linkOrEntity) {
if (!linkOrEntity.isPersistent()) {
validateAndUpdateBaseSnapshot(linkOrEntity);
linkOrEntity.setPersistent(true);
}
}
/**
* Records the link for creation in the persistent repository and sets its
* {@link RepositoryObject#setOrigin(SnapshotIdentifier) snapshot identifier} to
* {@link #getUpdatingTag()} which will be initialized to the <tt>defaultSnapshot</tt> if
* <tt>null</tt> at the beginning of this call.
*
* @param at for ordered associations, specify the position at which the link was added.
* <tt>null</tt> or <tt>-1</tt> are not allowed if <tt>link.isOrdered()</tt>. The real
* insert position must be specified instead which then equals the size of the ordered
* collection holding the links when viewed from the unordered end.
*/
public void linkCreated(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link, Integer at) {
makePersistent(link);
changeSet.linkCreated(link, at);
}
/**
* Records the entity for deletion and sets its
* {@link RepositoryObject#setOrigin(SnapshotIdentifier) snapshot identifier} to <tt>null</tt>.
*/
public void entityDeleted(EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> entity) {
makeTransient(entity);
changeSet.entityDeleted(entity);
}
/**
* Records the link for deletion and sets its
* {@link RepositoryObject#setOrigin(SnapshotIdentifier) snapshot identifier} to <tt>null</tt>.
* @param at for links of ordered associations, specify the position from which the link was deleted
*/
public void linkDeleted(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link, Integer at) {
makeTransient(link);
changeSet.linkDeleted(link, at);
}
private void makeTransient(final RepositoryObject entityOrLink) {
if (entityOrLink.isPersistent()) {
validateAndUpdateBaseSnapshot(entityOrLink);
entityOrLink.setPersistent(false);
}
}
/**
* Should this buffer's {@link #getUpdatingTag()} be <tt>null</tt>, it will be initialized to
* the <tt>object</tt>'s {@link RepositoryObject#getOrigin() snapshot identifier}. The
* <tt>object</tt>'s snapshot identifier has to match with this buffer's
* {@link #getUpdatingTag()} after the initialization described above. If it does not, an
* exception is thrown.
*/
public void validateAndUpdateBaseSnapshot(RepositoryObject object) {
if (getUpdatingTag() == null) {
setUpdatingTag(object.getOrigin());
}
assert object.getOrigin().equals(getUpdatingTag());
}
/**
* Commits open changes to the {@link Repository}. Returns <code>null</code> if no open changes exist.
*
* @param repository
* the storage component into which to commit the changes in the current {@link #changeSet}.
* See also {@link Repository#apply(ChangeSet, SnapshotIdentifier)}
* @return the snapshot created by committing the change set
*/
public Snapshot commit(Repository<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> repository) {
Snapshot result = null;
if (!changeSet.isEmpty()) {
result = repository.apply(changeSet, getUpdatingTag());
changeSet = new ChangeSetImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(getModelAdapter());
getUpdatingTag().unresolve(); // "unresolve" snapshot identifier to let it resolve
// itself again next time around
setUpdatingTag(null);
}
return result;
}
public SnapshotIdentifier getUpdatingTag() {
return updatingTag;
}
public void setUpdatingTag(SnapshotIdentifier updatingTag) {
this.updatingTag = updatingTag;
}
@Override
public boolean conflictsWith(ChangeSet<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> other) {
return changeSet.conflictsWith(other);
}
@Override
public Iterator<RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> getChanges() {
return changeSet.getChanges();
}
@Override
public boolean isEmpty() {
return changeSet.isEmpty();
}
/**
* Undoes all changes recorded in this buffer by making those objects/links that were recorded
* as persistent/created as transient again by clearing their
* {@link RepositoryObject#setOrigin(SnapshotIdentifier) origin snapshot} and for those
* objects/link that were recorded as deleted, re-establish them as persistent objects
* by setting their {@link RepositoryObject#setOrigin(SnapshotIdentifier) original snapshot}
* back to the identifier that this transaction buffer has as its {@link #getUpdatingTag()}.
* Finally, all records in this transaction buffer are cleared, asserting {@link #isEmpty()}.
*/
public void rollback() {
for (Iterator<RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> i = getChanges(); i.hasNext(); ) {
RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> eo = i.next();
if (eo.isCreate()) {
eo.getObject().setPersistent(false);
} else {
eo.getObject().setPersistent(true);
}
}
changeSet.clear();
}
@Override
public Iterator<EntityCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> getEntityCreations() {
return changeSet.getEntityCreations();
}
@Override
public Iterator<EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> getEntityDeletions() {
return changeSet.getEntityDeletions();
}
//FIXME: delete!!!
public ChangeSetImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getChangeSetImpl() {
return (ChangeSetImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) changeSet;
}
@Override
public Iterator<RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> iterator() {
return changeSet.iterator();
}
@Override
public int totalSize() {
return changeSet.totalSize();
}
}