/* * 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.nodetype.registration; import org.exoplatform.services.jcr.core.nodetype.NodeDefinitionData; import org.exoplatform.services.jcr.core.nodetype.NodeTypeData; import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager; import org.exoplatform.services.jcr.dataflow.ItemDataConsumer; import org.exoplatform.services.jcr.dataflow.PlainChangesLog; import org.exoplatform.services.jcr.dataflow.PlainChangesLogImpl; import org.exoplatform.services.jcr.datamodel.InternalQName; import org.exoplatform.services.jcr.datamodel.ItemData; import org.exoplatform.services.jcr.datamodel.ItemType; import org.exoplatform.services.jcr.datamodel.NodeData; import org.exoplatform.services.jcr.datamodel.QPathEntry; import org.exoplatform.services.jcr.impl.Constants; import org.exoplatform.services.jcr.impl.core.nodetype.ItemAutocreator; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; /** * Created by The eXo Platform SAS. * * @author <a href="mailto:Sergey.Kabashnyuk@gmail.com">Sergey Kabashnyuk</a> * @version $Id: $ */ public class NodeDefinitionComparator extends AbstractDefinitionComparator<NodeDefinitionData> { /** * Class logger. */ private static final Log LOG = ExoLogger.getLogger("exo.jcr.component.core.NodeDefinitionComparator"); private final List<NodeData> affectedNodes; private final NodeTypeDataManager nodeTypeDataManager; private final ItemDataConsumer dataConsumer; private final ItemAutocreator itemAutocreator; /** * @param nodeTypeDataManager * @param dataConsumer * @param itemAutocreator * @param affectedNodes */ public NodeDefinitionComparator(NodeTypeDataManager nodeTypeDataManager, ItemDataConsumer dataConsumer, ItemAutocreator itemAutocreator, List<NodeData> affectedNodes) { this.nodeTypeDataManager = nodeTypeDataManager; this.dataConsumer = dataConsumer; this.itemAutocreator = itemAutocreator; this.affectedNodes = affectedNodes; } @Override public PlainChangesLog compare(NodeTypeData registeredNodeType, NodeDefinitionData[] ancestorDefinition, NodeDefinitionData[] recipientDefinition) throws ConstraintViolationException, RepositoryException { List<NodeDefinitionData> sameDefinitionData = new ArrayList<NodeDefinitionData>(); List<RelatedDefinition<NodeDefinitionData>> changedDefinitionData = new ArrayList<RelatedDefinition<NodeDefinitionData>>(); List<NodeDefinitionData> newDefinitionData = new ArrayList<NodeDefinitionData>(); List<NodeDefinitionData> removedDefinitionData = new ArrayList<NodeDefinitionData>(); init(ancestorDefinition, recipientDefinition, sameDefinitionData, changedDefinitionData, newDefinitionData, removedDefinitionData); // create changes log PlainChangesLog changesLog = new PlainChangesLogImpl(); // check removed validateRemoved(registeredNodeType, removedDefinitionData, recipientDefinition, affectedNodes); validateAdded(registeredNodeType.getName(), newDefinitionData, affectedNodes, recipientDefinition); // changed validateChanged(registeredNodeType.getName(), changedDefinitionData, affectedNodes, recipientDefinition); // doAdd(newDefinitionData, changesLog, affectedNodes, registeredNodeType); return changesLog; } /** * @param nodesData * @param nodeDefinitionData * @throws RepositoryException * @throws ConstraintViolationException */ private void checkMandatoryItems(List<NodeData> nodesData, NodeDefinitionData nodeDefinitionData) throws RepositoryException, ConstraintViolationException { for (NodeData nodeData : nodesData) { ItemData child = dataConsumer.getItemData(nodeData, new QPathEntry(nodeDefinitionData.getName(), 0), ItemType.NODE); if (child == null || !child.isNode()) { throw new ConstraintViolationException("Fail to add mandatory and not auto-created " + "child node definition " + nodeDefinitionData.getName().getAsString() + " for " + nodeDefinitionData.getDeclaringNodeType().getAsString() + " because node " + nodeData.getQPath().getAsString() + " doesn't contains child node with name " + nodeDefinitionData.getName().getAsString()); } } } /** * @param registeredNodeType * @param nodesData * @param ancestorRequiredPrimaryTypes * @param recipientDefinitionData * @throws RepositoryException */ private void checkRequiredPrimaryType(InternalQName registeredNodeType, List<NodeData> nodesData, InternalQName[] ancestorRequiredPrimaryTypes, NodeDefinitionData recipientDefinitionData, NodeDefinitionData[] allRecipientDefinition) throws RepositoryException { // Required type change InternalQName[] requiredPrimaryTypes = recipientDefinitionData.getRequiredPrimaryTypes(); for (NodeData nodeData : nodesData) { if (recipientDefinitionData.getName().equals(Constants.JCR_ANY_NAME)) { List<NodeData> childs = dataConsumer.getChildNodesData(nodeData); for (NodeData child : childs) { if (isResidualMatch(child.getQPath().getName(), allRecipientDefinition)) { for (int i = 0; i < requiredPrimaryTypes.length; i++) { if (!nodeTypeDataManager.isNodeType(requiredPrimaryTypes[i], child.getPrimaryTypeName(), child.getMixinTypeNames())) { StringBuilder buffer = new StringBuilder(); buffer.append("Fail to change "); buffer.append(recipientDefinitionData.getName().getAsString()); buffer.append(" node definition for "); buffer.append(registeredNodeType.getAsString()); buffer.append("node type from "); if (ancestorRequiredPrimaryTypes != null) buffer.append(Arrays.toString(ancestorRequiredPrimaryTypes)); else buffer.append(" '' "); buffer.append(" to "); buffer.append(Arrays.toString(recipientDefinitionData.getRequiredPrimaryTypes())); buffer.append(" because "); buffer.append(child.getQPath().getAsString()); buffer.append(" doesn't much "); buffer.append(requiredPrimaryTypes[i].getAsString()); buffer.append(" as required primary type"); throw new ConstraintViolationException(buffer.toString()); } } } } } else { List<NodeData> childs = dataConsumer.getChildNodesData(nodeData); for (NodeData child : childs) { if (child.getQPath().getName().equals(recipientDefinitionData.getName())) { for (int i = 0; i < requiredPrimaryTypes.length; i++) { if (!nodeTypeDataManager.isNodeType(requiredPrimaryTypes[i], child.getPrimaryTypeName(), child.getMixinTypeNames())) { StringBuilder buffer = new StringBuilder(); buffer.append("Fail to change "); buffer.append(recipientDefinitionData.getName().getAsString()); buffer.append(" node definition for "); buffer.append(registeredNodeType.getAsString()); buffer.append("node type from "); if (ancestorRequiredPrimaryTypes != null) buffer.append(Arrays.toString(ancestorRequiredPrimaryTypes)); else buffer.append(" '' "); buffer.append(" to "); buffer.append(Arrays.toString(recipientDefinitionData.getRequiredPrimaryTypes())); buffer.append(" because "); buffer.append(child.getQPath().getAsString()); buffer.append("doesn't much "); buffer.append(requiredPrimaryTypes[i].getAsString()); buffer.append(" as required primary type"); throw new ConstraintViolationException(buffer.toString()); } } } } } } } /** * @param registeredNodeType * @param nodesData * @param recipientName * @param allRecipientDefinition * @throws RepositoryException */ private void checkSameNameSibling(InternalQName registeredNodeType, List<NodeData> nodesData, InternalQName recipientName, NodeDefinitionData[] allRecipientDefinition) throws RepositoryException { for (NodeData nodeData : nodesData) { if (recipientName.equals(Constants.JCR_ANY_NAME)) { // child of node type List<NodeData> childs = dataConsumer.getChildNodesData(nodeData); for (NodeData child : childs) { if (isResidualMatch(child.getQPath().getName(), allRecipientDefinition)) { List<NodeData> childs2 = dataConsumer.getChildNodesData(child); for (NodeData child2 : childs2) { if (child2.getQPath().getIndex() > 1) { StringBuilder buffer = new StringBuilder(); buffer.append("Fail to change "); buffer.append(recipientName.getAsString()); buffer.append(" node definition for "); buffer.append(registeredNodeType.getAsString()); buffer.append("node type from AllowsSameNameSiblings = true to AllowsSameNameSiblings = false"); buffer.append(" because "); buffer.append(child.getQPath().getAsString()); buffer.append(" contains more then one child with name"); buffer.append(child2.getQPath().getName().getAsString()); throw new ConstraintViolationException(buffer.toString()); } } } } } else { // child of node type List<NodeData> childs = dataConsumer.getChildNodesData(nodeData); for (NodeData child : childs) { if (child.getQPath().getName().equals(recipientName)) { List<NodeData> childs2 = dataConsumer.getChildNodesData(child); for (NodeData child2 : childs2) { if (child2.getQPath().getIndex() > 1) { StringBuilder buffer = new StringBuilder(); buffer.append("Fail to change "); buffer.append(recipientName.getAsString()); buffer.append(" node definition for "); buffer.append(registeredNodeType.getAsString()); buffer.append("node type from AllowsSameNameSiblings = true to AllowsSameNameSiblings = false"); buffer.append(" because "); buffer.append(child.getQPath().getAsString()); buffer.append(" contains more then one child with name"); buffer.append(child2.getQPath().getName().getAsString()); throw new ConstraintViolationException(buffer.toString()); } } } } } } } /** * @param toAddList * @param changesLog * @param nodesData * @param registeredNodeType * @throws RepositoryException */ private void doAdd(List<NodeDefinitionData> toAddList, PlainChangesLog changesLog, List<NodeData> nodesData, NodeTypeData registeredNodeType) throws RepositoryException { for (NodeData nodeData : nodesData) { // added properties for (NodeDefinitionData newNodeDefinitionData : toAddList) { if (!newNodeDefinitionData.getName().equals(Constants.JCR_ANY_NAME) && newNodeDefinitionData.isAutoCreated()) changesLog.addAll(itemAutocreator.makeAutoCreatedNodes(nodeData, registeredNodeType.getName(), new NodeDefinitionData[]{newNodeDefinitionData}, dataConsumer, nodeData.getACL().getOwner()) .getAllStates()); } } } /** * @param nodeTypeName * @param newDefinitionData * @param nodesData * @param recipientDefinition * @throws RepositoryException */ private void validateAdded(InternalQName nodeTypeName, List<NodeDefinitionData> newDefinitionData, List<NodeData> nodesData, NodeDefinitionData[] recipientDefinition) throws RepositoryException { for (NodeDefinitionData nodeDefinitionData : newDefinitionData) { if (nodeDefinitionData.getName().equals(Constants.JCR_ANY_NAME)) { checkRequiredPrimaryType(nodeTypeName, nodesData, null, nodeDefinitionData, recipientDefinition); checkSameNameSibling(nodeTypeName, nodesData, nodeDefinitionData.getName(), recipientDefinition); } else { // check existed nodes for new constraint checkRequiredPrimaryType(nodeTypeName, nodesData, null, nodeDefinitionData, recipientDefinition); checkSameNameSibling(nodeTypeName, nodesData, nodeDefinitionData.getName(), recipientDefinition); // try to add mandatory or auto-created properties for // for already addded nodes. if (nodeDefinitionData.isMandatory() && !nodeDefinitionData.isAutoCreated()) { checkMandatoryItems(nodesData, nodeDefinitionData); } } } } /** * @param registeredNodeType * @param changedDefinitionData * @param nodesData * @param allRecipientDefinition * @throws RepositoryException */ private void validateChanged(InternalQName registeredNodeType, List<RelatedDefinition<NodeDefinitionData>> changedDefinitionData, List<NodeData> nodesData, NodeDefinitionData[] allRecipientDefinition) throws RepositoryException { for (RelatedDefinition<NodeDefinitionData> changedDefinitions : changedDefinitionData) { NodeDefinitionData ancestorDefinitionData = changedDefinitions.getAncestorDefinition(); NodeDefinitionData recipientDefinitionData = changedDefinitions.getRecepientDefinition(); // change from mandatory=false to mandatory = true if (!ancestorDefinitionData.isMandatory() && recipientDefinitionData.isMandatory()) { for (NodeData nodeData : nodesData) { ItemData child = dataConsumer.getItemData(nodeData, new QPathEntry(recipientDefinitionData.getName(), 0), ItemType.NODE); if (child == null || !child.isNode()) { String message = "Can not change " + recipientDefinitionData.getName().getAsString() + " node definition for " + registeredNodeType.getAsString() + " node type " + " from mandatory=false to mandatory = true , because " + " node " + nodeData.getQPath().getAsString() + " doesn't have child node with name " + recipientDefinitionData.getName().getAsString(); throw new ConstraintViolationException(message); } } } // change from Protected=false to Protected = true if (!ancestorDefinitionData.isProtected() && recipientDefinitionData.isProtected()) { for (NodeData nodeData : nodesData) { ItemData child = dataConsumer.getItemData(nodeData, new QPathEntry(recipientDefinitionData.getName(), 0), ItemType.NODE); if (child == null || !child.isNode()) { String message = "Fail to change " + recipientDefinitionData.getName().getAsString() + " node definition for " + registeredNodeType.getAsString() + " node type from protected=false to Protected = true , because " + " node " + nodeData.getQPath().getAsString() + " doesn't have child node with name " + recipientDefinitionData.getName().getAsString(); throw new ConstraintViolationException(message); } } } if (!Arrays.deepEquals(ancestorDefinitionData.getRequiredPrimaryTypes(), recipientDefinitionData.getRequiredPrimaryTypes())) { checkRequiredPrimaryType(registeredNodeType, nodesData, ancestorDefinitionData.getRequiredPrimaryTypes(), recipientDefinitionData, allRecipientDefinition); } // check sibling if (ancestorDefinitionData.isAllowsSameNameSiblings() && !recipientDefinitionData.isAllowsSameNameSiblings()) { checkSameNameSibling(registeredNodeType, nodesData, recipientDefinitionData.getName(), allRecipientDefinition); } } } /** * @param registeredNodeType * @param removedDefinitionData * @param recipientDefinition * @throws ConstraintViolationException * @throws RepositoryException */ private void validateRemoved(NodeTypeData registeredNodeType, List<NodeDefinitionData> removedDefinitionData, NodeDefinitionData[] recipientDefinition, List<NodeData> nodesData) throws ConstraintViolationException, RepositoryException { for (NodeDefinitionData removeNodeDefinitionData : removedDefinitionData) { if (removeNodeDefinitionData.getName().equals(Constants.JCR_ANY_NAME)) { for (NodeData nodeData : nodesData) { List<NodeData> childs = dataConsumer.getChildNodesData(nodeData); // more then mixin and primary type if (childs.size() > 0) { for (NodeData nodeData2 : childs) { if (!isNonResidualMatch(nodeData2.getQPath().getName(), recipientDefinition)) { String msg = "Can't remove node definition " + removeNodeDefinitionData.getName().getAsString() + " for " + registeredNodeType.getName().getAsString() + " node type because node " + nodeData.getQPath().getAsString() + " " + " countains child nodes with name " + nodeData2.getQPath().getName().getAsString(); throw new ConstraintViolationException(msg); } } } } } else { if (!isResidualMatch(removeNodeDefinitionData.getName(), recipientDefinition)) { for (NodeData nodeData : nodesData) { ItemData child = dataConsumer.getItemData(nodeData, new QPathEntry(removeNodeDefinitionData.getName(), 0), ItemType.NODE); if (child != null && child.isNode()) { throw new ConstraintViolationException("Can't remove node definition " + removeNodeDefinitionData.getName().getAsString() + " for " + registeredNodeType.getName().getAsString() + " node type because node " + nodeData.getQPath().getAsString() + " " + " countains child node with name " + child.getQPath().getName().getAsString()); } } } } } } }