/*
* Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.versioning;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.domain.DomainServiceInterfaceExposer;
import com.arsdigita.persistence.DataAssociation;
import com.arsdigita.persistence.DataAssociationCursor;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.metadata.ObjectType;
import com.arsdigita.persistence.metadata.Property;
import com.arsdigita.util.Assert;
import java.util.HashSet;
import java.util.Iterator;
import org.apache.log4j.Logger;
// old versioning
/**
* VersionController class
*
* @author Joseph A. Bank (jbank@alum.mit.edu)
* @author Stanislav Freidin
* @version $Revision: #21 $ $Date: 2004/08/16 $
*/
class VersionController {
private static final Logger s_log =
Logger.getLogger(VersionController.class);
/**
* Recursively follows the object's composite sub-objects, setting their
* master_id to the given top-level object. This method should be used by an
* initializer to upgrade existing objects to correctly versioned objects.
*
* @param obj the object to be fixed
* @param master the correct master object
*/
protected static void autoPropagateMaster
(VersionedACSObject obj, VersionedACSObject master) {
Assert.isTrue
(master.isMaster(),
"Object " + master.getOID() + " is the master object");
recursivelyTraverse
(obj, master, new TraversalListener() {
public boolean preorderProcess
(DataObject data, VersionedACSObject vobj,
VersionedACSObject master) {
// Fix the master for the current object
if(! master.equals(vobj.getMaster())) {
s_log.info
("Object " + vobj.getID() +
" had wrong master object " +
vobj.getMaster().getID() +
", setting correct master " + master.getOID());
vobj.setMaster(master);
vobj.save();
}
return true;
}
public boolean postorderProcess
(DataObject data, VersionedACSObject vobj,
VersionedACSObject master) {
return true;
}
});
}
/**
* Recursively follows the object's composite sub-objects, executing some
* action for each object (including the topmost parent).
*
* @param obj the object to check
* @param master the correct master object
* @param action the {@link TraversalListener} that will perform the
* required action on each object
*/
private static void recursivelyTraverse
(VersionedACSObject obj, VersionedACSObject master,
TraversalListener action) {
recursivelyTraverse(obj, master, new HashSet(), action);
}
/**
* Recursively follows the object's composite sub-objects, executing some
* action for each object (including the topmost parent).
*
* @param obj the object to check
* @param master the correct master object
* @param visited a set of all objects visited so far; ensures that
* there are no loops in the containment hierarchy.
* @param action the {@link TraversalListener} that will perform the
* required action on each object
*/
private static void recursivelyTraverse
(VersionedACSObject obj, VersionedACSObject master, HashSet visited,
TraversalListener action) {
// Specialize the object
DataObject data = DomainServiceInterfaceExposer.getDataObject(obj);
VersionedACSObject vobj =
(VersionedACSObject) DomainObjectFactory.newInstance(data);
OID oid = vobj.getOID();
if (visited.contains(oid)) {
throw new IllegalStateException
("The object " + oid + " appears more than once in the " +
"containment hierarchy. This indicates that the PDL files " +
"for the object or its containters create a loop, " +
"such as \'A contains component B, B contains " +
"component A\'; this is illegal for a variety of reasons.");
}
// Prevent loops
visited.add(oid);
// Perform the action for the current object
if(!action.preorderProcess(data, vobj, master)) {
return;
}
// Iterate over properties
ObjectType type = oid.getObjectType();
for (Iterator i = type.getProperties(); i.hasNext(); ) {
Property prop = (Property) i.next();
String propName = prop.getName();
// Skip aggregates, skip scalars,
// skip master object reference
if (!prop.isComponent() || !prop.isRole() ||
VersionedACSObject.MASTER.equals(propName)) {
continue;
}
// Get the value directly from the data object, to avoid
// the overridden get method that skips deleted objects
Object value = data.get(propName);
// If value is null, nothing to worry about
if(value == null) {
continue;
}
if (value instanceof DataObject) {
// Check a 1..1 or 0..1 association
traverseDataObject((DataObject)value, master, visited, action);
} else if (value instanceof DataAssociation) {
// Check 1..n, 0..n association
DataAssociation assoc = (DataAssociation)value;
ObjectType childType = assoc.getObjectType();
if (VersionedACSObject.isSubtype(childType)) {
// Iterate over children
DataAssociationCursor cur =
assoc.getDataAssociationCursor();
while (cur.next()) {
// Fix link attribute
DataObject linkData = cur.getLink();
if (linkData != null) {
traverseDataObject
(linkData, master, visited, action);
}
// Fix actual data
traverseDataObject
(cur.getDataObject(), master, visited, action);
}
cur.close();
}
}
}
// Perform the postorder action for the current object
if (!action.postorderProcess(data, vobj, master)) {
return;
}
}
// Helper method to convert a data object to a domain object, if possible
private static void traverseDataObject
(DataObject data, VersionedACSObject master, HashSet visited,
TraversalListener action) {
ObjectType type = data.getObjectType();
if (VersionedACSObject.isSubtype(type)) {
VersionedACSObject obj =
(VersionedACSObject)DomainObjectFactory.newInstance(data);
recursivelyTraverse(obj, master, visited, action);
}
}
/**
* A listener that runs for each object during recursive traversal.
*/
private interface TraversalListener {
/**
* Processes the given object before processing its children.
*
* @param data the current DataObject
* @param vobj the corresponding VersionedACSObject
* @param master the correct master object for the current object;
* may be different from vobj.getMaster()
* @return true if the traversal is to continue, false if it needs
* to be aborted
*/
public boolean preorderProcess
(DataObject data, VersionedACSObject vobj,
VersionedACSObject master);
/**
* Processes the given object after processing its children.
*
* @param data the current DataObject
* @param vobj the corresponding VersionedACSObject
* @param master the correct master object for the current object;
* may be different from vobj.getMaster()
* @return true if the traversal is to continue, false if it needs
* to be aborted
*/
public boolean postorderProcess
(DataObject data, VersionedACSObject vobj,
VersionedACSObject master);
}
}