Author: jukka
Date: Thu Aug 5 09:43:08 2010
New Revision: 982520
URL: http://svn.apache.org/viewvc?rev=982520&view=rev
Log:
JCR-890: concurrent read-only access to a session
Turn addMixin() and removeMixin() into SessionOperations
Added:
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/AddMixinOperation.java
(with props)
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java
(with props)
Modified:
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/AddMixinOperation.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/AddMixinOperation.java?rev=982520&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/AddMixinOperation.java
(added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/AddMixinOperation.java
Thu Aug 5 09:43:08 2010
@@ -0,0 +1,171 @@
+/*
+ * 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.core;
+
+import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT;
+import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS;
+import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD;
+import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK;
+import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_SIMPLE_VERSIONABLE;
+import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NodeDefinition;
+import javax.jcr.nodetype.PropertyDefinition;
+
+import org.apache.jackrabbit.core.nodetype.EffectiveNodeType;
+import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException;
+import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
+import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
+import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
+import org.apache.jackrabbit.core.security.authorization.Permission;
+import org.apache.jackrabbit.core.session.SessionContext;
+import org.apache.jackrabbit.core.session.SessionOperation;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl;
+import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl;
+
+/**
+ * Session operation for adding a mixin type to a node.
+ */
+class AddMixinOperation implements SessionOperation<Object> {
+
+ private final NodeImpl node;
+
+ private final Name mixinName;
+
+ public AddMixinOperation(NodeImpl node, Name mixinName) {
+ this.node = node;
+ this.mixinName = mixinName;
+ }
+
+ public Object perform(SessionContext context) throws RepositoryException {
+ SessionImpl session = context.getSessionImpl();
+
+ int permissions = Permission.NODE_TYPE_MNGMT;
+ // special handling of mix:(simple)versionable. since adding the
+ // mixin alters the version storage jcr:versionManagement privilege
+ // is required in addition.
+ if (MIX_VERSIONABLE.equals(mixinName)
+ || MIX_SIMPLE_VERSIONABLE.equals(mixinName)) {
+ permissions |= Permission.VERSION_MNGMT;
+ }
+ session.getValidator().checkModify(
+ node,
+ CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD,
+ permissions);
+
+ NodeTypeManagerImpl ntMgr = session.getNodeTypeManager();
+ NodeTypeImpl mixin = ntMgr.getNodeType(mixinName);
+ if (!mixin.isMixin()) {
+ throw new RepositoryException(
+ session.getJCRName(mixinName) + " is not a mixin node type");
+ }
+
+ Name primaryTypeName = node.getNodeState().getNodeTypeName();
+ NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName);
+ if (primaryType.isDerivedFrom(mixinName)) {
+ // new mixin is already included in primary type
+ return this;
+ }
+
+ // build effective node type of mixin's & primary type in order
+ // to detect conflicts
+ NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry();
+ EffectiveNodeType entExisting;
+ try {
+ // existing mixin's
+ Set<Name> mixins = new HashSet<Name>(
+ node.getNodeState().getMixinTypeNames());
+
+ // build effective node type representing primary type including
+ // existing mixin's
+ entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins);
+ if (entExisting.includesNodeType(mixinName)) {
+ // new mixin is already included in existing mixin type(s)
+ return this;
+ }
+
+ // add new mixin
+ mixins.add(mixinName);
+ // try to build new effective node type (will throw in case
+ // of conflicts)
+ ntReg.getEffectiveNodeType(primaryTypeName, mixins);
+ } catch (NodeTypeConflictException e) {
+ throw new ConstraintViolationException(e.getMessage(), e);
+ }
+
+ // do the actual modifications implied by the new mixin;
+ // try to revert the changes in case an exception occurs
+ try {
+ // modify the state of this node
+ NodeState thisState =
+ (NodeState) node.getOrCreateTransientItemState();
+ // add mixin name
+ Set<Name> mixins = new HashSet<Name>(thisState.getMixinTypeNames());
+ mixins.add(mixinName);
+ thisState.setMixinTypeNames(mixins);
+
+ // set jcr:mixinTypes property
+ node.setMixinTypesProperty(mixins);
+
+ // add 'auto-create' properties defined in mixin type
+ for (PropertyDefinition aPda : mixin.getAutoCreatedPropertyDefinitions()) {
+ PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda;
+ // make sure that the property is not already defined
+ // by primary type or existing mixin's
+ NodeTypeImpl declaringNT =
+ (NodeTypeImpl) pd.getDeclaringNodeType();
+ if (!entExisting.includesNodeType(declaringNT.getQName())) {
+ node.createChildProperty(
+ pd.unwrap().getName(), pd.getRequiredType(), pd);
+ }
+ }
+
+ // recursively add 'auto-create' child nodes defined in mixin type
+ for (NodeDefinition aNda : mixin.getAutoCreatedNodeDefinitions()) {
+ NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda;
+ // make sure that the child node is not already defined
+ // by primary type or existing mixin's
+ NodeTypeImpl declaringNT =
+ (NodeTypeImpl) nd.getDeclaringNodeType();
+ if (!entExisting.includesNodeType(declaringNT.getQName())) {
+ node.createChildNode(
+ nd.unwrap().getName(),
+ (NodeTypeImpl) nd.getDefaultPrimaryType(),
+ null);
+ }
+ }
+ } catch (RepositoryException re) {
+ // try to undo the modifications by removing the mixin
+ try {
+ node.removeMixin(mixinName);
+ } catch (RepositoryException re1) {
+ // silently ignore & fall through
+ }
+ throw re;
+ }
+
+ return this;
+ }
+
+}
\ No newline at end of file
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/AddMixinOperation.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java?rev=982520&r1=982519&r2=982520&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
(original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
Thu Aug 5 09:43:08 2010
@@ -24,11 +24,9 @@ import java.util.BitSet;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import javax.jcr.AccessDeniedException;
@@ -697,7 +695,7 @@ public class NodeImpl extends ItemImpl i
setRemoved();
}
- private void setMixinTypesProperty(Set<Name> mixinNames) throws RepositoryException
{
+ void setMixinTypesProperty(Set<Name> mixinNames) throws RepositoryException {
NodeState thisState = data.getNodeState();
// get or create jcr:mixinTypes property
PropertyImpl prop;
@@ -905,102 +903,8 @@ public class NodeImpl extends ItemImpl i
*
* @see Node#addMixin(String)
*/
- public void addMixin(Name mixinName)
- throws NoSuchNodeTypeException, VersionException,
- ConstraintViolationException, LockException, RepositoryException {
- // check state of this instance
- sanityCheck();
-
- int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT
- | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD;
- int permissions = Permission.NODE_TYPE_MNGMT;
- // special handling of mix:(simple)versionable. since adding the mixin alters
- // the version storage jcr:versionManagement privilege is required
- // in addition.
- if (NameConstants.MIX_VERSIONABLE.equals(mixinName)
- || NameConstants.MIX_SIMPLE_VERSIONABLE.equals(mixinName)) {
- permissions |= Permission.VERSION_MNGMT;
- }
- session.getValidator().checkModify(this, options, permissions);
-
- NodeTypeManagerImpl ntMgr = session.getNodeTypeManager();
- NodeTypeImpl mixin = ntMgr.getNodeType(mixinName);
- if (!mixin.isMixin()) {
- throw new RepositoryException(mixinName + ": not a mixin node type");
- }
-
- final Name primaryTypeName = data.getNodeState().getNodeTypeName();
- NodeTypeImpl primaryType = ntMgr.getNodeType(primaryTypeName);
- if (primaryType.isDerivedFrom(mixinName)) {
- // new mixin is already included in primary type
- return;
- }
-
- // build effective node type of mixin's & primary type in order to detect conflicts
- NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry();
- EffectiveNodeType entExisting;
- try {
- // existing mixin's
- Set<Name> mixins = new HashSet<Name>(data.getNodeState().getMixinTypeNames());
-
- // build effective node type representing primary type including existing mixin's
- entExisting = ntReg.getEffectiveNodeType(primaryTypeName, mixins);
- if (entExisting.includesNodeType(mixinName)) {
- // new mixin is already included in existing mixin type(s)
- return;
- }
-
- // add new mixin
- mixins.add(mixinName);
- // try to build new effective node type (will throw in case of conflicts)
- ntReg.getEffectiveNodeType(primaryTypeName, mixins);
- } catch (NodeTypeConflictException e) {
- throw new ConstraintViolationException(e.getMessage(), e);
- }
-
- // do the actual modifications implied by the new mixin;
- // try to revert the changes in case an exception occurs
- try {
- // modify the state of this node
- NodeState thisState = (NodeState) getOrCreateTransientItemState();
- // add mixin name
- Set<Name> mixins = new HashSet<Name>(thisState.getMixinTypeNames());
- mixins.add(mixinName);
- thisState.setMixinTypeNames(mixins);
-
- // set jcr:mixinTypes property
- setMixinTypesProperty(mixins);
-
- // add 'auto-create' properties defined in mixin type
- for (PropertyDefinition aPda : mixin.getAutoCreatedPropertyDefinitions()) {
- PropertyDefinitionImpl pd = (PropertyDefinitionImpl) aPda;
- // make sure that the property is not already defined by primary type
- // or existing mixin's
- NodeTypeImpl declaringNT = (NodeTypeImpl) pd.getDeclaringNodeType();
- if (!entExisting.includesNodeType(declaringNT.getQName())) {
- createChildProperty(pd.unwrap().getName(), pd.getRequiredType(), pd);
- }
- }
-
- // recursively add 'auto-create' child nodes defined in mixin type
- for (NodeDefinition aNda : mixin.getAutoCreatedNodeDefinitions()) {
- NodeDefinitionImpl nd = (NodeDefinitionImpl) aNda;
- // make sure that the child node is not already defined by primary type
- // or existing mixin's
- NodeTypeImpl declaringNT = (NodeTypeImpl) nd.getDeclaringNodeType();
- if (!entExisting.includesNodeType(declaringNT.getQName())) {
- createChildNode(nd.unwrap().getName(), (NodeTypeImpl) nd.getDefaultPrimaryType(),
null);
- }
- }
- } catch (RepositoryException re) {
- // try to undo the modifications by removing the mixin
- try {
- removeMixin(mixinName);
- } catch (RepositoryException re1) {
- // silently ignore & fall through
- }
- throw re;
- }
+ public void addMixin(Name mixinName) throws RepositoryException {
+ perform(new AddMixinOperation(this, mixinName));
}
/**
@@ -1009,207 +913,8 @@ public class NodeImpl extends ItemImpl i
*
* @see Node#removeMixin(String)
*/
- public void removeMixin(Name mixinName)
- throws NoSuchNodeTypeException, VersionException,
- ConstraintViolationException, LockException, RepositoryException {
- // check state of this instance
- sanityCheck();
-
- int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CHECKED_OUT
- | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD;
- int permissions = Permission.NODE_TYPE_MNGMT;
- session.getValidator().checkModify(this, options, permissions);
-
- // check if mixin is assigned
- final NodeState state = data.getNodeState();
- if (!state.getMixinTypeNames().contains(mixinName)) {
- throw new NoSuchNodeTypeException();
- }
-
- NodeTypeManagerImpl ntMgr = session.getNodeTypeManager();
- NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry();
-
- // build effective node type of remaining mixin's & primary type
- Set<Name> remainingMixins = new HashSet<Name>(state.getMixinTypeNames());
- // remove name of target mixin
- remainingMixins.remove(mixinName);
- EffectiveNodeType entResulting;
- try {
- // build effective node type representing primary type including remaining mixin's
- entResulting = ntReg.getEffectiveNodeType(
- state.getNodeTypeName(), remainingMixins);
- } catch (NodeTypeConflictException e) {
- throw new ConstraintViolationException(e.getMessage(), e);
- }
-
- /**
- * mix:referenceable needs special handling because it has
- * special semantics:
- * it can only be removed if there no more references to this node
- */
- NodeTypeImpl mixin = ntMgr.getNodeType(mixinName);
- if ((NameConstants.MIX_REFERENCEABLE.equals(mixinName)
- || mixin.isDerivedFrom(NameConstants.MIX_REFERENCEABLE))
- && !entResulting.includesNodeType(NameConstants.MIX_REFERENCEABLE))
{
- // removing this mixin would effectively remove mix:referenceable:
- // make sure no references exist
- PropertyIterator iter = getReferences();
- if (iter.hasNext()) {
- throw new ConstraintViolationException(mixinName + " can not be removed:
the node is being referenced"
- + " through at least one property of type REFERENCE");
- }
- }
-
- /*
- * mix:lockable: the mixin cannot be removed if the node is currently
- * locked even if the editing session is the lock holder.
- */
- if ((NameConstants.MIX_LOCKABLE.equals(mixinName)
- || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE))
- && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE)
- && isLocked()) {
- throw new ConstraintViolationException(mixinName + " can not be removed: the
node is locked.");
- }
-
- NodeState thisState = (NodeState) getOrCreateTransientItemState();
-
- // collect information about properties and nodes which require
- // further action as a result of the mixin removal;
- // we need to do this *before* actually changing the assigned mixin types,
- // otherwise we wouldn't be able to retrieve the current definition
- // of an item.
- Map<PropertyId, PropertyDefinition> affectedProps = new HashMap<PropertyId,
PropertyDefinition>();
- Map<ChildNodeEntry, NodeDefinition> affectedNodes = new HashMap<ChildNodeEntry,
NodeDefinition>();
- try {
- Set<Name> names = thisState.getPropertyNames();
- for (Name propName : names) {
- PropertyId propId = new PropertyId(thisState.getNodeId(), propName);
- PropertyState propState = (PropertyState) stateMgr.getItemState(propId);
- PropertyDefinition oldDef = itemMgr.getDefinition(propState);
- // check if property has been defined by mixin type (or one of its supertypes)
- NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType();
- if (!entResulting.includesNodeType(declaringNT.getQName())) {
- // the resulting effective node type doesn't include the
- // node type that declared this property
- affectedProps.put(propId, oldDef);
- }
- }
-
- List<ChildNodeEntry> entries = thisState.getChildNodeEntries();
- for (ChildNodeEntry entry : entries) {
- NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId());
- NodeDefinition oldDef = itemMgr.getDefinition(nodeState);
- // check if node has been defined by mixin type (or one of its supertypes)
- NodeTypeImpl declaringNT = (NodeTypeImpl) oldDef.getDeclaringNodeType();
- if (!entResulting.includesNodeType(declaringNT.getQName())) {
- // the resulting effective node type doesn't include the
- // node type that declared this child node
- affectedNodes.put(entry, oldDef);
- }
- }
- } catch (ItemStateException e) {
- throw new RepositoryException("Internal Error: Failed to determine effect of
removing mixin " + session.getJCRName(mixinName), e);
- }
-
- // modify the state of this node
- thisState.setMixinTypeNames(remainingMixins);
- // set jcr:mixinTypes property
- setMixinTypesProperty(remainingMixins);
-
- // process affected nodes & properties:
- // 1. try to redefine item based on the resulting
- // new effective node type (see JCR-2130)
- // 2. remove item if 1. fails
- boolean success = false;
- try {
- for (PropertyId id : affectedProps.keySet()) {
- PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id);
- PropertyDefinition oldDef = affectedProps.get(id);
-
- if (oldDef.isProtected()) {
- // remove 'orphaned' protected properties immediately
- removeChildProperty(id.getName());
- continue;
- }
- // try to find new applicable definition first and
- // redefine property if possible (JCR-2130)
- try {
- PropertyDefinitionImpl newDef = getApplicablePropertyDefinition(
- id.getName(), prop.getType(),
- oldDef.isMultiple(), false);
- if (newDef.getRequiredType() != PropertyType.UNDEFINED
- && newDef.getRequiredType() != prop.getType()) {
- // value conversion required
- if (oldDef.isMultiple()) {
- // convert value
- Value[] values =
- ValueHelper.convert(
- prop.getValues(),
- newDef.getRequiredType(),
- session.getValueFactory());
- // redefine property
- prop.onRedefine(newDef.unwrap());
- // set converted values
- prop.setValue(values);
- } else {
- // convert value
- Value value =
- ValueHelper.convert(
- prop.getValue(),
- newDef.getRequiredType(),
- session.getValueFactory());
- // redefine property
- prop.onRedefine(newDef.unwrap());
- // set converted values
- prop.setValue(value);
- }
- } else {
- // redefine property
- prop.onRedefine(newDef.unwrap());
- }
- } catch (ValueFormatException vfe) {
- // value conversion failed, remove it
- removeChildProperty(id.getName());
- } catch (ConstraintViolationException cve) {
- // no suitable definition found for this property,
- // remove it
- removeChildProperty(id.getName());
- }
- }
-
- for (ChildNodeEntry entry : affectedNodes.keySet()) {
- NodeState nodeState = (NodeState) stateMgr.getItemState(entry.getId());
- NodeImpl node = (NodeImpl) itemMgr.getItem(entry.getId());
- NodeDefinition oldDef = affectedNodes.get(entry);
-
- if (oldDef.isProtected()) {
- // remove 'orphaned' protected child node immediately
- removeChildNode(entry.getId());
- continue;
- }
-
- // try to find new applicable definition first and
- // redefine node if possible (JCR-2130)
- try {
- NodeDefinitionImpl newDef = getApplicableChildNodeDefinition(
- entry.getName(),
- nodeState.getNodeTypeName());
- // redefine node
- node.onRedefine(newDef.unwrap());
- } catch (ConstraintViolationException cve) {
- // no suitable definition found for this child node,
- // remove it
- removeChildNode(entry.getId());
- }
- }
- success = true;
- } catch (ItemStateException e) {
- throw new RepositoryException("Failed to clean up child items defined by removed
mixin " + session.getJCRName(mixinName), e);
- } finally {
- if (!success) {
- // TODO JCR-1914: revert any changes made so far
- }
- }
+ public void removeMixin(Name mixinName) throws RepositoryException {
+ perform(new RemoveMixinOperation(this, mixinName));
}
/**
@@ -2561,31 +2266,23 @@ public class NodeImpl extends ItemImpl i
return nta;
}
- /**
- * {@inheritDoc}
- */
- public void addMixin(String mixinName)
- throws NoSuchNodeTypeException, VersionException,
- ConstraintViolationException, LockException, RepositoryException {
+ /** Wrapper around {@link #addMixin(Name)}. */
+ public void addMixin(String mixinName) throws RepositoryException {
try {
addMixin(session.getQName(mixinName));
} catch (NameException e) {
throw new RepositoryException(
- "invalid mixin type name: " + mixinName, e);
+ "Invalid mixin type name: " + mixinName, e);
}
}
- /**
- * {@inheritDoc}
- */
- public void removeMixin(String mixinName)
- throws NoSuchNodeTypeException, VersionException,
- ConstraintViolationException, LockException, RepositoryException {
+ /** Wrapper around {@link #removeMixin(Name)}. */
+ public void removeMixin(String mixinName) throws RepositoryException {
try {
removeMixin(session.getQName(mixinName));
} catch (NameException e) {
throw new RepositoryException(
- "invalid mixin type name: " + mixinName, e);
+ "Invalid mixin type name: " + mixinName, e);
}
}
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java?rev=982520&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java
(added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java
Thu Aug 5 09:43:08 2010
@@ -0,0 +1,296 @@
+/*
+ * 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.core;
+
+import static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT;
+import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS;
+import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD;
+import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK;
+import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.NodeDefinition;
+import javax.jcr.nodetype.PropertyDefinition;
+
+import org.apache.jackrabbit.core.id.PropertyId;
+import org.apache.jackrabbit.core.nodetype.EffectiveNodeType;
+import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException;
+import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
+import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
+import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
+import org.apache.jackrabbit.core.security.authorization.Permission;
+import org.apache.jackrabbit.core.session.SessionContext;
+import org.apache.jackrabbit.core.session.SessionOperation;
+import org.apache.jackrabbit.core.state.ChildNodeEntry;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.state.PropertyState;
+import org.apache.jackrabbit.core.state.SessionItemStateManager;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl;
+import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl;
+import org.apache.jackrabbit.value.ValueHelper;
+
+/**
+ * Session operation for removing a mixin type from a node.
+ */
+class RemoveMixinOperation implements SessionOperation<Object> {
+
+ private final NodeImpl node;
+
+ private final Name mixinName;
+
+ public RemoveMixinOperation(NodeImpl node, Name mixinName) {
+ this.node = node;
+ this.mixinName = mixinName;
+ }
+
+ public Object perform(SessionContext context) throws RepositoryException {
+ SessionImpl session = context.getSessionImpl();
+ ItemManager itemMgr = context.getItemManager();
+ SessionItemStateManager stateMgr = context.getItemStateManager();
+
+ session.getValidator().checkModify(
+ node,
+ CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD,
+ Permission.NODE_TYPE_MNGMT);
+
+ // check if mixin is assigned
+ NodeState state = node.getNodeState();
+ if (!state.getMixinTypeNames().contains(mixinName)) {
+ throw new NoSuchNodeTypeException(
+ "Mixin " + session.getJCRName(mixinName)
+ + " not included in " + node);
+ }
+
+ NodeTypeManagerImpl ntMgr = session.getNodeTypeManager();
+ NodeTypeRegistry ntReg = ntMgr.getNodeTypeRegistry();
+
+ // build effective node type of remaining mixin's & primary type
+ Set<Name> remainingMixins = new HashSet<Name>(state.getMixinTypeNames());
+ // remove name of target mixin
+ remainingMixins.remove(mixinName);
+ EffectiveNodeType entResulting;
+ try {
+ // build effective node type representing primary type
+ // including remaining mixin's
+ entResulting = ntReg.getEffectiveNodeType(
+ state.getNodeTypeName(), remainingMixins);
+ } catch (NodeTypeConflictException e) {
+ throw new ConstraintViolationException(e.getMessage(), e);
+ }
+
+ // mix:referenceable needs special handling because it has
+ // special semantics:
+ // it can only be removed if there no more references to this node
+ NodeTypeImpl mixin = ntMgr.getNodeType(mixinName);
+ if (isReferenceable(mixin)
+ && !entResulting.includesNodeType(MIX_REFERENCEABLE)) {
+ if (node.getReferences().hasNext()) {
+ throw new ConstraintViolationException(
+ mixinName + " can not be removed:"
+ + " the node is being referenced through at least"
+ + " one property of type REFERENCE");
+ }
+ }
+
+ // mix:lockable: the mixin cannot be removed if the node is
+ // currently locked even if the editing session is the lock holder.
+ if ((NameConstants.MIX_LOCKABLE.equals(mixinName)
+ || mixin.isDerivedFrom(NameConstants.MIX_LOCKABLE))
+ && !entResulting.includesNodeType(NameConstants.MIX_LOCKABLE)
+ && node.isLocked()) {
+ throw new ConstraintViolationException(
+ mixinName + " can not be removed: the node is locked.");
+ }
+
+ NodeState thisState = (NodeState) node.getOrCreateTransientItemState();
+
+ // collect information about properties and nodes which require further
+ // action as a result of the mixin removal; we need to do this *before*
+ // actually changing the assigned mixin types, otherwise we wouldn't
+ // be able to retrieve the current definition of an item.
+ Map<PropertyId, PropertyDefinition> affectedProps =
+ new HashMap<PropertyId, PropertyDefinition>();
+ Map<ChildNodeEntry, NodeDefinition> affectedNodes =
+ new HashMap<ChildNodeEntry, NodeDefinition>();
+ try {
+ Set<Name> names = thisState.getPropertyNames();
+ for (Name propName : names) {
+ PropertyId propId =
+ new PropertyId(thisState.getNodeId(), propName);
+ PropertyState propState =
+ (PropertyState) stateMgr.getItemState(propId);
+ PropertyDefinition oldDef = itemMgr.getDefinition(propState);
+ // check if property has been defined by mixin type
+ // (or one of its supertypes)
+ NodeTypeImpl declaringNT =
+ (NodeTypeImpl) oldDef.getDeclaringNodeType();
+ if (!entResulting.includesNodeType(declaringNT.getQName())) {
+ // the resulting effective node type doesn't include the
+ // node type that declared this property
+ affectedProps.put(propId, oldDef);
+ }
+ }
+
+ List<ChildNodeEntry> entries = thisState.getChildNodeEntries();
+ for (ChildNodeEntry entry : entries) {
+ NodeState nodeState =
+ (NodeState) stateMgr.getItemState(entry.getId());
+ NodeDefinition oldDef = itemMgr.getDefinition(nodeState);
+ // check if node has been defined by mixin type
+ // (or one of its supertypes)
+ NodeTypeImpl declaringNT =
+ (NodeTypeImpl) oldDef.getDeclaringNodeType();
+ if (!entResulting.includesNodeType(declaringNT.getQName())) {
+ // the resulting effective node type doesn't include the
+ // node type that declared this child node
+ affectedNodes.put(entry, oldDef);
+ }
+ }
+ } catch (ItemStateException e) {
+ throw new RepositoryException(
+ "Failed to determine effect of removing mixin "
+ + session.getJCRName(mixinName), e);
+ }
+
+ // modify the state of this node
+ thisState.setMixinTypeNames(remainingMixins);
+ // set jcr:mixinTypes property
+ node.setMixinTypesProperty(remainingMixins);
+
+ // process affected nodes & properties:
+ // 1. try to redefine item based on the resulting
+ // new effective node type (see JCR-2130)
+ // 2. remove item if 1. fails
+ boolean success = false;
+ try {
+ for (PropertyId id : affectedProps.keySet()) {
+ PropertyImpl prop = (PropertyImpl) itemMgr.getItem(id);
+ PropertyDefinition oldDef = affectedProps.get(id);
+
+ if (oldDef.isProtected()) {
+ // remove 'orphaned' protected properties immediately
+ node.removeChildProperty(id.getName());
+ continue;
+ }
+ // try to find new applicable definition first and
+ // redefine property if possible (JCR-2130)
+ try {
+ PropertyDefinitionImpl newDef =
+ node.getApplicablePropertyDefinition(
+ id.getName(), prop.getType(),
+ oldDef.isMultiple(), false);
+ if (newDef.getRequiredType() != PropertyType.UNDEFINED
+ && newDef.getRequiredType() != prop.getType()) {
+ // value conversion required
+ if (oldDef.isMultiple()) {
+ // convert value
+ Value[] values =
+ ValueHelper.convert(
+ prop.getValues(),
+ newDef.getRequiredType(),
+ session.getValueFactory());
+ // redefine property
+ prop.onRedefine(newDef.unwrap());
+ // set converted values
+ prop.setValue(values);
+ } else {
+ // convert value
+ Value value =
+ ValueHelper.convert(
+ prop.getValue(),
+ newDef.getRequiredType(),
+ session.getValueFactory());
+ // redefine property
+ prop.onRedefine(newDef.unwrap());
+ // set converted values
+ prop.setValue(value);
+ }
+ } else {
+ // redefine property
+ prop.onRedefine(newDef.unwrap());
+ }
+ } catch (ValueFormatException vfe) {
+ // value conversion failed, remove it
+ node.removeChildProperty(id.getName());
+ } catch (ConstraintViolationException cve) {
+ // no suitable definition found for this property,
+ // remove it
+ node.removeChildProperty(id.getName());
+ }
+ }
+
+ for (ChildNodeEntry entry : affectedNodes.keySet()) {
+ NodeState nodeState =
+ (NodeState) stateMgr.getItemState(entry.getId());
+ NodeImpl node = (NodeImpl) itemMgr.getItem(entry.getId());
+ NodeDefinition oldDef = affectedNodes.get(entry);
+
+ if (oldDef.isProtected()) {
+ // remove 'orphaned' protected child node immediately
+ node.removeChildNode(entry.getId());
+ continue;
+ }
+
+ // try to find new applicable definition first and
+ // redefine node if possible (JCR-2130)
+ try {
+ NodeDefinitionImpl newDef =
+ node.getApplicableChildNodeDefinition(
+ entry.getName(),
+ nodeState.getNodeTypeName());
+ // redefine node
+ node.onRedefine(newDef.unwrap());
+ } catch (ConstraintViolationException cve) {
+ // no suitable definition found for this child node,
+ // remove it
+ node.removeChildNode(entry.getId());
+ }
+ }
+ success = true;
+ } catch (ItemStateException e) {
+ throw new RepositoryException(
+ "Failed to clean up child items defined by removed mixin "
+ + session.getJCRName(mixinName), e);
+ } finally {
+ if (!success) {
+ // TODO JCR-1914: revert any changes made so far
+ }
+ }
+
+ return this;
+ }
+
+ private boolean isReferenceable(NodeTypeImpl mixin) {
+ return MIX_REFERENCEABLE.equals(mixinName)
+ || mixin.isDerivedFrom(MIX_REFERENCEABLE);
+ }
+
+}
\ No newline at end of file
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RemoveMixinOperation.java
------------------------------------------------------------------------------
svn:eol-style = native
|