/* * 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; import org.exoplatform.commons.utils.SecurityHelper; import org.exoplatform.container.ExoContainerContext; import org.exoplatform.services.jcr.RepositoryService; import org.exoplatform.services.jcr.config.RepositoryEntry; import org.exoplatform.services.jcr.core.ManageableRepository; import org.exoplatform.services.jcr.core.nodetype.ExtendedNodeTypeManager; import org.exoplatform.services.jcr.core.nodetype.ItemDefinitionData; 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.core.nodetype.NodeTypeValue; import org.exoplatform.services.jcr.core.nodetype.PropertyDefinitionData; import org.exoplatform.services.jcr.core.nodetype.PropertyDefinitionDatas; import org.exoplatform.services.jcr.dataflow.ItemDataConsumer; import org.exoplatform.services.jcr.dataflow.ItemState; 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.PropertyData; import org.exoplatform.services.jcr.datamodel.QPathEntry; import org.exoplatform.services.jcr.impl.Constants; import org.exoplatform.services.jcr.impl.core.LocationFactory; import org.exoplatform.services.jcr.impl.core.NamespaceRegistryImpl; import org.exoplatform.services.jcr.impl.core.nodetype.registration.CNDNodeTypeDataPersister; import org.exoplatform.services.jcr.impl.core.nodetype.registration.NodeDefinitionComparator; import org.exoplatform.services.jcr.impl.core.nodetype.registration.NodeTypeConverter; import org.exoplatform.services.jcr.impl.core.nodetype.registration.NodeTypeDataPersister; import org.exoplatform.services.jcr.impl.core.nodetype.registration.NodeTypeDataValidator; import org.exoplatform.services.jcr.impl.core.nodetype.registration.PropertyDefinitionComparator; import org.exoplatform.services.jcr.impl.core.nodetype.registration.XmlNodeTypeDataPersister; import org.exoplatform.services.jcr.impl.core.query.RepositoryIndexSearcherHolder; import org.exoplatform.services.jcr.impl.core.value.ValueFactoryImpl; 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.impl.util.io.FileCleanerHolder; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.services.rpc.RPCService; import org.exoplatform.services.rpc.RemoteCommand; import org.picocontainer.Startable; import java.io.InputStream; import java.io.Serializable; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.WeakHashMap; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; /** * Created by The eXo Platform SAS. <br> * Date: 26.11.2008 * * @author <a href="mailto:peter.nedonosko@exoplatform.com.ua">Peter * Nedonosko</a> * @version $Id: NodeTypeDataManagerImpl.java 111 2008-11-11 11:11:11Z * pnedonosko $ */ public class NodeTypeDataManagerImpl implements NodeTypeDataManager, Startable { private static final String NODETYPES_FILE = "nodetypes.xml"; private static final Log LOG = ExoLogger.getLogger("exo.jcr.component.core.NodeTypeDataManagerImpl"); protected final String accessControlPolicy; protected final ItemDataConsumer dataManager; protected final RepositoryIndexSearcherHolder indexSearcherHolder; protected final LocationFactory locationFactory; protected final NamespaceRegistry namespaceRegistry; private final Set<InternalQName> buildInNodeTypesNames; protected final NodeTypeRepository nodeTypeRepository; protected final NodeTypeConverter nodeTypeConverter; protected final NodeTypeDataValidator nodeTypeDataValidator; protected final FileCleanerHolder cleanerHolder; protected final NodeTypeDataPersister persister; /** * Component used to execute commands over the cluster. */ private RPCService rpcService; /** * We rely on the repository to decide whether the remote commands should be launched or not */ private ManageableRepository repository; /** * The command that registers the node types over the cluster */ private RemoteCommand registerNodeTypes; /** * The command that unregisters a node type over the cluster */ private RemoteCommand unregisterNodeType; /** * Id used to avoid launching twice the same command on the same node */ private String id; /** * The name of the repository */ private String repositoryName; /** * Listeners (soft references) */ private final Map<NodeTypeManagerListener, NodeTypeManagerListener> listeners; private final ValueFactoryImpl valueFactory; private boolean started = false; /** * NodeTypeDataManagerImpl constructor. * * @param accessControlPolicy String * @param locationFactory LocationFactory * @param namespaceRegistry NamespaceRegistry * @param persister NodeTypeDataPersister * @param dataManager ItemDataConsumer * @param indexSearcherHolder RepositoryIndexSearcherHolder * @param nodeTypeRepository NodeTypeRepository */ public NodeTypeDataManagerImpl(final String accessControlPolicy, final LocationFactory locationFactory, final NamespaceRegistry namespaceRegistry, final NodeTypeDataPersister persister, final ItemDataConsumer dataManager, final RepositoryIndexSearcherHolder indexSearcherHolder, final NodeTypeRepository nodeTypeRepository, FileCleanerHolder cleanerHolder) { this.namespaceRegistry = namespaceRegistry; this.locationFactory = locationFactory; this.dataManager = dataManager; this.indexSearcherHolder = indexSearcherHolder; this.valueFactory = new ValueFactoryImpl(locationFactory, cleanerHolder); this.cleanerHolder = cleanerHolder; this.accessControlPolicy = accessControlPolicy; this.nodeTypeRepository = nodeTypeRepository; this.listeners = Collections.synchronizedMap(new WeakHashMap<NodeTypeManagerListener, NodeTypeManagerListener>()); this.buildInNodeTypesNames = new HashSet<InternalQName>(); this.nodeTypeConverter = new NodeTypeConverter(this.locationFactory, this.accessControlPolicy); this.nodeTypeDataValidator = new NodeTypeDataValidator(this.locationFactory, this.nodeTypeRepository, cleanerHolder); this.persister = persister; } /** * Constructor for in-container use. * * @param config RepositoryEntry * @param locationFactory LocationFactory * @param namespaceRegistry NamespaceRegistry * @param persister NodeTypeDataPersister * @param dataManager ItemDataConsumer * @param indexSearcherHolder RepositoryIndexSearcherHolder */ public NodeTypeDataManagerImpl(RepositoryEntry config, final LocationFactory locationFactory, final NamespaceRegistry namespaceRegistry, final NodeTypeDataPersister persister, final ItemDataConsumer dataManager, final RepositoryIndexSearcherHolder indexSearcherHolder, FileCleanerHolder cleanerHolder) { this(null, config, locationFactory, namespaceRegistry, persister, dataManager, indexSearcherHolder, cleanerHolder); } /** * Constructor for in-container use. * * @param rpcService The RPCService that we uses to communicate with other cluster nodes * @param config RepositoryEntry * @param locationFactory LocationFactory * @param namespaceRegistry NamespaceRegistry * @param persister NodeTypeDataPersister * @param dataManager ItemDataConsumer * @param indexSearcherHolder RepositoryIndexSearcherHolder */ public NodeTypeDataManagerImpl(RPCService rpcService, final RepositoryEntry config, final LocationFactory locationFactory, final NamespaceRegistry namespaceRegistry, final NodeTypeDataPersister persister, final ItemDataConsumer dataManager, final RepositoryIndexSearcherHolder indexSearcherHolder, FileCleanerHolder cleanerHolder) { this(config.getAccessControl(), locationFactory, namespaceRegistry, persister, dataManager, indexSearcherHolder, new InmemoryNodeTypeRepository(persister), cleanerHolder); this.rpcService = rpcService; this.repositoryName = config.getName(); if (rpcService != null) { initRemoteCommands(); } } /** * Registers all the remote commands */ private void initRemoteCommands() { this.id = UUID.randomUUID().toString(); registerNodeTypes = rpcService.registerCommand(new RemoteCommand() { public String getId() { return "org.exoplatform.services.jcr.impl.core.nodetype.NodeTypeDataManagerImpl-registerNodeTypes-" + repositoryName; } public Serializable execute(Serializable[] args) throws Throwable { if (!id.equals(args[0])) { try { String[] names = (String[])args[1]; final List<NodeTypeData> allNodeTypes = new ArrayList<NodeTypeData>(); for (int i = 0; i < names.length; i++) { NodeTypeData nodeType = persister.getNodeType(InternalQName.parse(names[i])); if (nodeType != null) allNodeTypes.add(nodeType); } // register nodetypes in runtime final Map<InternalQName, NodeTypeData> volatileNodeTypes = new HashMap<InternalQName, NodeTypeData>(); //create map from list for (final NodeTypeData nodeTypeData : allNodeTypes) { volatileNodeTypes.put(nodeTypeData.getName(), nodeTypeData); } for (final NodeTypeData nodeTypeData : allNodeTypes) { nodeTypeRepository.addNodeType(nodeTypeData, volatileNodeTypes); for (NodeTypeManagerListener listener : listeners.values()) { listener.nodeTypeRegistered(nodeTypeData.getName()); } } } catch (Exception e) { LOG.warn("Could not register the node types", e); } } return true; } }); unregisterNodeType = rpcService.registerCommand(new RemoteCommand() { public String getId() { return "org.exoplatform.services.jcr.impl.core.nodetype.NodeTypeDataManagerImpl-unregisterNodeType-" + repositoryName; } public Serializable execute(Serializable[] args) throws Throwable { if (!id.equals(args[0])) { try { String name = (String)args[1]; NodeTypeData nodeType = nodeTypeRepository.getNodeType(InternalQName.parse(name)); if (nodeType != null) { nodeTypeRepository.removeNodeType(nodeType); for (NodeTypeManagerListener listener : listeners.values()) { listener.nodeTypeUnregistered(nodeType.getName()); } } } catch (Exception e) { LOG.warn("Could not register the node type", e); } } return true; } }); } /** * Unregisters the remote commands. */ private void unregisterRemoteCommands() { if (rpcService != null) { rpcService.unregisterCommand(registerNodeTypes); rpcService.unregisterCommand(unregisterNodeType); } } /** * Add a <code>NodeTypeRegistryListener</code> * * @param listener the new listener to be informed on (un)registration of node * types */ public void addListener(final NodeTypeManagerListener listener) { if (!this.listeners.containsKey(listener)) { this.listeners.put(listener, listener); } } /** * {@inheritDoc} */ public NodeDefinitionData[] getAllChildNodeDefinitions(final InternalQName... nodeTypeNames) { final Collection<NodeDefinitionData> defsAny = new ArrayList<NodeDefinitionData>(); final HashMap<InternalQName, NodeDefinitionData> defs = new HashMap<InternalQName, NodeDefinitionData>(); for (final InternalQName ntname : nodeTypeNames) { for (final InternalQName suname : this.nodeTypeRepository.getSupertypes(ntname)) { for (final NodeDefinitionData cnd : this.nodeTypeRepository.getNodeType(suname) .getDeclaredChildNodeDefinitions()) { if (cnd.getName().equals(Constants.JCR_ANY_NAME)) { defsAny.add(cnd); } else { defs.put(cnd.getName(), cnd); } } } for (final NodeDefinitionData cnd : this.nodeTypeRepository.getNodeType(ntname) .getDeclaredChildNodeDefinitions()) { if (cnd.getName().equals(Constants.JCR_ANY_NAME)) { defsAny.add(cnd); } else { defs.put(cnd.getName(), cnd); } } } defsAny.addAll(defs.values()); return defsAny.toArray(new NodeDefinitionData[defsAny.size()]); } /** * {@inheritDoc} */ public List<NodeTypeData> getAllNodeTypes() { try { return this.nodeTypeRepository.getAllNodeTypes(); } catch (RepositoryException e) { LOG.error(e.getLocalizedMessage()); } return new ArrayList<NodeTypeData>(); } /** * {@inheritDoc} */ public PropertyDefinitionData[] getAllPropertyDefinitions(final InternalQName... nodeTypeNames) { final Collection<PropertyDefinitionData> defsAny = new ArrayList<PropertyDefinitionData>(); final HashMap<InternalQName, PropertyDefinitionData> defs = new HashMap<InternalQName, PropertyDefinitionData>(); for (final InternalQName ntname : nodeTypeNames) { for (final InternalQName suname : this.nodeTypeRepository.getSupertypes(ntname)) { for (final PropertyDefinitionData pd : this.nodeTypeRepository.getNodeType(suname) .getDeclaredPropertyDefinitions()) { if (pd.getName().equals(Constants.JCR_ANY_NAME)) { defsAny.add(pd); } else { defs.put(pd.getName(), pd); } } } for (final PropertyDefinitionData pd : this.nodeTypeRepository.getNodeType(ntname) .getDeclaredPropertyDefinitions()) { if (pd.getName().equals(Constants.JCR_ANY_NAME)) { defsAny.add(pd); } else { defs.put(pd.getName(), pd); } } } defsAny.addAll(defs.values()); return defsAny.toArray(new PropertyDefinitionData[defsAny.size()]); } /** * {@inheritDoc} * @throws RepositoryException */ public NodeDefinitionData getChildNodeDefinition(final InternalQName nodeName, final InternalQName... nodeTypeNames) throws RepositoryException { return this.nodeTypeRepository.getDefaultChildNodeDefinition(nodeName, nodeTypeNames); } /** * {@inheritDoc} * @throws RepositoryException */ public NodeDefinitionData getChildNodeDefinition(final InternalQName nodeName, final InternalQName primaryNodeType, final InternalQName[] mixinTypes) throws RepositoryException { return getChildNodeDefinition(nodeName, getNodeTypeNames(primaryNodeType, mixinTypes)); } /** * {@inheritDoc} */ public NodeDefinitionData getChildNodeDefinition(InternalQName nodeName, InternalQName nodeType, InternalQName parentNodeType, InternalQName[] parentMixinTypes) throws RepositoryException { NodeDefinitionData[] defs = getAllChildNodeDefinitions(getNodeTypeNames(parentNodeType, parentMixinTypes)); NodeDefinitionData residualDef = null; NodeDefinitionData firstResidualDef = null; outer : for (NodeDefinitionData nodeDef : defs) { if (nodeDef.getName().equals(nodeName)) { return nodeDef; } else if (nodeDef.isResidualSet()) { // store first residual definition to be able to return if (firstResidualDef == null) { firstResidualDef = nodeDef; } // check required primary types for (InternalQName requiredPrimaryType : nodeDef.getRequiredPrimaryTypes()) { if (!isNodeType(requiredPrimaryType, nodeType)) { continue outer; } } // when there are several suitable definitions take the most older if (residualDef == null || isNodeType(residualDef.getRequiredPrimaryTypes()[0], nodeDef.getRequiredPrimaryTypes()[0])) { residualDef = nodeDef; } } } return residualDef != null ? residualDef : firstResidualDef; } /** * {@inheritDoc} */ public Set<InternalQName> getDeclaredSubtypes(final InternalQName nodeTypeName) { return this.nodeTypeRepository.getDeclaredSubtypes(nodeTypeName); } public List<ItemDefinitionData> getManadatoryItemDefs(final InternalQName primaryNodeType, final InternalQName[] mixinTypes) throws RepositoryException { final Collection<ItemDefinitionData> mandatoryDefs = new HashSet<ItemDefinitionData>(); // primary type properties ItemDefinitionData[] itemDefs = getAllPropertyDefinitions(new InternalQName[]{primaryNodeType}); for (final ItemDefinitionData itemDef : itemDefs) { if (itemDef.isMandatory()) { mandatoryDefs.add(itemDef); } } // primary type nodes itemDefs = getAllChildNodeDefinitions(new InternalQName[]{primaryNodeType}); for (final ItemDefinitionData itemDef : itemDefs) { if (itemDef.isMandatory()) { mandatoryDefs.add(itemDef); } } // mixin properties itemDefs = getAllPropertyDefinitions(mixinTypes); for (final ItemDefinitionData itemDef : itemDefs) { if (itemDef.isMandatory()) { mandatoryDefs.add(itemDef); } } // mixin nodes itemDefs = getAllChildNodeDefinitions(mixinTypes); for (final ItemDefinitionData itemDef : itemDefs) { if (itemDef.isMandatory()) { mandatoryDefs.add(itemDef); } } return new ArrayList<ItemDefinitionData>(mandatoryDefs); } /** * {@inheritDoc} */ public NodeTypeData getNodeType(final InternalQName typeName) { return this.nodeTypeRepository.getNodeType(typeName); } /** * {@inheritDoc} * @throws RepositoryException */ public PropertyDefinitionDatas getPropertyDefinitions(final InternalQName propertyName, final InternalQName... nodeTypeNames) throws RepositoryException { PropertyDefinitionDatas propertyDefinitions = this.nodeTypeRepository.getPropertyDefinitions(propertyName, nodeTypeNames); // Try super if (propertyDefinitions == null) { for (int i = 0; i < nodeTypeNames.length && propertyDefinitions == null; i++) { final InternalQName[] supers = this.nodeTypeRepository.getNodeType(nodeTypeNames[i]).getDeclaredSupertypeNames(); propertyDefinitions = getPropertyDefinitions(propertyName, supers); } } // try residual def if (propertyDefinitions == null && !propertyName.equals(Constants.JCR_ANY_NAME)) { propertyDefinitions = getPropertyDefinitions(Constants.JCR_ANY_NAME, nodeTypeNames); } return propertyDefinitions; } /** * {@inheritDoc} * @throws RepositoryException */ public PropertyDefinitionDatas getPropertyDefinitions(final InternalQName propertyName, final InternalQName primaryNodeType, final InternalQName[] mixinTypes) throws RepositoryException { if (mixinTypes != null) { final InternalQName[] nts = new InternalQName[mixinTypes.length + 1]; nts[0] = primaryNodeType; System.arraycopy(mixinTypes, 0, nts, 1, mixinTypes.length); return getPropertyDefinitions(propertyName, nts); } return getPropertyDefinitions(propertyName, primaryNodeType); } /** * {@inheritDoc} */ public Set<InternalQName> getSubtypes(final InternalQName nodeTypeName) { return this.nodeTypeRepository.getSubtypes(nodeTypeName); } /** * {@inheritDoc} */ public Set<InternalQName> getSupertypes(final InternalQName nodeTypeName) { return this.nodeTypeRepository.getSupertypes(nodeTypeName); } /** * {@inheritDoc} */ public boolean isChildNodePrimaryTypeAllowed(final InternalQName childName, final InternalQName childNodeType, final InternalQName parentNodeType, final InternalQName[] parentMixinNames) throws RepositoryException { final Set<InternalQName> testSuperTypesNames = this.nodeTypeRepository.getSupertypes(childNodeType); NodeDefinitionData[] allChildNodeDefinitions = getAllChildNodeDefinitions(parentNodeType); for (final NodeDefinitionData cnd : allChildNodeDefinitions) { for (final InternalQName reqNodeType : cnd.getRequiredPrimaryTypes()) { InternalQName reqName = cnd.getName(); if (isChildAllowed(childName, childNodeType, cnd, reqName, reqNodeType)) { return true; } for (final InternalQName superName : testSuperTypesNames) { if (isChildAllowed(childName, superName, cnd, reqName, reqNodeType)) { return true; } } } } allChildNodeDefinitions = getAllChildNodeDefinitions(parentMixinNames); for (final NodeDefinitionData cnd : allChildNodeDefinitions) { for (final InternalQName reqNodeType : cnd.getRequiredPrimaryTypes()) { InternalQName reqName = cnd.getName(); if (isChildAllowed(childName, childNodeType, cnd, reqName, reqNodeType)) { return true; } for (final InternalQName superName : testSuperTypesNames) { if (isChildAllowed(childName, superName, cnd, reqName, reqNodeType)) { return true; } } } } return false; } /** * Checks if node with <code>childName</code>as name and <code>childNodeType</code> as node type * allowed as child. */ private boolean isChildAllowed(final InternalQName childName, final InternalQName childNodeType, final NodeDefinitionData cnd, InternalQName reqName, final InternalQName reqNodeType) { return childNodeType.equals(reqNodeType) && (cnd.isResidualSet() || reqName.equals(childName)); } /** * {@inheritDoc} */ public boolean isNodeType(final InternalQName testTypeName, final InternalQName... typesNames) { return this.nodeTypeRepository.isNodeType(testTypeName, typesNames); } /** * {@inheritDoc} */ public boolean isNodeType(final InternalQName testTypeName, final InternalQName primaryType, final InternalQName[] mixinTypes) { if (this.nodeTypeRepository.isNodeType(testTypeName, primaryType)) { return true; } if (this.nodeTypeRepository.isNodeType(testTypeName, mixinTypes)) { return true; } return false; } /** * {@inheritDoc} * @throws RepositoryException */ public boolean isOrderableChildNodesSupported(final InternalQName primaryType, final InternalQName[] mixinTypes) throws RepositoryException { final int nlen = mixinTypes != null ? mixinTypes.length : 0; for (int i = -1; i < nlen; i++) { InternalQName name; if (i < 0) { name = primaryType; } else { name = mixinTypes[i]; } final NodeTypeData nt = this.nodeTypeRepository.getNodeType(name); if (nt != null) { if (nt.hasOrderableChildNodes()) { return true; } final Set<InternalQName> supers = this.nodeTypeRepository.getSupertypes(nt.getName()); for (final InternalQName suName : supers) { final NodeTypeData su = this.nodeTypeRepository.getNodeType(suName); if (su != null && su.hasOrderableChildNodes()) { return true; } } } } return false; } /** * {@inheritDoc} */ public List<NodeTypeData> registerNodeTypes(final InputStream is, final int alreadyExistsBehaviour, final String contentType) throws RepositoryException { NodeTypeDataPersister serializer = null; if (contentType.equalsIgnoreCase(TEXT_XML)) { serializer = new XmlNodeTypeDataPersister(nodeTypeConverter, is); } else if (contentType.equalsIgnoreCase(TEXT_X_JCR_CND)) { serializer = new CNDNodeTypeDataPersister(is, (NamespaceRegistryImpl)namespaceRegistry); } else { throw new RepositoryException("Unsupported content type:" + contentType); } final List<NodeTypeData> nodeTypes = serializer.getAllNodeTypes(); return registerListOfNodeTypes(nodeTypes, alreadyExistsBehaviour); } /** * {@inheritDoc} */ public List<NodeTypeData> registerNodeTypes(final List<NodeTypeValue> ntvalues, final int alreadyExistsBehaviour) throws RepositoryException { // convert to Node data. final List<NodeTypeData> nodeTypes = nodeTypeConverter.convertFromValueToData(ntvalues); return registerListOfNodeTypes(nodeTypes, alreadyExistsBehaviour); } /** * Registers the provided node types */ private List<NodeTypeData> registerListOfNodeTypes(final List<NodeTypeData> nodeTypes, final int alreadyExistsBehaviour) throws RepositoryException { // validate nodeTypeDataValidator.validateNodeType(nodeTypes); nodeTypeRepository.registerNodeType(nodeTypes, this, accessControlPolicy, alreadyExistsBehaviour); for (NodeTypeData nodeType : nodeTypes) { for (NodeTypeManagerListener listener : listeners.values()) { listener.nodeTypeRegistered(nodeType.getName()); } } if (started && rpcService != null && repository != null && repository.getState() == ManageableRepository.ONLINE) { try { String[] names = new String[nodeTypes.size()]; for (int i = 0; i < names.length; i++) { names[i] = nodeTypes.get(i).getName().getAsString(); } rpcService.executeCommandOnAllNodes(registerNodeTypes, false, id, names); } catch (Exception e) { LOG.warn("Could not register the node types on other cluster nodes", e); } } return nodeTypes; } /** * Remove a <code>NodeTypeRegistryListener</code>. * * @param listener an existing listener */ public void removeListener(final NodeTypeManagerListener listener) { this.listeners.remove(listener); } /** * {@inheritDoc} */ public PlainChangesLog setPrimaryType(final NodeData nodeData, final InternalQName nodeTypeName) throws RepositoryException { final PlainChangesLog changesLog = new PlainChangesLogImpl(); final NodeTypeData ancestorDefinition = getNodeType(nodeData.getPrimaryTypeName()); final NodeTypeData recipientDefinition = getNodeType(nodeTypeName); InternalQName[] ancestorAllNodeTypeNames = null; if (nodeData.getMixinTypeNames() == null || nodeData.getMixinTypeNames().length == 0) { ancestorAllNodeTypeNames = new InternalQName[]{nodeData.getPrimaryTypeName()}; } else { ancestorAllNodeTypeNames = new InternalQName[nodeData.getMixinTypeNames().length + 1]; ancestorAllNodeTypeNames[0] = nodeData.getPrimaryTypeName(); System.arraycopy(nodeData.getMixinTypeNames(), 0, ancestorAllNodeTypeNames, 1, nodeData.getMixinTypeNames().length); } InternalQName[] recipienAllNodeTypeNames = null; if (nodeData.getMixinTypeNames() == null || nodeData.getMixinTypeNames().length == 0) { recipienAllNodeTypeNames = new InternalQName[]{nodeTypeName}; } else { recipienAllNodeTypeNames = new InternalQName[nodeData.getMixinTypeNames().length + 1]; recipienAllNodeTypeNames[0] = nodeTypeName; System.arraycopy(nodeData.getMixinTypeNames(), 0, recipienAllNodeTypeNames, 1, nodeData.getMixinTypeNames().length); } final boolean recipientsMixVersionable = isNodeType(Constants.MIX_VERSIONABLE, recipienAllNodeTypeNames); final boolean ancestorIsMixVersionable = isNodeType(Constants.MIX_VERSIONABLE, ancestorAllNodeTypeNames); ItemAutocreator itemAutocreator = new ItemAutocreator(this, valueFactory, dataManager, false); if (recipientsMixVersionable && !ancestorIsMixVersionable) { changesLog.addAll(itemAutocreator.makeMixVesionableChanges(nodeData).getAllStates()); } else if (!recipientsMixVersionable && ancestorIsMixVersionable) { final StringBuilder buffer = new StringBuilder(); buffer.append("Fail to change node type from "); buffer.append(ancestorDefinition.getName().getAsString()); buffer.append(" to "); buffer.append(recipientDefinition.getName().getAsString()); buffer.append(" because change from mix:versionable = true "); buffer.append(" to mix:versionable = false is not alowed"); throw new ConstraintViolationException(buffer.toString()); } // update primary type final PropertyData item = (PropertyData)this.dataManager.getItemData(nodeData, new QPathEntry(Constants.JCR_PRIMARYTYPE, 1), ItemType.PROPERTY); final TransientPropertyData primaryTypeData = new TransientPropertyData(item.getQPath(), item.getIdentifier(), item.getPersistedVersion(), item.getType(), item.getParentIdentifier(), item.isMultiValued(), new TransientValueData(nodeTypeName)); changesLog.add(ItemState.createUpdatedState(primaryTypeData, true)); final List<NodeData> affectedNodes = new ArrayList<NodeData>(); affectedNodes.add(nodeData); // child nodes final NodeDefinitionComparator nodeDefinitionComparator = new NodeDefinitionComparator(this, dataManager, itemAutocreator, affectedNodes); changesLog.addAll(nodeDefinitionComparator.compare(recipientDefinition, getAllChildNodeDefinitions(ancestorAllNodeTypeNames), getAllChildNodeDefinitions(recipienAllNodeTypeNames)) .getAllStates()); // properties defs final PropertyDefinitionComparator propertyDefinitionComparator = new PropertyDefinitionComparator(this, dataManager, itemAutocreator, affectedNodes, locationFactory); changesLog.addAll(propertyDefinitionComparator.compare(recipientDefinition, getAllPropertyDefinitions(ancestorAllNodeTypeNames), getAllPropertyDefinitions(recipienAllNodeTypeNames)) .getAllStates()); return changesLog; } /** * * @see org.picocontainer.Startable#start() */ public void start() { if (!started) { try { // check if default node type saved if (!nodeTypeRepository.isStorageFilled()) { final InputStream xml = SecurityHelper.doPrivilegedAction(new PrivilegedAction<InputStream>() { public InputStream run() { return NodeTypeManagerImpl.class.getResourceAsStream(NODETYPES_FILE); } }); if (xml != null) { List<NodeTypeData> registerNodeTypes = registerNodeTypes(xml, ExtendedNodeTypeManager.IGNORE_IF_EXISTS, TEXT_XML); for (NodeTypeData nodeTypeData : registerNodeTypes) { buildInNodeTypesNames.add(nodeTypeData.getName()); } } else { throw new RuntimeException("Resource file '" + NODETYPES_FILE + "' with NodeTypes configuration does not found. Can not create node type manager"); } } else { final List<NodeTypeData> allNodeTypes = nodeTypeRepository.getAllNodeTypes(); // register nodetypes in runtime final Map<InternalQName, NodeTypeData> volatileNodeTypes = new HashMap<InternalQName, NodeTypeData>(); //create map from list for (final NodeTypeData nodeTypeData : allNodeTypes) { volatileNodeTypes.put(nodeTypeData.getName(), nodeTypeData); } for (final NodeTypeData nodeTypeData : allNodeTypes) { this.nodeTypeRepository.addNodeType(nodeTypeData, volatileNodeTypes); } } } catch (final RepositoryException e) { throw new RuntimeException(e.getLocalizedMessage(), e); } if (rpcService != null) { try { RepositoryService rs = (RepositoryService)ExoContainerContext.getCurrentContainer().getComponentInstanceOfType(RepositoryService.class); repository = rs.getRepository(repositoryName); } catch (Exception e) { LOG.warn("Could not get the repository '" + repositoryName + "'", e); } } started = true; } } /** * * @see org.picocontainer.Startable#stop() */ public void stop() { unregisterRemoteCommands(); } /** * Unregisters the specified node type. In order for a node type to be * successfully unregistered it must meet the following conditions: * <ol> * <li>the node type must obviously be registered.</li> * <li>a built-in node type can not be unregistered.</li> * <li>the node type must not have dependents, i.e. other node types that are * referencing it.</li> * <li>the node type must not be currently used by any workspace.</li> * </ol> * * @param nodeTypeName name of the node type to be unregistered * @throws NoSuchNodeTypeException if <code>ntName</code> does not denote a * registered node type. * @throws RepositoryException * @throws RepositoryException if another error occurs. * @see #unregisterNodeType(InternalQName) */ public void unregisterNodeType(final InternalQName nodeTypeName) throws RepositoryException { final NodeTypeData nodeType = this.nodeTypeRepository.getNodeType(nodeTypeName); if (nodeType == null) { throw new NoSuchNodeTypeException(nodeTypeName.getAsString()); } // check build in if (this.buildInNodeTypesNames.contains(nodeTypeName)) { throw new RepositoryException(nodeTypeName.toString() + ": can't unregister built-in node type."); } // check dependencies final Set<InternalQName> descendantNt = this.nodeTypeRepository.getSubtypes(nodeTypeName); if (descendantNt.size() > 0) { StringBuilder message = new StringBuilder("Can not remove ").append(nodeTypeName.getAsString()).append( "nodetype, because the following node types depend on it: "); for (final InternalQName internalQName : descendantNt) { message.append(internalQName.getAsString()).append(" "); } throw new RepositoryException(message.toString()); } final Set<String> nodes = this.indexSearcherHolder.getNodesByNodeType(nodeTypeName); if (nodes.size() > 0) { StringBuilder message = new StringBuilder("Can not remove ").append(nodeTypeName.getAsString()).append( " nodetype, because the following node types is used in nodes with uuid: "); for (final String uuids : nodes) { message.append(uuids).append(" "); } throw new RepositoryException(message.toString()); } this.nodeTypeRepository.unregisterNodeType(nodeType); for (NodeTypeManagerListener listener : listeners.values()) { listener.nodeTypeUnregistered(nodeType.getName()); } if (started && rpcService != null && repository != null && repository.getState() == ManageableRepository.ONLINE) { try { rpcService.executeCommandOnAllNodes(unregisterNodeType, false, id, nodeType.getName().getAsString()); } catch (Exception e) { LOG.warn("Could not unregister the node type on other cluster nodes", e); } } } /** * {@inheritDoc} */ public PlainChangesLog updateNodeType(NodeTypeData ancestorDefinition, NodeTypeData recipientDefinition, Map<InternalQName, NodeTypeData> volatileNodeTypes) throws ConstraintViolationException, RepositoryException { if (!ancestorDefinition.getName().equals(recipientDefinition.getName())) { throw new RepositoryException("Unsupported Operation"); } if (this.buildInNodeTypesNames.contains(recipientDefinition.getName())) { throw new RepositoryException(recipientDefinition.getName() + ": can't reregister built-in node type."); } final PlainChangesLog changesLog = new PlainChangesLogImpl(); final VolatileNodeTypeDataManager volatileNodeTypeDataManager = new VolatileNodeTypeDataManager(this); volatileNodeTypeDataManager.registerVolatileNodeTypes(volatileNodeTypes); ItemAutocreator itemAutocreator = new ItemAutocreator(volatileNodeTypeDataManager, valueFactory, dataManager, false); final Set<String> nodes = this.indexSearcherHolder.getNodesByNodeType(recipientDefinition.getName()); // check add mix:versionable super if (isNodeType(Constants.MIX_VERSIONABLE, recipientDefinition.getDeclaredSupertypeNames()) && !isNodeType(Constants.MIX_VERSIONABLE, ancestorDefinition.getDeclaredSupertypeNames())) { for (final String uuid : nodes) { final ItemData item = this.dataManager.getItemData(uuid); if (item != null && item.isNode()) { changesLog.addAll(itemAutocreator.makeMixVesionableChanges((NodeData)item).getAllStates()); } } } else if (!isNodeType(Constants.MIX_VERSIONABLE, recipientDefinition.getDeclaredSupertypeNames()) && isNodeType(Constants.MIX_VERSIONABLE, ancestorDefinition.getDeclaredSupertypeNames())) { if (nodes.size() > 0) { final StringBuilder buffer = new StringBuilder(); buffer.append("Fail to change "); buffer.append(recipientDefinition.getName().getAsString()); buffer.append(" node type from mix:versionable = true to mix:versionable = false"); buffer.append(" because the folowing node exists: "); if (nodes.size() < 100) { for (final String uuid : nodes) { final ItemData item = this.dataManager.getItemData(uuid); if (item != null && item.isNode()) { buffer.append(item.getQPath().getAsString()); buffer.append(" "); } } } throw new ConstraintViolationException(buffer.toString()); } } final List<NodeData> affectedNodes = new ArrayList<NodeData>(); for (final String uuid : nodes) { final ItemData nodeData = this.dataManager.getItemData(uuid); if (nodeData != null) { if (nodeData.isNode()) { affectedNodes.add((NodeData)nodeData); } } } // child nodes final NodeDefinitionComparator nodeDefinitionComparator = new NodeDefinitionComparator(volatileNodeTypeDataManager, this.dataManager, itemAutocreator, affectedNodes); changesLog.addAll(nodeDefinitionComparator.compare(recipientDefinition, getAllChildNodeDefinitions(ancestorDefinition.getName()), volatileNodeTypeDataManager.getAllChildNodeDefinitions(recipientDefinition.getName())).getAllStates()); // properties defs final PropertyDefinitionComparator propertyDefinitionComparator = new PropertyDefinitionComparator(volatileNodeTypeDataManager, this.dataManager, itemAutocreator, affectedNodes, this.locationFactory); changesLog.addAll(propertyDefinitionComparator.compare(recipientDefinition, getAllPropertyDefinitions(ancestorDefinition.getName()), volatileNodeTypeDataManager.getAllPropertyDefinitions(recipientDefinition.getName())).getAllStates()); // notify listeners about changes if (!Arrays.deepEquals(recipientDefinition.getDeclaredSupertypeNames(), ancestorDefinition .getDeclaredSupertypeNames())) { for (final String uuid : nodes) { ItemData item = this.dataManager.getItemData(uuid); if (item != null && item.isNode()) { if (!(item instanceof TransientNodeData)) { item = new TransientNodeData(item.getQPath(), item.getIdentifier(), item.getPersistedVersion(), ((NodeData)item).getPrimaryTypeName(), ((NodeData)item).getMixinTypeNames(), ((NodeData)item) .getOrderNumber(), ((NodeData)item).getParentIdentifier(), ((NodeData)item).getACL()); } changesLog.add(new ItemState(item, ItemState.MIXIN_CHANGED, false, null)); } } } // mixin changed if (ancestorDefinition.isMixin() != recipientDefinition.isMixin()) { if (nodes.size() > 0) { final StringBuilder buffer = new StringBuilder(); buffer.append("Fail to change "); buffer.append(recipientDefinition.getName().getAsString()); buffer.append(" node type from IsMixin="); buffer.append(ancestorDefinition.isMixin()); buffer.append(" to IsMixin="); buffer.append(recipientDefinition.isMixin()); buffer.append(" because the folowing node exists: "); if (nodes.size() < 100) { for (final String uuid : nodes) { final ItemData item = this.dataManager.getItemData(uuid); if (item != null && item.isNode()) { buffer.append(item.getQPath().getAsString()); buffer.append(" "); } } } throw new ConstraintViolationException(buffer.toString()); } } this.nodeTypeRepository.removeNodeType(ancestorDefinition); this.nodeTypeRepository.addNodeType(recipientDefinition, volatileNodeTypes); for (NodeTypeManagerListener listener : listeners.values()) { listener.nodeTypeReRegistered(recipientDefinition.getName()); } return changesLog; } private InternalQName[] getNodeTypeNames(final InternalQName primaryNodeType, final InternalQName[] mixinTypes) throws RepositoryException { InternalQName[] ntn = new InternalQName[1 + (mixinTypes == null ? 0 : mixinTypes.length)]; ntn[0] = primaryNodeType; if (mixinTypes != null) { System.arraycopy(mixinTypes, 0, ntn, 1, mixinTypes.length); } return ntn; } }