/*
* Copyright (c) 2009, SQL Power Group Inc.
*
* This file is part of SQL Power Library.
*
* SQL Power Library 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.
*
* SQL Power Library 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/>.
*/
package ca.sqlpower.dao;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.log4j.Logger;
import ca.sqlpower.dao.SPPersister.DataType;
import ca.sqlpower.dao.helper.PersisterHelperFinder;
import ca.sqlpower.dao.helper.SPPersisterHelper;
import ca.sqlpower.dao.session.SessionPersisterSuperConverter;
import ca.sqlpower.object.SPChildEvent;
import ca.sqlpower.object.SPListener;
import ca.sqlpower.object.SPObject;
import ca.sqlpower.sqlobject.SQLObject;
import ca.sqlpower.util.HashTreeSetMultimap;
import ca.sqlpower.util.SQLPowerUtils;
import ca.sqlpower.util.TransactionEvent;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.SortedSetMultimap;
/**
* This generic listener will use the persister helper factory given to it to
* make persist calls when events are fired on the object being listened to.
*/
public class SPPersisterListener implements SPListener {
private static final String PROPERTY_CHANGED_MSG = "Start event: propertyChange";
private static final Logger logger = Logger.getLogger(SPPersisterListener.class);
/**
* This persister will have persist calls made on it when the object(s) this
* listener is attached to fires events.
*/
private final SPPersister target;
/**
* A class that can convert a complex object into a basic representation
* that can be placed in a string and can also convert the string
* representation back into the complex object.
*/
private final SessionPersisterSuperConverter converter;
/**
* This is the persister that is tied to the listener for echo cancellation.
* If this persister is currently changing the object model we should not
* send new persist calls.
*/
private SPSessionPersister eventSource;
/**
* Persisted property buffer, mapping a {@link SPObject} uuid
* to a list of persisted properties for that uuid.
*/
private Multimap<String, PersistedSPOProperty> persistedProperties = LinkedListMultimap.create();
/**
* Persisted {@link WabitObject} buffer, contains all the data that was
* passed into the persistedObject call in the order of insertion
*/
private LinkedHashMap<String, PersistedSPObject> persistedObjects = new LinkedHashMap<String, PersistedSPObject>();
/**
* Keep track of the SPObjects that exist under the same parent.
* Key - the UUID of the parent.
* Value - the PersistedSPObject that is a child of parent with (parentUUID = key)
*/
private SortedSetMultimap<String, PersistedSPObject> parentPeristedObjects = new HashTreeSetMultimap<String, PersistedSPObject>(new Comparator<PersistedSPObject>() {
@Override
public int compare(PersistedSPObject o1, PersistedSPObject o2) {
if (o1 == null && o2 == null) return 0;
if (o1 == null) return -1;
if (o2 == null) return 1;
return o1.getIndex() - o2.getIndex();
}
});
/**
* {@link WabitObject} removal buffer, mapping of {@link WabitObject} UUIDs
* to their parents
*/
private LinkedHashMap<String, RemovedObjectEntry> objectsToRemove = new LinkedHashMap<String, RemovedObjectEntry>();
/**
* Keeps track of the UUIDs of all removed objects and all their descendants
* so that when properties are being persisted, this can be used to check
* whether the object the property belongs to has been removed or not.
*/
private Set<String> removedObjectsUUIDs = new HashSet<String>();
/**
* The depth of the current transaction. When this goes from 1 to 0 we know we have exited the outer-most
* transaction and need to send the pooled persist calls.
*/
private int transactionCount = 0;
/**
* If true the current persist calls in the roll back lists are being undone.
*/
private boolean rollingBack;
/**
* If defined this listener will not roll back changes to the workspace it
* is listening to. Instead it will still reset itself but it will rely on
* this persister to perform the rollback. This listener will also not
* forward changes while this persister is rolling back.
* <p>
* If this value is null a rollback on this listener will undo the changes
* it received from the model.
*/
private final SPSessionPersister rollbackPersister;
/**
* This listener can be attached to a hierarchy of objects to persist events
* to the target persister contained in the given persister helper factory.
*
* @param target
* The target persister that will be sent persist calls.
* @param converter
* A converter to convert complex types of objects into simple
* objects so the object can be passed or persisted.
*/
public SPPersisterListener(SPPersister target, SessionPersisterSuperConverter converter) {
this(target, null, converter);
}
/**
* For the rest of the parameters see
* {@link #SPPersisterListener(SPPersister, SessionPersisterSuperConverter)}
* .
*
* @param dontEcho
* If this persister is specified the listener will not collect
* events when the persister is updating the model.
*/
public SPPersisterListener(SPPersister target, SPSessionPersister dontEcho, SessionPersisterSuperConverter converter) {
this(target, dontEcho, converter, null);
}
/**
* For the rest of the parameters see
* {@link #SPPersisterListener(SPPersister, SPSessionPersister, SessionPersisterSuperConverter)}
*
* @param rollbackUpdatesWorkspace
* If true this listener will roll back changes it heard when
* rollback is performed. Rollback is normally performed when
* either explicitly called or when an exception occurs in a
* later persister. If false the listener will reset but it will
* not update the workspace it is listening to.
*/
public SPPersisterListener(SPPersister target, SPSessionPersister dontEcho,
SessionPersisterSuperConverter converter, SPSessionPersister rollbackPersister) {
this.target = target;
this.converter = converter;
this.eventSource = dontEcho;
this.rollbackPersister = rollbackPersister;
}
private String getParentPersistedObjectsId(PersistedSPObject pso) {
return getParentPersistedObjectsId(pso.getParentUUID(), pso.getType());
}
private String getParentPersistedObjectsId(String parentUUID, String type) {
return parentUUID + "." + type;
}
public void childAdded(SPChildEvent e) {
if (!e.getSource().getRunnableDispatcher().isForegroundThread()) {
throw new RuntimeException("New child event " + e + " not fired on the foreground.");
}
int index = e.getIndex();
// Get all the sibling under the same parent
PersistedSPObject minValue = new PersistedSPObject(
e.getSource().getUUID(), e.getChildType().getName(), e.getChild().getUUID(), index);
Set<PersistedSPObject> toBeUpdated = new HashSet<PersistedSPObject>(parentPeristedObjects.get(getParentPersistedObjectsId(minValue)).tailSet(minValue));
for (PersistedSPObject psp : toBeUpdated) {
// Update the sibling's index
PersistedSPObject newIndexedSibling = new PersistedSPObject(psp.getParentUUID(), psp.getType(), psp.getUUID(), psp.getIndex()+1);
parentPeristedObjects.remove(getParentPersistedObjectsId(psp), psp);
parentPeristedObjects.put(getParentPersistedObjectsId(newIndexedSibling), newIndexedSibling);
psp.setIndex(psp.getIndex() + 1);
}
SQLPowerUtils.listenToHierarchy(e.getChild(), this);
if (wouldEcho()) return;
if (logger.isDebugEnabled()) {
logger.debug("Child added: " + e);
}
persistObject(e.getChild(), index);
removedObjectsUUIDs.removeAll(getDescendantUUIDs(e.getChild()));
}
/**
* This persists a given object and all of its descendants to the target
* persister in this listener. Each object in the descendant tree will have
* one persist object call made and any number of additional persist
* property calls as needed. This can be useful for persisting an entire
* tree of objects to the JCR as an initial commit.
*
* @param o
* The object to be persisted.
* @param index
* the index the object is located in its parent's list of
* children of the same object type.
*/
public void persistObject(final SPObject o, int index) {
persistObject(o, index, true);
}
/**
* This persists a given object and all of its descendants to the target
* persister in this listener. Each object in the descendant tree will have
* one persist object call made and any number of additional persist
* property calls as needed. This can be useful for persisting an entire
* tree of objects to the JCR as an initial commit.
*
* @param o
* The object to be persisted.
* @param index
* the index the object is located in its parent's list of
* children of the same object type.
* @param includeRootPersistObject
* If set to false the persist object for 'o' that is passed in
* will not be sent. The persist properties of final values will
* also not be sent. This is useful if we want to use the
* persister listener to update an object tree but the root
* already exists and we cannot or do not want to recreate it.
*/
public void persistObject(final SPObject o, int index, final boolean includeRootPersistObject) {
if (wouldEcho()) return;
this.transactionStarted(TransactionEvent.createStartTransactionEvent(this,
"Persisting " + o.getName() + " and its descendants."));
//This persister is used to put persist calls to the pooled lists in this listener.
SPPersister poolingPersister = new SPPersister() {
public void rollback() {
//do nothing, just looking for persist calls.
}
public void removeObject(String parentUUID, String uuid)
throws SPPersistenceException {
throw new IllegalStateException("There was a remove object call when " +
"trying to persist an object. This should not happen.");
}
public void persistProperty(String uuid, String propertyName,
DataType propertyType, Object newValue)
throws SPPersistenceException {
//unconditional properties in this case are only on new objects
//so undoing these properties will only come just before the object is
//removed and doesn't matter.
persistedProperties.put(uuid, new PersistedSPOProperty(uuid, propertyName,
propertyType, newValue, newValue, true));
}
public void persistProperty(String uuid, String propertyName,
DataType propertyType, Object oldValue, Object newValue)
throws SPPersistenceException {
persistedProperties.put(uuid, new PersistedSPOProperty(
uuid, propertyName, propertyType, oldValue, newValue, false));
}
public void persistObject(String parentUUID, String type, String uuid,
int index) throws SPPersistenceException {
if (logger.isDebugEnabled()) logger.debug("Adding a " + type + " with UUID: " + uuid + " to persistedObjects");
// Check to see this object has not already been added.
if (getPersistedObject(uuid) != null) {
throw new SPPersistenceException(uuid, "Cannot add object of type "
+ type + " with UUID " + uuid + " because an object with "
+ " the same UUID has already been added");
}
PersistedSPObject pspo = new PersistedSPObject(parentUUID, type, uuid, index);
persistedObjects.put(uuid, pspo);
parentPeristedObjects.put(getParentPersistedObjectsId(pspo), pspo);
}
public void commit() throws SPPersistenceException {
//do nothing, just looking for persist calls.
}
public void begin() throws SPPersistenceException {
//do nothing, just looking for persist calls.
}
};
try {
persistObjectInterleaveProperties(o, index, includeRootPersistObject, poolingPersister);
} catch (SPPersistenceException e) {
throw new RuntimeException(e);
}
this.transactionEnded(TransactionEvent.createEndTransactionEvent(this));
}
public void persistObjectInterleaveProperties(SPObject o, int index,
boolean includeRootPersistObject, SPPersister localTarget) throws SPPersistenceException {
persistObjectInterleaveProperties(o, index, includeRootPersistObject, localTarget,
Collections.<Class<? extends SPObject>>emptySet());
}
/**
* The object and all of its descendants will be persisted to the given
* persister. The difference from this method and
* {@link #persistObject(SPObject, int, boolean)} is this method will
* persist the objects and properties inter-leaved where each set of
* properties will come after its persist object call.
*
* @param o
* The object to persist and all of its descendants will be
* persisted as well.
* @param index
* The index of the object in its parent list.
* @param includeRootPersistObject
* If false the persist call for persisting object passed in as o
* will be skipped.
* @param localTarget
* The persister to make all of the persist object calls to.
* @param skipList
* The classes in the given list will be skipped.
* @throws SPPersistenceException
*/
public void persistObjectInterleaveProperties(SPObject o, int index,
boolean includeRootPersistObject, SPPersister localTarget, Collection<Class<? extends SPObject>> skipList)
throws SPPersistenceException {
final SPPersisterHelper<? extends SPObject> persisterHelper;
try {
persisterHelper = PersisterHelperFinder.findPersister(o.getClass());
} catch (Exception e) {
throw new SPPersistenceException(o.getUUID(), e);
}
localTarget.begin();
if (includeRootPersistObject) {
persisterHelper.persistObject(o, index, localTarget, converter);
} else {
List<String> emptyList = new ArrayList<String>();
persisterHelper.persistObjectProperties(o, localTarget, converter, emptyList);
}
for (Class<? extends SPObject> childType : o.getAllowedChildTypes()) {
if (skipList.contains(childType)) continue;
List<? extends SPObject> children;
if (o instanceof SQLObject) {
children = ((SQLObject) o).getChildrenWithoutPopulating(childType);
} else {
children = o.getChildren(childType);
}
if (logger.isDebugEnabled()) {
logger.debug("Persisting children " + children + " of " + o);
}
for (int i = 0; i < children.size(); i++) {
persistObjectInterleaveProperties(children.get(i), i, true, localTarget, skipList);
}
}
localTarget.commit();
}
public void childRemoved(SPChildEvent e) {
if (!e.getSource().getRunnableDispatcher().isForegroundThread()) {
throw new RuntimeException("Removed child event " + e + " not fired on the foreground.");
}
String parentUUID = e.getSource().getUUID();
int index = e.getIndex();
List<PersistedSPObject> toBeUpdated = new ArrayList<PersistedSPObject>();
// Get all the sibling under the same parent
//TODO make this work like childAdded
for (PersistedSPObject persisterSibling : parentPeristedObjects.get(getParentPersistedObjectsId(parentUUID, e.getChildType().getName()))) {
if (persisterSibling.getIndex() > index && persisterSibling.getType().equals(e.getChildType().getName())) {
toBeUpdated.add(persisterSibling);
}
}
for (PersistedSPObject psp : toBeUpdated) {
// Update the sibling's index
PersistedSPObject newIndexedSibling = new PersistedSPObject(psp.getParentUUID(), psp.getType(), psp.getUUID(), psp.getIndex()-1);
persistedObjects.remove(psp.getUUID());
persistedObjects.put(psp.getUUID(), newIndexedSibling);
parentPeristedObjects.remove(getParentPersistedObjectsId(psp), psp);
parentPeristedObjects.put(getParentPersistedObjectsId(newIndexedSibling), newIndexedSibling);
}
SQLPowerUtils.unlistenToHierarchy(e.getChild(), this);
if (wouldEcho()) return;
String uuid = e.getChild().getUUID();
if (getRemovedObject(uuid) != null && getPersistedObject(uuid) == null) {
throw new IllegalStateException("Cannot add object of type "
+ e.getChildType() + " with UUID " + uuid + " because an object with "
+ " the same UUID has already been removed");
}
PersistedSPObject pso = getPersistedObject(uuid);
if (pso == null) {
transactionStarted(TransactionEvent.createStartTransactionEvent(this,
"Start of transaction triggered by childRemoved event"));
objectsToRemove.put(e.getChild().getUUID(),
new RemovedObjectEntry(
e.getSource().getUUID(),
e.getChild(),
e.getIndex()));
removedObjectsUUIDs.addAll(getDescendantUUIDs(e.getChild()));
transactionEnded(TransactionEvent.createEndTransactionEvent(this));
}
//When a remove comes in we need to remove all of the persist calls for the
//object being removed and its descendants regardless if a remove event is included.
persistedProperties.removeAll(uuid);
if (pso != null) {
persistedObjects.remove(pso.getUUID());
parentPeristedObjects.remove(getParentPersistedObjectsId(pso), pso);
}
List<String> descendantUUIDs = new ArrayList<String>(getDescendantUUIDs(e.getChild()));
descendantUUIDs.remove(uuid);
for (String uuidToRemove : descendantUUIDs) {
persistedProperties.removeAll(uuidToRemove);
PersistedSPObject childPSO = persistedObjects.get(uuidToRemove);
persistedObjects.remove(uuidToRemove);
if (childPSO != null) {
parentPeristedObjects.remove(getParentPersistedObjectsId(childPSO), childPSO);
}
}
}
public void transactionEnded(TransactionEvent e) {
if (wouldEcho()) return;
try {
if (logger.isDebugEnabled()) logger.debug("transactionEnded " + ((e == null) ? null : e.getMessage()));
commit();
} catch (SPPersistenceException e1) {
throw new RuntimeException(e1);
}
}
public void transactionRollback(TransactionEvent e) {
if (logger.isDebugEnabled()) logger.debug("transactionRollback " + ((e == null) ? null : e.getMessage()));
rollback();
}
public void transactionStarted(TransactionEvent e) {
if (wouldEcho()) return;
if (logger.isDebugEnabled()) logger.debug("transactionStarted " + ((e == null) ? null : e.getMessage()));
transactionCount++;
}
public void propertyChanged(PropertyChangeEvent evt) {
SPObject source = (SPObject) evt.getSource();
String uuid = source.getUUID();
String propertyName = evt.getPropertyName();
Object oldValue = evt.getOldValue();
Object newValue = evt.getNewValue();
try {
if (!PersisterHelperFinder.findPersister(source.getClass())
.getPersistedProperties()
.contains(propertyName)) {
if (logger.isDebugEnabled()) logger.debug("Tried to persist a property that shouldn't be. Ignoring the property: " + propertyName);
return;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!((SPObject) evt.getSource()).getRunnableDispatcher().isForegroundThread()) {
throw new RuntimeException("Property change " + evt + " not fired on the foreground.");
}
Object oldBasicType = converter.convertToBasicType(oldValue);
Object newBasicType = converter.convertToBasicType(newValue);
PersistedSPOProperty property = null;
for (PersistedSPOProperty p : persistedProperties.get(uuid)) {
if (p.getPropertyName().equals(propertyName)) {
property = p;
break;
}
}
if (property != null) {
boolean valuesMatch;
if (property.getNewValue() == null) {
valuesMatch = oldBasicType == null;
} else {
// Check that the old property's new value is equal to the new change's old value.
// Also, accept the change if it is the same as the last one.
valuesMatch = property.getNewValue().equals(oldBasicType) ||
(property.getOldValue().equals(oldBasicType) && property.getNewValue().equals(newBasicType));
}
if (!valuesMatch) {
try {
throw new RuntimeException("Multiple property changes do not follow after each other properly. " +
"Property " + property.getPropertyName() + ", on object " + source + " of type " +
source.getClass() + ", Old " + oldBasicType + ", new " + property.getNewValue());
} finally {
this.rollback();
}
}
}
if (wouldEcho()) {
//The persisted property was changed by a persist call received from the server.
//The property is removed from the persist calls as it now matchs what is
//in the server.
persistedProperties.remove(uuid, property);
return;
}
transactionStarted(TransactionEvent.createStartTransactionEvent(this, PROPERTY_CHANGED_MSG));
//Not persisting non-settable properties.
//TODO A method in the persister helpers would make more sense than
//using reflection here.
PropertyDescriptor propertyDescriptor;
try {
propertyDescriptor= PropertyUtils.getPropertyDescriptor(source, propertyName);
} catch (Exception ex) {
this.rollback();
throw new RuntimeException(ex);
}
if (propertyDescriptor == null
|| propertyDescriptor.getWriteMethod() == null) {
transactionEnded(TransactionEvent.createEndTransactionEvent(this));
return;
}
DataType typeForClass = PersisterUtils.
getDataType(newValue == null ? Void.class : newValue.getClass());
boolean unconditional = false;
if (property != null) {
// Hang on to the old value
oldBasicType = property.getOldValue();
// If an object was created, and unconditional properties are being sent,
// this will maintain that flag in the event of additional property changes
// to the same property in the same transaction.
unconditional = property.isUnconditional();
persistedProperties.remove(uuid, property);
}
if (logger.isDebugEnabled()) logger.debug("persistProperty(" + uuid + ", " + propertyName + ", " +
typeForClass.name() + ", " + oldValue + ", " + newValue + ")");
persistedProperties.put(uuid, new PersistedSPOProperty(
uuid, propertyName, typeForClass, oldBasicType, newBasicType, unconditional));
this.transactionEnded(TransactionEvent.createEndTransactionEvent(this));
}
/**
* Returns true if the WabitSessionPersister that this listener complements
* is currently in the middle of an update. In that case, none of the
* WabitListener methods should make calls into the target persister.
*
* @return True if forwarding an event to the target persister would
* constitute an echo.
*/
private boolean wouldEcho() {
if (rollingBack) return true;
if (rollbackPersister != null && rollbackPersister.isHeadingToWisconsin()) return true;
return eventSource != null && eventSource.isUpdatingWorkspace();
}
/**
* Does the actual commit when the transaction count reaches 0. This ensures
* the persist calls are contained in a transaction if a lone change comes
* through. This also allows us to roll back changes if an exception comes
* from the server.
*
* @throws SPPersistenceException
*/
private void commit() throws SPPersistenceException {
if (logger.isDebugEnabled()) logger.debug("commit(): transactionCount = " + transactionCount);
if (transactionCount==1) {
try {
if (logger.isDebugEnabled()) logger.debug("Calling commit...");
//If nothing actually changed in the transaction do not send
//the begin and commit to reduce server traffic.
if (objectsToRemove.isEmpty() && persistedObjects.isEmpty() &&
persistedProperties.isEmpty()) return;
target.begin();
commitRemovals();
commitObjects();
commitProperties();
target.commit();
if (logger.isDebugEnabled()) logger.debug("...commit completed.");
if (logger.isDebugEnabled()) {
try {
final Clip clip = AudioSystem.getClip();
clip.open(AudioSystem.getAudioInputStream(
getClass().getResource("/sounds/transaction_complete.wav")));
clip.addLineListener(new LineListener() {
public void update(LineEvent event) {
if (event.getType().equals(LineEvent.Type.STOP)) {
logger.debug("Stopping sound");
clip.close();
}
}
});
clip.start();
} catch (Exception ex) {
logger.debug("A transaction committed but we cannot play the commit sound.", ex);
}
}
} catch (Throwable t) {
logger.warn("Rolling back due to " + t, t);
this.rollback();
if (t instanceof SPPersistenceException) throw (SPPersistenceException) t;
if (t instanceof FriendlyRuntimeSPPersistenceException) throw (FriendlyRuntimeSPPersistenceException) t;
throw new SPPersistenceException(null,t);
} finally {
clear();
this.transactionCount = 0;
}
} else {
transactionCount--;
}
}
/**
* Performs the rollback if a problem occurred during the persist call.
*/
public void rollback() {
if (wouldEcho() || eventSource == null ||
eventSource.isHeadingToWisconsin()) {
// This means that the SessionPersister is cleaning his stuff and
// we need to do the same. Close all current transactions... bla bla bla.
this.objectsToRemove.clear();
this.removedObjectsUUIDs.clear();
this.persistedObjects.clear();
this.parentPeristedObjects.clear();
this.persistedProperties.clear();
this.transactionCount = 0;
target.rollback();
return;
}
rollingBack = true;
SPObject workspace = eventSource.getWorkspaceContainer().getWorkspace();
boolean initialMagic = workspace.isMagicEnabled();
if (initialMagic) {
workspace.setMagicEnabled(false);
}
try {
if (rollbackPersister == null) {
List<PersistedObjectEntry> rollbackObjects = new LinkedList<PersistedObjectEntry>();
List<PersistedPropertiesEntry> rollbackProperties = new LinkedList<PersistedPropertiesEntry>();
for (PersistedSPObject o : persistedObjects.values()) {
rollbackObjects.add(new PersistedObjectEntry(o.getParentUUID(), o.getUUID()));
}
for (PersistedSPOProperty p : persistedProperties.values()) {
rollbackProperties.add(new PersistedPropertiesEntry(
p.getUUID(), p.getPropertyName(), p.getDataType(), p.getOldValue()));
}
SPSessionPersister.undoForSession(
eventSource.getWorkspaceContainer().getWorkspace(),
rollbackObjects,
rollbackProperties,
objectsToRemove,
converter);
}
} catch (SPPersistenceException e) {
logger.error(e);
} finally {
this.objectsToRemove.clear();
this.removedObjectsUUIDs.clear();
this.persistedObjects.clear();
this.parentPeristedObjects.clear();
this.persistedProperties.clear();
this.transactionCount = 0;
rollingBack = false;
target.rollback();
if (initialMagic) {
eventSource.getWorkspaceContainer().getWorkspace().setMagicEnabled(true);
}
}
}
/**
* Commits the persisted {@link SPObject}s that we pooled during
* the transaction. Also updates the roll back list as we go in case
* it is needed.
*
* @throws SPPersistenceException
*/
private void commitObjects() throws SPPersistenceException {
if (logger.isDebugEnabled()) logger.debug("Committing objects");
for (PersistedSPObject pwo : persistedObjects.values()) {
if (logger.isDebugEnabled()) logger.debug("Commiting persist call: " + pwo);
target.persistObject(
pwo.getParentUUID(),
pwo.getType(),
pwo.getUUID(),
pwo.getIndex());
}
}
/**
* Commits the persisted properties that were pooled during the transaction.
* Updates the roll back list as we go.
*
* @throws SPPersistenceException
*/
private void commitProperties() throws SPPersistenceException {
if (logger.isDebugEnabled()) logger.debug("commitProperties()");
for (PersistedSPOProperty property : persistedProperties.values()) {
if (removedObjectsUUIDs.contains(property.getUUID())) continue;
if (property.isUnconditional()) {
target.persistProperty(
property.getUUID(),
property.getPropertyName(),
property.getDataType(),
property.getNewValue());
} else {
target.persistProperty(
property.getUUID(),
property.getPropertyName(),
property.getDataType(),
property.getOldValue(),
property.getNewValue());
}
}
}
/**
* Commits the removals that were pooled during the transaction. Also
* updates the roll back list.
*/
private void commitRemovals() throws SPPersistenceException {
if (logger.isDebugEnabled()) logger.debug("commitRemovals()");
for (RemovedObjectEntry entry: this.objectsToRemove.values()) {
// Don't make removal persist calls for children of
// objects that are also being removed, since the JCR handles that.
if (removedObjectsUUIDs.contains(entry.getParentUUID())) continue;
if (logger.isDebugEnabled()) logger.debug("target.removeObject(" + entry.getParentUUID() + ", " +
entry.getRemovedChild().getUUID() + ")");
target.removeObject(
entry.getParentUUID(),
entry.getRemovedChild().getUUID());
}
}
public List<PersistedSPOProperty> getPersistedProperties() {
return new LinkedList<PersistedSPOProperty>(persistedProperties.values());
}
public List<PersistedSPObject> getPersistedObjects() {
return new ArrayList<PersistedSPObject>(persistedObjects.values());
}
public LinkedHashMap<String, RemovedObjectEntry> getObjectsToRemove() {
return objectsToRemove;
}
/**
* This method cycles through the given list of pooled objects that
* are being persisted to see if it contains the object with the given uuid
* @param uuid
* @return The object with the given uuid, or null if it cannot be found
*/
public PersistedSPObject getPersistedObject(String uuid) {
return persistedObjects.get(uuid);
}
/**
* This method cycles through the given list of pooled objects that
* are being removed to see if it contains the object with the given uuid
* @param uuid
* @return The object with the given uuid, or null if it cannot be found
*/
public RemovedObjectEntry getRemovedObject(String uuid) {
return objectsToRemove.get(uuid);
}
/**
* Returns a list of UUIDs that contains this object's UUID and all of the
* children's uuid.
*/
public Set<String> getDescendantUUIDs(SPObject parent) {
Set<String> uuids = new HashSet<String>();
uuids.add(parent.getUUID());
List<? extends SPObject> children;
//XXX We need a way to get the children of SQLObjects for persistence
//without causing them to populate.
if (parent instanceof SQLObject) {
children = ((SQLObject) parent).getChildrenWithoutPopulating();
} else {
children = parent.getChildren();
}
for (SPObject child : children) {
uuids.addAll(getDescendantUUIDs(child));
}
return uuids;
}
/**
* This method can be used to reset the listener back to an initial state.
* The ability to reset the listener is useful if we are trying to re-apply
* changes such as in the network conflict resolver but the listener is already
* in a transaction.
*/
public void clear() {
this.objectsToRemove.clear();
this.removedObjectsUUIDs.clear();
this.persistedObjects.clear();
this.parentPeristedObjects.clear();
this.persistedProperties.clear();
}
/**
* Returns true if the listener is in a transaction. False if the listener
* is not in a transaction.
*/
public boolean isInTransaction() {
return transactionCount > 0;
}
/**
* The event source of a listener can be changed so the listener does not
* echo events. This can only be done when the listener is not in a
* transaction.
*/
public void setEventSource(SPSessionPersister eventSource) {
if (isInTransaction()) throw new IllegalStateException("Cannot set the event source when in a transaction!");
this.eventSource = eventSource;
}
}