/* * 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.SessionWriteOperation; 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 SessionWriteOperation<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 { 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; } context.getItemValidator().checkModify( node, CHECK_LOCK | CHECK_CHECKED_OUT | CHECK_CONSTRAINTS | CHECK_HOLD, permissions); NodeTypeManagerImpl ntMgr = context.getNodeTypeManager(); NodeTypeImpl mixin = ntMgr.getNodeType(mixinName); if (!mixin.isMixin()) { throw new RepositoryException( context.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 = context.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; } //--------------------------------------------------------------< Object > /** * Returns a string representation of this operation. */ public String toString() { return "node.addMixin(" + mixinName + ")"; } }