org.apache.jackrabbit.jcr2spi.state.SessionItemStateManager Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.jcr2spi.state;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import javax.jcr.AccessDeniedException;
import javax.jcr.InvalidItemStateException;
import javax.jcr.ItemExistsException;
import javax.jcr.PropertyType;
import javax.jcr.ReferentialIntegrityException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.version.VersionException;
import org.apache.jackrabbit.jcr2spi.SessionImpl;
import org.apache.jackrabbit.jcr2spi.hierarchy.NodeEntry;
import org.apache.jackrabbit.jcr2spi.hierarchy.PropertyEntry;
import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeType;
import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeTypeProvider;
import org.apache.jackrabbit.jcr2spi.nodetype.ItemDefinitionProvider;
import org.apache.jackrabbit.jcr2spi.operation.AddNode;
import org.apache.jackrabbit.jcr2spi.operation.AddProperty;
import org.apache.jackrabbit.jcr2spi.operation.IgnoreOperation;
import org.apache.jackrabbit.jcr2spi.operation.Move;
import org.apache.jackrabbit.jcr2spi.operation.Operation;
import org.apache.jackrabbit.jcr2spi.operation.OperationVisitor;
import org.apache.jackrabbit.jcr2spi.operation.Remove;
import org.apache.jackrabbit.jcr2spi.operation.ReorderNodes;
import org.apache.jackrabbit.jcr2spi.operation.SetMixin;
import org.apache.jackrabbit.jcr2spi.operation.SetTree;
import org.apache.jackrabbit.jcr2spi.operation.SetPrimaryType;
import org.apache.jackrabbit.jcr2spi.operation.SetPropertyValue;
import org.apache.jackrabbit.jcr2spi.operation.TransientOperationVisitor;
import org.apache.jackrabbit.jcr2spi.util.ReferenceChangeTracker;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.QNodeDefinition;
import org.apache.jackrabbit.spi.QPropertyDefinition;
import org.apache.jackrabbit.spi.QValue;
import org.apache.jackrabbit.spi.QValueFactory;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SessionItemStateManager
...
*/
public class SessionItemStateManager extends TransientOperationVisitor implements UpdatableItemStateManager {
private static Logger log = LoggerFactory.getLogger(SessionItemStateManager.class);
/**
* State manager that allows updates
*/
private final UpdatableItemStateManager workspaceItemStateMgr;
/**
* State manager for the transient items
*/
private final TransientItemStateManager transientStateMgr;
private final ItemStateValidator validator;
private final QValueFactory qValueFactory;
private final SessionImpl mgrProvider;
/**
* Creates a new SessionItemStateManager
instance.
*
* @param workspaceItemStateMgr
* @param validator
* @param qValueFactory
* @param isf
* @param mgrProvider
*/
public SessionItemStateManager(UpdatableItemStateManager workspaceItemStateMgr,
ItemStateValidator validator,
QValueFactory qValueFactory,
ItemStateFactory isf, SessionImpl mgrProvider) {
this.workspaceItemStateMgr = workspaceItemStateMgr;
this.transientStateMgr = new TransientItemStateManager();
isf.addCreationListener(transientStateMgr);
this.validator = validator;
this.qValueFactory = qValueFactory;
this.mgrProvider = mgrProvider;
}
/**
* @return true
if this manager has any transient state;
* false
otherwise.
*/
public boolean hasPendingChanges() {
return transientStateMgr.hasPendingChanges();
}
/**
* This will save state
and all descendants items of
* state
that are transiently modified in a single step. If
* this operation fails, no item will have been saved.
*
* @param state the root state of the update operation
*/
public void save(ItemState state) throws ReferentialIntegrityException,
InvalidItemStateException, RepositoryException {
// shortcut, if no modifications are present
if (!transientStateMgr.hasPendingChanges()) {
return;
}
// collect the changes to be saved
ChangeLog changeLog = transientStateMgr.getChangeLog(state, true);
if (!changeLog.isEmpty()) {
// only pass changelog if there are transient modifications available
// for the specified item and its descendants.
workspaceItemStateMgr.execute(changeLog);
// remove states and operations just processed from the transient ISM
transientStateMgr.dispose(changeLog);
// now its save to clear the changeLog
changeLog.reset();
}
}
/**
* This will undo all changes made to state
and descendant
* items of state
inside this item state manager.
*
* @param itemState the root state of the cancel operation.
* @throws ConstraintViolationException
* @throws RepositoryException if undoing changes made to state
* and descendant items is not a closed set of changes. That is, at least
* another item needs to be canceled as well in another sub-tree.
*/
public void undo(ItemState itemState) throws ConstraintViolationException, RepositoryException {
// short cut
if (!transientStateMgr.hasPendingChanges()) {
return;
}
ChangeLog changeLog = transientStateMgr.getChangeLog(itemState, false);
if (!changeLog.isEmpty()) {
// let changelog revert all changes
changeLog.undo();
// remove transient states and related operations from the t-statemanager
transientStateMgr.dispose(changeLog);
changeLog.reset();
}
}
/**
* Adjust references at the end of a successful
* {@link Session#importXML(String, InputStream, int) XML import}.
*
* @param refTracker
* @throws ConstraintViolationException
* @throws RepositoryException
*/
public void adjustReferences(ReferenceChangeTracker refTracker) throws ConstraintViolationException, RepositoryException {
Iterator it = refTracker.getReferences();
while (it.hasNext()) {
PropertyState propState = it.next();
boolean modified = false;
QValue[] values = propState.getValues();
QValue[] newVals = new QValue[values.length];
for (int i = 0; i < values.length; i++) {
QValue val = values[i];
QValue adjusted = refTracker.getMappedReference(val, qValueFactory);
if (adjusted != null) {
newVals[i] = adjusted;
modified = true;
} else {
// reference doesn't need adjusting, just copy old value
newVals[i] = val;
}
}
if (modified) {
int options = ItemStateValidator.CHECK_LOCK |
ItemStateValidator.CHECK_VERSIONING |
ItemStateValidator.CHECK_CONSTRAINTS;
setPropertyStateValue(propState, newVals, PropertyType.REFERENCE, options);
}
}
// make sure all entries are removed
refTracker.clear();
}
//------------------------------------------< UpdatableItemStateManager >---
/**
* {@inheritDoc}
* @see UpdatableItemStateManager#execute(Operation)
*/
public void execute(Operation operation) throws RepositoryException {
operation.accept(this);
}
/**
* {@inheritDoc}
* @see UpdatableItemStateManager#execute(ChangeLog)
*/
public void execute(ChangeLog changes) throws RepositoryException {
throw new UnsupportedOperationException("Not implemented for SessionItemStateManager");
}
/**
* {@inheritDoc}
* @see UpdatableItemStateManager#dispose()
*/
public void dispose() {
// discard all transient changes
transientStateMgr.dispose();
// dispose our (i.e. 'local') state manager
workspaceItemStateMgr.dispose();
}
//---------------------------------------------------< OperationVisitor >---
/**
* @see OperationVisitor#visit(AddNode)
*/
public void visit(AddNode operation) throws LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
NodeState parent = operation.getParentState();
ItemDefinitionProvider defProvider = mgrProvider.getItemDefinitionProvider();
QNodeDefinition def = defProvider.getQNodeDefinition(parent.getAllNodeTypeNames(), operation.getNodeName(), operation.getNodeTypeName());
List newStates = addNodeState(parent, operation.getNodeName(), operation.getNodeTypeName(), operation.getUuid(), def, operation.getOptions());
operation.addedState(newStates);
if (!(operation instanceof IgnoreOperation)) {
transientStateMgr.addOperation(operation);
}
}
/**
* @see OperationVisitor#visit(AddProperty)
*/
public void visit(AddProperty operation) throws ValueFormatException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
NodeState parent = operation.getParentState();
Name propertyName = operation.getPropertyName();
QPropertyDefinition pDef = operation.getDefinition();
int targetType = pDef.getRequiredType();
if (targetType == PropertyType.UNDEFINED) {
targetType = operation.getPropertyType();
if (targetType == PropertyType.UNDEFINED) {
targetType = PropertyType.STRING;
}
}
addPropertyState(parent, propertyName, targetType, operation.getValues(), pDef, operation.getOptions());
if (!(operation instanceof IgnoreOperation)) {
transientStateMgr.addOperation(operation);
}
}
/**
* @see OperationVisitor#visit(org.apache.jackrabbit.jcr2spi.operation.SetTree)
*/
public void visit(SetTree operation) throws RepositoryException {
transientStateMgr.addOperation(operation);
}
/**
* @see OperationVisitor#visit(Move)
*/
public void visit(Move operation) throws LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
// retrieve states and assert they are modifiable
NodeState srcState = operation.getSourceState();
NodeState srcParent = operation.getSourceParentState();
NodeState destParent = operation.getDestinationParentState();
// state validation: move-Source can be removed from old/added to new parent
validator.checkRemoveItem(srcState, operation.getOptions());
validator.checkAddNode(destParent, operation.getDestinationName(),
srcState.getNodeTypeName(), operation.getOptions());
// retrieve applicable definition at the new place
ItemDefinitionProvider defProvider = mgrProvider.getItemDefinitionProvider();
QNodeDefinition newDefinition = defProvider.getQNodeDefinition(destParent.getAllNodeTypeNames(), operation.getDestinationName(), srcState.getNodeTypeName());
// perform the move (modifying states)
srcParent.moveChildNodeEntry(destParent, srcState, operation.getDestinationName(), newDefinition);
// remember operation
transientStateMgr.addOperation(operation);
}
/**
* @see OperationVisitor#visit(Remove)
*/
public void visit(Remove operation) throws ConstraintViolationException, AccessDeniedException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
ItemState state = operation.getRemoveState();
removeItemState(state, operation.getOptions());
transientStateMgr.addOperation(operation);
operation.getParentState().markModified();
}
/**
* @see OperationVisitor#visit(SetMixin)
*/
public void visit(SetMixin operation) throws ConstraintViolationException, AccessDeniedException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
// NOTE: nodestate is only modified upon save of the changes!
Name[] mixinNames = operation.getMixinNames();
NodeState nState = operation.getNodeState();
NodeEntry nEntry = nState.getNodeEntry();
// assert the existence of the property entry and set the array of
// mixinNames to be set on the corresponding property state
PropertyEntry mixinEntry = nEntry.getPropertyEntry(NameConstants.JCR_MIXINTYPES);
if (mixinNames.length > 0) {
// update/create corresponding property state
if (mixinEntry != null) {
// execute value of existing property
PropertyState pState = mixinEntry.getPropertyState();
setPropertyStateValue(pState, getQValues(mixinNames, qValueFactory), PropertyType.NAME, operation.getOptions());
} else {
// create new jcr:mixinTypes property
ItemDefinitionProvider defProvider = mgrProvider.getItemDefinitionProvider();
QPropertyDefinition pd = defProvider.getQPropertyDefinition(nState.getAllNodeTypeNames(), NameConstants.JCR_MIXINTYPES, PropertyType.NAME, true);
QValue[] mixinValue = getQValues(mixinNames, qValueFactory);
addPropertyState(nState, pd.getName(), pd.getRequiredType(), mixinValue, pd, operation.getOptions());
}
nState.markModified();
transientStateMgr.addOperation(operation);
} else if (mixinEntry != null) {
// remove the jcr:mixinTypes property state if already present
PropertyState pState = mixinEntry.getPropertyState();
removeItemState(pState, operation.getOptions());
nState.markModified();
transientStateMgr.addOperation(operation);
} // else: empty Name array and no mixin-prop-entry (should not occur)
}
/**
* @see OperationVisitor#visit(SetPrimaryType)
*/
public void visit(SetPrimaryType operation) throws ConstraintViolationException, RepositoryException {
// NOTE: nodestate is only modified upon save of the changes!
Name primaryName = operation.getPrimaryTypeName();
NodeState nState = operation.getNodeState();
NodeEntry nEntry = nState.getNodeEntry();
// detect obvious node type conflicts
EffectiveNodeTypeProvider entProvider = mgrProvider.getEffectiveNodeTypeProvider();
// try to build new effective node type (will throw in case of conflicts)
Name[] mixins = nState.getMixinTypeNames();
List all = new ArrayList(Arrays.asList(mixins));
all.add(primaryName);
// retrieve effective to assert validity of arguments
entProvider.getEffectiveNodeType(all.toArray(new Name[all.size()]));
// modify the value of the jcr:primaryType property entry without
// changing the node state itself
PropertyEntry pEntry = nEntry.getPropertyEntry(NameConstants.JCR_PRIMARYTYPE);
PropertyState pState = pEntry.getPropertyState();
setPropertyStateValue(pState, getQValues(new Name[] {primaryName}, qValueFactory), PropertyType.NAME, operation.getOptions());
// mark the affected node state modified and remember the operation
nState.markModified();
transientStateMgr.addOperation(operation);
}
/**
* @see OperationVisitor#visit(SetPropertyValue)
*/
public void visit(SetPropertyValue operation) throws ValueFormatException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
PropertyState pState = operation.getPropertyState();
setPropertyStateValue(pState, operation.getValues(), operation.getValueType(), operation.getOptions());
transientStateMgr.addOperation(operation);
}
/**
* @see OperationVisitor#visit(ReorderNodes)
*/
public void visit(ReorderNodes operation) throws ConstraintViolationException, AccessDeniedException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
NodeState parent = operation.getParentState();
// modify the parent node state
parent.reorderChildNodeEntries(operation.getInsertNode(), operation.getBeforeNode());
// remember the operation
transientStateMgr.addOperation(operation);
}
//--------------------------------------------< Internal State Handling >---
/**
*
* @param parent
* @param propertyName
* @param propertyType
* @param values
* @param pDef
* @param options int used to validate the given params. Note, that the options
* differ depending if the 'addProperty' is called regularly or to create
* auto-created (or protected) properties.
* @throws LockException
* @throws ConstraintViolationException
* @throws AccessDeniedException
* @throws ItemExistsException
* @throws NoSuchNodeTypeException
* @throws UnsupportedRepositoryOperationException
* @throws VersionException
* @throws RepositoryException
*/
private PropertyState addPropertyState(NodeState parent, Name propertyName,
int propertyType, QValue[] values,
QPropertyDefinition pDef, int options)
throws LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
validator.checkAddProperty(parent, propertyName, pDef, options);
// create property state
return transientStateMgr.createNewPropertyState(propertyName, parent, pDef, values, propertyType);
}
private List addNodeState(NodeState parent, Name nodeName, Name nodeTypeName,
String uuid, QNodeDefinition definition, int options)
throws RepositoryException, ConstraintViolationException, AccessDeniedException,
UnsupportedRepositoryOperationException, NoSuchNodeTypeException,
ItemExistsException, VersionException {
// check if add node is possible. note, that the options differ if
// the 'addNode' is called from inside a regular add-node to create
// autocreated child nodes that may be 'protected'.
validator.checkAddNode(parent, nodeName, nodeTypeName, options);
// a new NodeState doesn't have mixins defined yet -> ent is ent of primarytype
EffectiveNodeType ent = mgrProvider.getEffectiveNodeTypeProvider().getEffectiveNodeType(nodeTypeName);
if (nodeTypeName == null) {
// no primary node type specified,
// try default primary type from definition
nodeTypeName = definition.getDefaultPrimaryType();
if (nodeTypeName == null) {
String msg = "No applicable node type could be determined for " + nodeName;
log.debug(msg);
throw new ConstraintViolationException(msg);
}
}
List addedStates = new ArrayList();
// create new nodeState. NOTE, that the uniqueID is not added to the
// state for consistency between 'addNode' and importXML
NodeState nodeState = transientStateMgr.createNewNodeState(nodeName, null, nodeTypeName, definition, parent);
addedStates.add(nodeState);
if (uuid != null) {
QValue[] value = getQValues(uuid, qValueFactory);
ItemDefinitionProvider defProvider = mgrProvider.getItemDefinitionProvider();
QPropertyDefinition pDef = defProvider.getQPropertyDefinition(NameConstants.MIX_REFERENCEABLE, NameConstants.JCR_UUID, PropertyType.STRING, false);
addedStates.add(addPropertyState(nodeState, NameConstants.JCR_UUID, PropertyType.STRING, value, pDef, 0));
}
// add 'auto-create' properties defined in node type
for (QPropertyDefinition pd : ent.getAutoCreateQPropertyDefinitions()) {
if (!nodeState.hasPropertyName(pd.getName())) {
QValue[] autoValue = computeSystemGeneratedPropertyValues(nodeState, pd);
if (autoValue != null) {
int propOptions = ItemStateValidator.CHECK_NONE;
// execute 'addProperty' without adding operation.
addedStates.add(addPropertyState(nodeState, pd.getName(), pd.getRequiredType(), autoValue, pd, propOptions));
}
}
}
// recursively add 'auto-create' child nodes defined in node type
for (QNodeDefinition nd : ent.getAutoCreateQNodeDefinitions()) {
// execute 'addNode' without adding the operation.
int opt = ItemStateValidator.CHECK_LOCK | ItemStateValidator.CHECK_COLLISION;
addedStates.addAll(addNodeState(nodeState, nd.getName(), nd.getDefaultPrimaryType(), null, nd, opt));
}
return addedStates;
}
private void removeItemState(ItemState itemState, int options) throws RepositoryException {
validator.checkRemoveItem(itemState, options);
// recursively remove the given state and all child states.
boolean success = false;
try {
itemState.getHierarchyEntry().transientRemove();
success = true;
} finally {
if (!success) {
// TODO: TOBEFIXED undo state modifications
}
}
}
/**
*
* @param propState
* @param iva
* @param valueType
* @throws ValueFormatException
* @throws LockException
* @throws ConstraintViolationException
* @throws AccessDeniedException
* @throws ItemExistsException
* @throws UnsupportedRepositoryOperationException
* @throws VersionException
* @throws RepositoryException
*/
private void setPropertyStateValue(PropertyState propState, QValue[] iva,
int valueType, int options)
throws ValueFormatException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
// assert that the property can be modified.
validator.checkSetProperty(propState, options);
propState.setValues(iva, valueType);
}
/**
* Computes the values of well-known system (i.e. protected) properties
* as well as auto-created properties which define default value(s)
*
* @param parent
* @param def
* @return the computed values
*/
private QValue[] computeSystemGeneratedPropertyValues(NodeState parent,
QPropertyDefinition def)
throws RepositoryException {
QValue[] genValues = null;
QValue[] qDefaultValues = def.getDefaultValues();
if (qDefaultValues != null && qDefaultValues.length > 0) {
genValues = qDefaultValues;
} else if (def.isAutoCreated()) {
// handle known predefined nodetypes that declare auto-created
// properties without default values
Name declaringNT = def.getDeclaringNodeType();
Name name = def.getName();
if (NameConstants.JCR_PRIMARYTYPE.equals(name)) {
// jcr:primaryType property
genValues = new QValue[]{qValueFactory.create(parent.getNodeTypeName())};
} else if (NameConstants.JCR_MIXINTYPES.equals(name)) {
// jcr:mixinTypes property
Name[] mixins = parent.getMixinTypeNames();
genValues = getQValues(mixins, qValueFactory);
} else if (NameConstants.JCR_CREATED.equals(name)
&& (NameConstants.MIX_CREATED.equals(declaringNT) ||
NameConstants.NT_HIERARCHYNODE.equals(declaringNT))) {
// jcr:created property of a mix:created or nt:hierarchyNode
genValues = new QValue[]{qValueFactory.create(Calendar.getInstance())};
} else if (NameConstants.JCR_CREATEDBY.equals(name)
&& NameConstants.MIX_CREATED.equals(declaringNT)) {
// jcr:createdBy property of a mix:created
genValues = new QValue[]{qValueFactory.create(mgrProvider.getUserID(), PropertyType.STRING)};
} else if (NameConstants.JCR_LASTMODIFIED.equals(name)
&& NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) {
// jcr:lastModified property of a mix:lastModified
genValues = new QValue[]{qValueFactory.create(Calendar.getInstance())};
} else if (NameConstants.JCR_LASTMODIFIEDBY.equals(name)
&& NameConstants.MIX_LASTMODIFIED.equals(declaringNT)) {
// jcr:lastModifiedBy property of a mix:lastModified
genValues = new QValue[]{qValueFactory.create(mgrProvider.getUserID(), PropertyType.STRING)};
} else {
// ask the SPI implementation for advice
genValues = qValueFactory.computeAutoValues(def);
}
}
return genValues;
}
/**
* @param qNames
* @param factory
* @return An array of QValue objects from the given Name
s
*/
private static QValue[] getQValues(Name[] qNames, QValueFactory factory) throws RepositoryException {
QValue[] ret = new QValue[qNames.length];
for (int i = 0; i < qNames.length; i++) {
ret[i] = factory.create(qNames[i]);
}
return ret;
}
private static QValue[] getQValues(String uniqueID, QValueFactory factory) throws RepositoryException {
return new QValue[] {factory.create(uniqueID, PropertyType.STRING)};
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy