/*
* Copyright (C) 2009 eXo Platform SAS.
*
* This 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 software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.services.jcr.impl.core.version;
import org.exoplatform.services.jcr.access.AccessControlEntry;
import org.exoplatform.services.jcr.access.AccessControlList;
import org.exoplatform.services.jcr.core.nodetype.NodeDefinitionData;
import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager;
import org.exoplatform.services.jcr.core.nodetype.PropertyDefinitionData;
import org.exoplatform.services.jcr.dataflow.ItemState;
import org.exoplatform.services.jcr.dataflow.PlainChangesLog;
import org.exoplatform.services.jcr.datamodel.InternalQName;
import org.exoplatform.services.jcr.datamodel.ItemType;
import org.exoplatform.services.jcr.datamodel.NodeData;
import org.exoplatform.services.jcr.datamodel.PropertyData;
import org.exoplatform.services.jcr.datamodel.QPath;
import org.exoplatform.services.jcr.datamodel.QPathEntry;
import org.exoplatform.services.jcr.datamodel.ValueData;
import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.core.SessionDataManager;
import org.exoplatform.services.jcr.impl.core.value.BaseValue;
import org.exoplatform.services.jcr.impl.dataflow.AbstractItemDataCopyVisitor;
import org.exoplatform.services.jcr.impl.dataflow.TransientNodeData;
import org.exoplatform.services.jcr.impl.dataflow.TransientPropertyData;
import org.exoplatform.services.jcr.impl.dataflow.TransientValueData;
import org.exoplatform.services.jcr.util.IdGenerator;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.ValueFactory;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.version.OnParentVersionAction;
import javax.jcr.version.VersionException;
/**
* Created by The eXo Platform SAS.
*
* @author Gennady Azarenkov
* @version $Id: FrozenNodeInitializer.java 11907 2008-03-13 15:36:21Z ksm $
*/
public class FrozenNodeInitializer extends AbstractItemDataCopyVisitor
{
private static Log log = ExoLogger.getLogger("exo.jcr.component.core.FrozenNodeInitializer");
private final Stack<NodeData> contextNodes;
private final NodeTypeDataManager ntManager;
private final PlainChangesLog changesLog;
private final SessionDataManager dataManager;
private final ValueFactory valueFactory;
public FrozenNodeInitializer(NodeData frozen, SessionDataManager dataManager, NodeTypeDataManager ntManager,
PlainChangesLog changesLog, ValueFactory valueFactory) throws RepositoryException
{
super(dataManager);
this.dataManager = dataManager;
this.ntManager = ntManager;
this.changesLog = changesLog;
this.valueFactory = valueFactory;
this.contextNodes = new Stack<NodeData>();
this.contextNodes.push(frozen);
}
/**
* {@inheritDoc}
*/
protected void visitChildNodes(NodeData node) throws RepositoryException
{
// It is not necessary to traverse child nodes since parent is null (OnParentVersion=IGNORE case)
if (currentNode() == null)
{
return;
}
super.visitChildNodes(node);
}
@Override
protected void entering(PropertyData property, int level) throws RepositoryException
{
if (log.isDebugEnabled())
{
log.debug("Entering property " + property.getQPath().getAsString());
}
if (currentNode() == null)
{
// skip if no parent - parent is COMPUTE, INITIALIZE
return;
}
PropertyData frozenProperty = null;
InternalQName qname = property.getQPath().getName();
List<ValueData> values = copyValues(property);
boolean mv = property.isMultiValued();
if (qname.equals(Constants.JCR_PRIMARYTYPE) && level == 1)
{
frozenProperty =
TransientPropertyData.createPropertyData(currentNode(), Constants.JCR_FROZENPRIMARYTYPE, PropertyType.NAME,
mv, values);
}
else if (qname.equals(Constants.JCR_UUID) && level == 1)
{
frozenProperty =
TransientPropertyData.createPropertyData(currentNode(), Constants.JCR_FROZENUUID, PropertyType.STRING, mv,
values);
}
else if (qname.equals(Constants.JCR_MIXINTYPES) && level == 1)
{
frozenProperty =
TransientPropertyData.createPropertyData(currentNode(), Constants.JCR_FROZENMIXINTYPES, PropertyType.NAME,
mv, values);
}
else
{
NodeData parent = (NodeData)dataManager.getItemData(property.getParentIdentifier());
PropertyDefinitionData pdef =
ntManager.getPropertyDefinitions(qname, parent.getPrimaryTypeName(), parent.getMixinTypeNames())
.getAnyDefinition();
int action = pdef.getOnParentVersion();
if (action == OnParentVersionAction.IGNORE)
{
return;
}
else if (action == OnParentVersionAction.ABORT)
{
throw new VersionException("Property is aborted " + property.getQPath().getAsString());
}
else if (action == OnParentVersionAction.COPY || action == OnParentVersionAction.VERSION
|| action == OnParentVersionAction.COMPUTE)
{
frozenProperty =
TransientPropertyData.createPropertyData(currentNode(), qname, property.getType(), mv, values);
}
else if (action == OnParentVersionAction.INITIALIZE)
{
// 8.2.11.3 INITIALIZE
// On checkin of N, a new P will be created and placed in version
// storage as a child of VN. The new P will be initialized just as it
// would
// be if created normally in a workspace
if (pdef.isAutoCreated())
{
if (pdef.getDefaultValues() != null && pdef.getDefaultValues().length > 0)
{
// to use default values
values.clear();
for (String defValue : pdef.getDefaultValues())
{
ValueData defData;
if (PropertyType.UNDEFINED == pdef.getRequiredType())
{
defData = ((BaseValue)valueFactory.createValue(defValue)).getInternalData();
}
else
{
defData =
((BaseValue)valueFactory.createValue(defValue, pdef.getRequiredType())).getInternalData();
}
// TransientValueData defData = ((BaseValue)
// defValue).getInternalData();
// values.add(defData.createTransientCopy());
values.add(defData);
}
}
else if (ntManager.isNodeType(Constants.NT_HIERARCHYNODE, parent.getPrimaryTypeName(), parent
.getMixinTypeNames())
&& qname.equals(Constants.JCR_CREATED))
{
// custom logic for nt:hierarchyNode jcr:created
values.clear();
values.add(new TransientValueData(dataManager.getTransactManager().getStorageDataManager()
.getCurrentTime()));
}
} // else... just as it would be if created normally in a workspace
// (sure with value data)
frozenProperty =
TransientPropertyData.createPropertyData(currentNode(), qname, property.getType(), mv, values);
}
else
throw new RepositoryException("Unknown OnParentVersion value " + action);
}
changesLog.add(ItemState.createAddedState(frozenProperty));
}
@Override
protected void entering(NodeData node, int level) throws RepositoryException
{
// this node is not taken in account
if (level == 0)
{
if (log.isDebugEnabled())
log.debug("Entering node " + node.getQPath().getAsString() + ", level=0");
return;
}
// ignored parent
if (currentNode() == null)
{
contextNodes.push(null);
if (log.isDebugEnabled())
log.debug("Entering node " + node.getQPath().getAsString() + ", HAS NULL PARENT");
return;
}
InternalQName qname = node.getQPath().getName();
NodeData parent = (NodeData)dataManager.getItemData(node.getParentIdentifier());
NodeDefinitionData ndef =
ntManager.getChildNodeDefinition(qname, node.getPrimaryTypeName(), parent.getPrimaryTypeName(),
parent.getMixinTypeNames());
if (ndef == null)
{
throw new ConstraintViolationException("Definition not found for " + qname.getAsString());
}
int action = ndef.getOnParentVersion();
if (log.isDebugEnabled())
log.debug("Entering node " + node.getQPath().getAsString() + ", "
+ OnParentVersionAction.nameFromValue(action));
NodeData frozenNode = null;
if (action == OnParentVersionAction.IGNORE)
{
contextNodes.push(null);
}
else if (action == OnParentVersionAction.ABORT)
{
throw new VersionException("Node is aborted " + node.getQPath().getAsString());
}
else if (action == OnParentVersionAction.COPY)
{
AccessControlList acl = currentNode().getACL();
boolean isPrivilegeable =
ntManager.isNodeType(Constants.EXO_PRIVILEGEABLE, node.getPrimaryTypeName(), node.getMixinTypeNames());
boolean isOwneable =
ntManager.isNodeType(Constants.EXO_OWNEABLE, node.getPrimaryTypeName(), node.getMixinTypeNames());
if (isPrivilegeable || isOwneable)
{
List<AccessControlEntry> permissionEntries = new ArrayList<AccessControlEntry>();
permissionEntries.addAll((isPrivilegeable ? node.getACL() : currentNode().getACL()).getPermissionEntries());
String owner = isOwneable ? node.getACL().getOwner() : currentNode().getACL().getOwner();
acl = new AccessControlList(owner, permissionEntries);
}
QPath frozenPath = QPath.makeChildPath(currentNode().getQPath(), qname, node.getQPath().getIndex());
frozenNode =
new TransientNodeData(frozenPath, IdGenerator.generate(), node.getPersistedVersion(),
node.getPrimaryTypeName(), node.getMixinTypeNames(), node.getOrderNumber(), currentNode()
.getIdentifier(), acl);
contextNodes.push(frozenNode);
changesLog.add(ItemState.createAddedState(frozenNode));
}
else if (action == OnParentVersionAction.VERSION)
{
if (ntManager.isNodeType(Constants.MIX_VERSIONABLE, node.getPrimaryTypeName(), node.getMixinTypeNames()))
{
frozenNode =
TransientNodeData.createNodeData(currentNode(), qname, Constants.NT_VERSIONEDCHILD, node.getQPath()
.getIndex());
PropertyData pt =
TransientPropertyData.createPropertyData(frozenNode, Constants.JCR_PRIMARYTYPE, PropertyType.NAME,
false, new TransientValueData(Constants.NT_VERSIONEDCHILD));
ValueData vh =
((PropertyData)dataManager.getItemData(node, new QPathEntry(Constants.JCR_VERSIONHISTORY, 0),
ItemType.PROPERTY)).getValues().get(0);
PropertyData pd =
TransientPropertyData.createPropertyData(frozenNode, Constants.JCR_CHILDVERSIONHISTORY,
PropertyType.REFERENCE, false, vh);
contextNodes.push(null);
changesLog.add(ItemState.createAddedState(frozenNode));
changesLog.add(ItemState.createAddedState(pt));
changesLog.add(ItemState.createAddedState(pd));
}
else
{ // behaviour of COPY
AccessControlList acl = currentNode().getACL();
boolean isPrivilegeable =
ntManager.isNodeType(Constants.EXO_PRIVILEGEABLE, node.getPrimaryTypeName(), node.getMixinTypeNames());
boolean isOwneable =
ntManager.isNodeType(Constants.EXO_OWNEABLE, node.getPrimaryTypeName(), node.getMixinTypeNames());
if (isPrivilegeable || isOwneable)
{
List<AccessControlEntry> accessList = new ArrayList<AccessControlEntry>();
accessList.addAll((isPrivilegeable ? node.getACL() : currentNode().getACL())
.getPermissionEntries());
String owner = isOwneable ? node.getACL().getOwner() : currentNode().getACL().getOwner();
acl = new AccessControlList(owner, accessList);
}
QPath frozenPath = QPath.makeChildPath(currentNode().getQPath(), qname, node.getQPath().getIndex());
frozenNode =
new TransientNodeData(frozenPath, IdGenerator.generate(), node.getPersistedVersion(),
node.getPrimaryTypeName(), node.getMixinTypeNames(), node.getOrderNumber(), currentNode()
.getIdentifier(), acl);
contextNodes.push(frozenNode);
changesLog.add(ItemState.createAddedState(frozenNode));
}
}
else if (action == OnParentVersionAction.INITIALIZE)
{
// 8.2.11.3 INITIALIZE
// On checkin of N, a new node C will be created and placed in version
// storage as a child of VN. This new C will be initialized just as it
// would be if created normally in a workspace. No state information
// of the current C in the workspace is preserved.
frozenNode =
TransientNodeData.createNodeData(currentNode(), qname, node.getPrimaryTypeName(), node.getQPath()
.getIndex());
contextNodes.push(null);
changesLog.add(ItemState.createAddedState(frozenNode));
}
else if (action == OnParentVersionAction.COMPUTE)
{
// 8.2.11.4 COMPUTE
// On checkin of N, a new node C will be created and placed in version
// storage as a child of VN. This new C will be initialized by some
// procedure defined for that type of child node.
// [PN] 10.04.06 Creating simply as new node with same name and same node
// type
frozenNode =
TransientNodeData.createNodeData(currentNode(), qname, node.getPrimaryTypeName(), node.getQPath()
.getIndex());
contextNodes.push(null);
changesLog.add(ItemState.createAddedState(frozenNode));
}
else
{
throw new RepositoryException("Unknown onParentVersion type " + action);
}
}
@Override
protected void leaving(PropertyData property, int level) throws RepositoryException
{
}
@Override
protected void leaving(NodeData node, int level) throws RepositoryException
{
contextNodes.pop();
}
private NodeData currentNode()
{
return contextNodes.peek();
}
@Override
public SessionDataManager getDataManager()
{
return dataManager;
}
}