/* * 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.nodetype; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.TreeSet; import java.util.List; import java.util.ArrayList; import javax.jcr.NamespaceRegistry; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.version.OnParentVersionAction; import org.apache.commons.collections.map.ReferenceMap; import org.apache.commons.io.IOUtils; import org.apache.jackrabbit.core.cluster.NodeTypeEventChannel; import org.apache.jackrabbit.core.cluster.NodeTypeEventListener; import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemException; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QValueConstraint; import org.apache.jackrabbit.spi.QValue; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.apache.jackrabbit.spi.commons.name.NameConstants; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.apache.jackrabbit.spi.commons.nodetype.NodeTypeDefDiff; import org.apache.jackrabbit.spi.commons.nodetype.QNodeDefinitionBuilder; import org.apache.jackrabbit.spi.commons.QNodeTypeDefinitionImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap; /** * A <code>NodeTypeRegistry</code> ... */ public class NodeTypeRegistry implements NodeTypeEventListener { private static Logger log = LoggerFactory.getLogger(NodeTypeRegistry.class); private static final String BUILTIN_NODETYPES_RESOURCE_PATH = "org/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd"; private static final String CUSTOM_NODETYPES_RESOURCE_NAME = "/nodetypes/custom_nodetypes.xml"; /** * Feature flag for the unfortunate behavior in Jackrabbit 2.1 and 2.2 * where the exception from {@link #checkForReferencesInContent(Name)} * was never thrown because of a mistaken commit for * <a href="https://issues.apache.org/jira/browse/JCR-2587">JCR-2587</a>. * Setting this flag to <code>true</code> (the default value comes from * the "disableCheckForReferencesInContentException" system property) * will disable the exception thrown by default by the method. * * @see <a href="https://issues.apache.org/jira/browse/JCR-3223">JCR-3223</a> */ public static volatile boolean disableCheckForReferencesInContentException = Boolean.getBoolean("disableCheckForReferencesInContentException"); /** * resource holding custom node type definitions which are represented as * nodes in the repository; it is needed in order to make the registrations * persistent. */ private final FileSystemResource customNodeTypesResource; // persistent node type definitions of built-in & custom node types private final NodeTypeDefStore builtInNTDefs; private final NodeTypeDefStore customNTDefs; // cache of pre-built aggregations of node types private EffectiveNodeTypeCache entCache; // map of node type names and node type definitions private final Map<Name, QNodeTypeDefinition> registeredNTDefs; // definition of the root node private final QNodeDefinition rootNodeDef; /** * namespace registry for resolving prefixes and namespace URI's; * used for (de)serializing node type definitions */ private final NamespaceRegistry nsReg; /** * Listeners (weak references) */ @SuppressWarnings("unchecked") private final Map<NodeTypeRegistryListener, NodeTypeRegistryListener> listeners = Collections.synchronizedMap(new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK)); /** * Node type event channel. */ private NodeTypeEventChannel eventChannel; //----------------------------------------< public NodeTypeRegistry 'api' > /** * Returns the names of all registered node types. That includes primary * and mixin node types. * * @return the names of all registered node types. */ public Name[] getRegisteredNodeTypes() { return registeredNTDefs.keySet().toArray(new Name[registeredNTDefs.size()]); } /** * Validates the <code>NodeTypeDef</code> and returns * an <code>EffectiveNodeType</code> object representing the newly * registered node type. * <p> * The validation includes the following checks: * <ul> * <li>Supertypes must exist and be registered</li> * <li>Inheritance graph must not be circular</li> * <li>Aggregation of supertypes must not result in name conflicts, * ambiguities, etc.</li> * <li>Definitions of auto-created properties must specify a name</li> * <li>Default values in property definitions must satisfy value constraints * specified in the same property definition</li> * <li>Definitions of auto-created child-nodes must specify a name</li> * <li>Default node type in child-node definitions must exist and be * registered</li> * <li>The aggregation of the default node types in child-node definitions * must not result in name conflicts, ambiguities, etc.</li> * <li>Definitions of auto-created child-nodes must not specify default * node types which would lead to infinite child node creation * (e.g. node type 'A' defines auto-created child node with default * node type 'A' ...)</li> * <li>Node types specified as constraints in child-node definitions * must exist and be registered</li> * <li>The aggregation of the node types specified as constraints in * child-node definitions must not result in name conflicts, ambiguities, * etc.</li> * <li>Default node types in child-node definitions must satisfy * node type constraints specified in the same child-node definition</li> * </ul> * * @param ntd the definition of the new node type * @return an <code>EffectiveNodeType</code> instance * @throws InvalidNodeTypeDefException if the given node type definition is invalid. * @throws RepositoryException if a repository error occurs. */ public EffectiveNodeType registerNodeType(QNodeTypeDefinition ntd) throws InvalidNodeTypeDefException, RepositoryException { EffectiveNodeType ent; synchronized (this) { // validate and register new node type definition ent = internalRegister(ntd); // persist new node type definition customNTDefs.add(ntd); persistCustomNodeTypeDefs(customNTDefs); // notify listeners notifyRegistered(ntd.getName()); } if (eventChannel != null) { Set<QNodeTypeDefinition> ntDefs = new HashSet<QNodeTypeDefinition>(); ntDefs.add(ntd); eventChannel.registered(ntDefs); } return ent; } /** * Same as <code>{@link #registerNodeType(QNodeTypeDefinition)}</code> except * that a collection of <code>NodeTypeDef</code>s is registered instead of * just one. * <p> * This method can be used to register a set of node types that have * dependencies on each other. * * @param ntDefs a collection of <code>QNodeTypeDefinition</code> objects * @throws InvalidNodeTypeDefException if the given node type definition is invalid. * @throws RepositoryException if a repository error occurs. */ public void registerNodeTypes(Collection<QNodeTypeDefinition> ntDefs) throws InvalidNodeTypeDefException, RepositoryException { registerNodeTypes(ntDefs, false); } /** * Internal implementation of {@link #registerNodeTypes(Collection)} * * @param ntDefs a collection of <code>QNodeTypeDefinition<code> objects * @param external whether this invocation should be considered external * @throws InvalidNodeTypeDefException if the given node type definition is invalid. * @throws RepositoryException if a repository error occurs. */ private void registerNodeTypes(Collection<QNodeTypeDefinition> ntDefs, boolean external) throws InvalidNodeTypeDefException, RepositoryException { synchronized (this) { // validate and register new node type definitions internalRegister(ntDefs, external); // persist new node type definitions for (QNodeTypeDefinition ntDef: ntDefs) { customNTDefs.add(ntDef); } persistCustomNodeTypeDefs(customNTDefs); // notify listeners for (QNodeTypeDefinition ntDef : ntDefs) { notifyRegistered(ntDef.getName()); } } // inform cluster if this is not an external invocation if (!external && eventChannel != null) { eventChannel.registered(ntDefs); } } /** * Same as <code>{@link #unregisterNodeType(Name)}</code> except * that a set of node types is unregistered instead of just one. * <p> * This method can be used to unregister a set of node types that depend on * each other. * * @param ntNames a collection of <code>Name</code> objects denoting the * node types to be unregistered * @throws NoSuchNodeTypeException if any of the specified names does not * denote a registered node type. * @throws RepositoryException if another error occurs * @see #unregisterNodeType(Name) */ public void unregisterNodeTypes(Set<Name> ntNames) throws NoSuchNodeTypeException, RepositoryException { unregisterNodeTypes(ntNames, false); } /** * Internal implementation of {@link #unregisterNodeTypes(Set)} * * @param ntNames a collection of <code>Name</code> objects denoting the * node types to be unregistered * @param external whether this invocation should be considered external * @throws NoSuchNodeTypeException if any of the specified names does not * denote a registered node type. * @throws RepositoryException if another error occurs */ private void unregisterNodeTypes( Collection<Name> ntNames, boolean external) throws NoSuchNodeTypeException, RepositoryException { synchronized (this) { // do some preliminary checks for (Name ntName: ntNames) { if (!registeredNTDefs.containsKey(ntName)) { throw new NoSuchNodeTypeException(ntName.toString()); } if (builtInNTDefs.contains(ntName)) { throw new RepositoryException(ntName.toString() + ": can't unregister built-in node type."); } // check for node types other than those to be unregistered // that depend on the given node types Set<Name> dependents = getDependentNodeTypes(ntName); dependents.removeAll(ntNames); if (dependents.size() > 0) { StringBuilder msg = new StringBuilder(); msg.append(ntName).append(" can not be removed because the following node types depend on it: "); for (Name dependent : dependents) { msg.append(dependent); msg.append(" "); } throw new RepositoryException(msg.toString()); } } // make sure node types are not currently in use for (Name ntName : ntNames) { checkForReferencesInContent(ntName); } // all preconditions are met, node types can now safely be unregistered internalUnregister(ntNames); // persist removal of node type definitions & notify listeners for (Name ntName : ntNames) { customNTDefs.remove(ntName); } notifyUnregistered(ntNames); persistCustomNodeTypeDefs(customNTDefs); } // inform cluster if this is not an external invocation if (!external && eventChannel != null) { eventChannel.unregistered(ntNames); } } /** * 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 ntName name of the node type to be unregistered * @throws NoSuchNodeTypeException if <code>ntName</code> does not * denote a registered node type. * @throws RepositoryException if another error occurs. * @see #unregisterNodeTypes(Collection, boolean) */ public void unregisterNodeType(Name ntName) throws NoSuchNodeTypeException, RepositoryException { HashSet<Name> ntNames = new HashSet<Name>(); ntNames.add(ntName); unregisterNodeTypes(ntNames); } /** * Reregister a node type. * @param ntd node type definition * @return the new effective node type * @throws NoSuchNodeTypeException if <code>ntd</code> refers to an * unknown node type * @throws InvalidNodeTypeDefException if the node type definition * is invalid * @throws RepositoryException if another error occurs */ public EffectiveNodeType reregisterNodeType(QNodeTypeDefinition ntd) throws NoSuchNodeTypeException, InvalidNodeTypeDefException, RepositoryException { return reregisterNodeType(ntd, false); } /** * Internal implementation of {@link #reregisterNodeType(QNodeTypeDefinition)}. * * @param ntd node type definition * @param external whether this invocation should be considered external * @return the new effective node type * @throws NoSuchNodeTypeException if <code>ntd</code> refers to an * unknown node type * @throws InvalidNodeTypeDefException if the node type definition * is invalid * @throws RepositoryException if another error occurs */ private EffectiveNodeType reregisterNodeType(QNodeTypeDefinition ntd, boolean external) throws NoSuchNodeTypeException, InvalidNodeTypeDefException, RepositoryException { EffectiveNodeType entNew; synchronized (this) { Name name = ntd.getName(); if (!registeredNTDefs.containsKey(name)) { throw new NoSuchNodeTypeException(name.toString()); } if (builtInNTDefs.contains(name)) { throw new RepositoryException(name.toString() + ": can't reregister built-in node type."); } /** * validate new node type definition */ ntd = checkNtBaseSubtyping(ntd, registeredNTDefs); validateNodeTypeDef(ntd, entCache, registeredNTDefs, nsReg, false); /** * build diff of current and new definition and determine type of change */ QNodeTypeDefinition ntdOld = registeredNTDefs.get(name); NodeTypeDefDiff diff = NodeTypeDefDiff.create(ntdOld, ntd); if (!diff.isModified()) { // the definition has not been modified, there's nothing to do here... return getEffectiveNodeType(name); } // make sure existing content would not conflict // with new node type definition checkForConflictingContent(ntd, diff); /** * re-register node type definition and update caches & * notify listeners on re-registration */ internalUnregister(name); // remove old node type definition from store customNTDefs.remove(name); entNew = internalRegister(ntd); // add new node type definition to store customNTDefs.add(ntd); // persist node type definitions persistCustomNodeTypeDefs(customNTDefs); // notify listeners notifyReRegistered(name); } // inform cluster if this is not an external invocation if (!external && eventChannel != null) { eventChannel.reregistered(ntd); } return entNew; } /** * @param ntName name * @return effective node type * @throws NoSuchNodeTypeException if node type does not exist */ public EffectiveNodeType getEffectiveNodeType(Name ntName) throws NoSuchNodeTypeException { return getEffectiveNodeType(ntName, entCache, registeredNTDefs); } /** * Returns the effective node type of a node with the given primary * and mixin types. * * @param primary primary type of the node * @param mixins mixin types of the node (set of {@link Name names}); * @return effective node type * @throws NodeTypeConflictException if the given types are conflicting * @throws NoSuchNodeTypeException if one of the given types is not found */ public EffectiveNodeType getEffectiveNodeType(Name primary, Set<Name> mixins) throws NodeTypeConflictException, NoSuchNodeTypeException { if (mixins.isEmpty()) { return getEffectiveNodeType(primary); } else { Name[] names = new Name[mixins.size() + 1]; mixins.toArray(names); names[names.length - 1] = primary; return getEffectiveNodeType(names, entCache, registeredNTDefs); } } /** * Returns the effective node type representation of the given node types. * * @param mixins mixin types of the node (set of {@link Name names}); * @return effective node type * @throws NodeTypeConflictException if the given types are conflicting * @throws NoSuchNodeTypeException if one of the given types is not found */ public EffectiveNodeType getEffectiveNodeType(Set<Name> mixins) throws NodeTypeConflictException, NoSuchNodeTypeException { Name[] names = new Name[mixins.size()]; mixins.toArray(names); return getEffectiveNodeType(names, entCache, registeredNTDefs); } /** * Returns the names of those registered node types that have * dependencies on the given node type. * * @param nodeTypeName node type name * @return a set of node type <code>Name</code>s * @throws NoSuchNodeTypeException if node type does not exist */ public Set<Name> getDependentNodeTypes(Name nodeTypeName) throws NoSuchNodeTypeException { if (!registeredNTDefs.containsKey(nodeTypeName)) { throw new NoSuchNodeTypeException(nodeTypeName.toString()); } /** * collect names of those node types that have dependencies on the given * node type */ HashSet<Name> names = new HashSet<Name>(); for (QNodeTypeDefinition ntd : registeredNTDefs.values()) { if (ntd.getDependencies().contains(nodeTypeName)) { names.add(ntd.getName()); } } return names; } /** * Returns the node type definition of the node type with the given name. * * @param nodeTypeName name of node type whose definition should be returned. * @return the node type definition of the node type with the given name. * @throws NoSuchNodeTypeException if a node type with the given name * does not exist */ public QNodeTypeDefinition getNodeTypeDef(Name nodeTypeName) throws NoSuchNodeTypeException { QNodeTypeDefinition def = registeredNTDefs.get(nodeTypeName); if (def == null) { throw new NoSuchNodeTypeException(nodeTypeName.toString()); } return def; } /** * @param nodeTypeName node type name * @return <code>true</code> if the specified node type is registered; * <code>false</code> otherwise. */ public boolean isRegistered(Name nodeTypeName) { return registeredNTDefs.containsKey(nodeTypeName); } /** * @param nodeTypeName node type name * @return <code>true</code> if the specified node type is built-in; * <code>false</code> otherwise. */ public boolean isBuiltIn(Name nodeTypeName) { return builtInNTDefs.contains(nodeTypeName); } /** * Add a <code>NodeTypeRegistryListener</code> * * @param listener the new listener to be informed on (un)registration * of node types */ public void addListener(NodeTypeRegistryListener listener) { if (!listeners.containsKey(listener)) { listeners.put(listener, listener); } } /** * Remove a <code>NodeTypeRegistryListener</code> * * @param listener an existing listener */ public void removeListener(NodeTypeRegistryListener listener) { listeners.remove(listener); } //--------------------------------------------------------------< Object > /** * {@inheritDoc} */ public String toString() { StringBuilder builder = new StringBuilder(); builder.append("NodeTypeRegistry (" + super.toString() + ")\n"); builder.append("Registered NodeTypes:\n"); for (QNodeTypeDefinition ntd : registeredNTDefs.values()) { builder.append(ntd.getName()); builder.append("\n"); builder.append( "\tSupertypes: " + Arrays.toString(ntd.getSupertypes()) + "\n"); builder.append("\tMixin\t" + ntd.isMixin() + "\n"); builder.append("\tOrderableChildNodes\t" + ntd.hasOrderableChildNodes() + "\n"); builder.append("\tPrimaryItemName\t" + (ntd.getPrimaryItemName() == null ? "<null>" : ntd.getPrimaryItemName().toString()) + "\n"); QPropertyDefinition[] pd = ntd.getPropertyDefs(); for (QPropertyDefinition aPd : pd) { builder.append("\tPropertyDefinition\n"); builder.append(" (declared in " + aPd.getDeclaringNodeType() + ")\n"); builder.append("\t\tName\t\t" + (aPd.definesResidual() ? "*" : aPd.getName().toString()) + "\n"); String type = aPd.getRequiredType() == 0 ? "null" : PropertyType.nameFromValue(aPd.getRequiredType()); builder.append("\t\tRequiredType\t" + type + "\n"); QValueConstraint[] vca = aPd.getValueConstraints(); StringBuilder constraints = new StringBuilder(); if (vca == null) { constraints.append("<null>"); } else { for (QValueConstraint aVca : vca) { if (constraints.length() > 0) { constraints.append(", "); } constraints.append(aVca.getString()); } } builder.append("\t\tValueConstraints\t" + constraints + "\n"); QValue[] defVals = aPd.getDefaultValues(); StringBuilder defaultValues = new StringBuilder(); if (defVals == null) { defaultValues.append("<null>"); } else { for (QValue defVal : defVals) { if (defaultValues.length() > 0) { defaultValues.append(", "); } defaultValues.append(defVal.toString()); } } builder.append("\t\tDefaultValue\t" + defaultValues + "\n"); builder.append("\t\tAutoCreated\t" + aPd.isAutoCreated() + "\n"); builder.append("\t\tMandatory\t" + aPd.isMandatory() + "\n"); builder.append("\t\tOnVersion\t" + OnParentVersionAction.nameFromValue(aPd.getOnParentVersion()) + "\n"); builder.append("\t\tProtected\t" + aPd.isProtected() + "\n"); builder.append("\t\tMultiple\t" + aPd.isMultiple() + "\n"); } QNodeDefinition[] nd = ntd.getChildNodeDefs(); for (QNodeDefinition aNd : nd) { builder.append("\tNodeDefinition\\n"); builder.append(" (declared in " + aNd.getDeclaringNodeType() + ")\\n"); builder.append("\t\tName\t\t" + (aNd.definesResidual() ? "*" : aNd.getName().toString()) + "\n"); Name[] reqPrimaryTypes = aNd.getRequiredPrimaryTypes(); if (reqPrimaryTypes != null && reqPrimaryTypes.length > 0) { for (Name reqPrimaryType : reqPrimaryTypes) { builder.append("\t\tRequiredPrimaryType\t" + reqPrimaryType + "\n"); } } Name defPrimaryType = aNd.getDefaultPrimaryType(); if (defPrimaryType != null) { builder.append("\n\t\tDefaultPrimaryType\t" + defPrimaryType + "\n"); } builder.append("\n\t\tAutoCreated\t" + aNd.isAutoCreated() + "\n"); builder.append("\t\tMandatory\t" + aNd.isMandatory() + "\n"); builder.append("\t\tOnVersion\t" + OnParentVersionAction.nameFromValue(aNd.getOnParentVersion()) + "\n"); builder.append("\t\tProtected\t" + aNd.isProtected() + "\n"); builder.append("\t\tAllowsSameNameSiblings\t" + aNd.allowsSameNameSiblings() + "\n"); } } builder.append(entCache); return builder.toString(); } //------------------------------------------------< NodeTypeEventListener > /** * {@inheritDoc} */ public void externalRegistered(Collection<QNodeTypeDefinition> ntDefs) throws RepositoryException, InvalidNodeTypeDefException { registerNodeTypes(ntDefs, true); } /** * {@inheritDoc} */ public void externalReregistered(QNodeTypeDefinition ntDef) throws NoSuchNodeTypeException, InvalidNodeTypeDefException, RepositoryException { reregisterNodeType(ntDef, true); } /** * {@inheritDoc} */ public void externalUnregistered(Collection<Name> ntNames) throws RepositoryException, NoSuchNodeTypeException { unregisterNodeTypes(ntNames, true); } //---------------------------------------------------------< overridables > /** * Constructor * * @param nsReg name space registry * @param fs repository file system * @throws RepositoryException if an error occurs */ @SuppressWarnings("unchecked") public NodeTypeRegistry(NamespaceRegistry nsReg, FileSystem fs) throws RepositoryException { this.nsReg = nsReg; customNodeTypesResource = new FileSystemResource(fs, CUSTOM_NODETYPES_RESOURCE_NAME); try { // make sure path to resource exists if (!customNodeTypesResource.exists()) { customNodeTypesResource.makeParentDirs(); } } catch (FileSystemException fse) { String error = "internal error: invalid resource: " + customNodeTypesResource.getPath(); log.debug(error); throw new RepositoryException(error, fse); } // use the improved node type cache // (replace with: entCache = new EffectiveNodeTypeCacheImpl(); // for the old one) entCache = new BitSetENTCacheImpl(); registeredNTDefs = new ConcurrentReaderHashMap(); // setup definition of root node rootNodeDef = createRootNodeDef(); // load and register pre-defined (i.e. built-in) node types builtInNTDefs = new NodeTypeDefStore(); try { // load built-in node type definitions loadBuiltInNodeTypeDefs(builtInNTDefs); // register built-in node types internalRegister(builtInNTDefs.all(), false, true); } catch (InvalidNodeTypeDefException intde) { String error = "internal error: invalid built-in node type definition stored in " + BUILTIN_NODETYPES_RESOURCE_PATH; log.debug(error); throw new RepositoryException(error, intde); } // load and register custom node types customNTDefs = new NodeTypeDefStore(); // load custom node type definitions loadCustomNodeTypeDefs(customNTDefs); // validate & register custom node types try { internalRegister(customNTDefs.all(), false); } catch (InvalidNodeTypeDefException intde) { String error = "internal error: invalid custom node type definition stored in " + customNodeTypesResource.getPath(); log.debug(error); throw new RepositoryException(error, intde); } } /** * Loads the built-in node type definitions into the given <code>store</code>. * <p> * This method may be overridden by extensions of this class; It must * only be called once and only from within the constructor though. * * @param store The {@link NodeTypeDefStore} into which the node type * definitions are loaded. * @throws RepositoryException If an error occurs while loading the * built-in node type definitions. */ protected void loadBuiltInNodeTypeDefs(NodeTypeDefStore store) throws RepositoryException { InputStream in = null; try { in = getClass().getClassLoader().getResourceAsStream(BUILTIN_NODETYPES_RESOURCE_PATH); if (in != null) { Reader r = new InputStreamReader(in, "utf-8"); store.loadCND(r, BUILTIN_NODETYPES_RESOURCE_PATH); } } catch (IOException ioe) { String error = "internal error: failed to read built-in node type definitions stored in " + BUILTIN_NODETYPES_RESOURCE_PATH; log.debug(error); throw new RepositoryException(error, ioe); } catch (InvalidNodeTypeDefException intde) { String error = "internal error: invalid built-in node type definition stored in " + BUILTIN_NODETYPES_RESOURCE_PATH; log.debug(error); throw new RepositoryException(error, intde); } finally { IOUtils.closeQuietly(in); } } /** * Loads the custom node type definitions into the given <code>store</code>. * <p> * This method may be overridden by extensions of this class; It must * only be called once and only from within the constructor though. * * @param store The {@link NodeTypeDefStore} into which the node type * definitions are loaded. * @throws RepositoryException If an error occurs while loading the * custom node type definitions. */ protected void loadCustomNodeTypeDefs(NodeTypeDefStore store) throws RepositoryException { InputStream in = null; try { if (customNodeTypesResource.exists()) { in = customNodeTypesResource.getInputStream(); } } catch (FileSystemException fse) { String error = "internal error: failed to access custom node type definitions stored in " + customNodeTypesResource.getPath(); log.debug(error); throw new RepositoryException(error, fse); } if (in == null) { log.info("no custom node type definitions found"); } else { try { store.load(in); } catch (IOException ioe) { String error = "internal error: failed to read custom node type definitions stored in " + customNodeTypesResource.getPath(); log.debug(error); throw new RepositoryException(error, ioe); } catch (InvalidNodeTypeDefException intde) { String error = "internal error: invalid custom node type definition stored in " + customNodeTypesResource.getPath(); log.debug(error); throw new RepositoryException(error, intde); } finally { try { in.close(); } catch (IOException ioe) { // ignore } } } } /** * Persists the custom node type definitions contained in the given * <code>store</code>. * * @param store The {@link NodeTypeDefStore} containing the definitions to * be persisted. * @throws RepositoryException If an error occurs while persisting the * custom node type definitions. */ protected void persistCustomNodeTypeDefs(NodeTypeDefStore store) throws RepositoryException { try { OutputStream out = customNodeTypesResource.getOutputStream(); try { store.store(out, nsReg); } finally { out.close(); } } catch (IOException ioe) { String error = "internal error: failed to persist custom node type definitions to " + customNodeTypesResource.getPath(); log.debug(error); throw new RepositoryException(error, ioe); } catch (FileSystemException fse) { String error = "internal error: failed to persist custom node type definitions to " + customNodeTypesResource.getPath(); log.debug(error); throw new RepositoryException(error, fse); } } /** * Checks whether there is existing content that would conflict with the * given node type definition. * <p> * This method is not implemented yet and always throws a * <code>RepositoryException</code>. * <p> * TODO * <ol> * <li>apply deep locks on root nodes in every workspace or alternatively * put repository in 'exclusive' or 'single-user' mode * <li>check if the given node type (or any node type that has * dependencies on this node type) is currently referenced by nodes * in the repository. * <li>check if applying the changed definitions to the affected items would * violate existing node type constraints * <li>apply and persist changes to affected nodes (e.g. update * definition id's, etc.) * </ol> * <p> * the above checks/actions are absolutely necessary in order to * guarantee integrity of repository content. * * * @param ntd The node type definition replacing the former node type * definition of the same name. * @param diff * @throws RepositoryException If there is conflicting content or if the * check failed for some other reason. */ protected void checkForConflictingContent(QNodeTypeDefinition ntd, final NodeTypeDefDiff diff) throws RepositoryException { if (!diff.isTrivial()) { /** * collect names of node types that have dependencies on the given * node type */ //Set dependentNTs = getDependentNodeTypes(ntd.getName()); String message = "The following node type change contains non-trivial changes." + "Up until now only trivial changes are supported." + " (see javadoc for " + NodeTypeDefDiff.class.getName() + "):\n" + diff.toString(); throw new RepositoryException(message); } /** * the change is trivial and has no effect on current content * (e.g. that would be the case when non-mandatory properties had * been added); */ } /** * Checks whether there is existing content that directly or indirectly * refers to the specified node type. * <p> * This method is not implemented yet and always throws a * <code>RepositoryException</code>. * <p> * TODO: * <ol> * <li>apply deep locks on root nodes in every workspace or alternatively * put repository in 'single-user' mode * <li>check if the given node type is currently referenced by nodes * in the repository. * <li>remove the node type if it is not currently referenced, otherwise * throw exception * </ol> * <p> * the above checks are absolutely necessary in order to guarantee * integrity of repository content. * * @param nodeTypeName The name of the node type to be checked. * @throws RepositoryException If the specified node type is currently * being referenced or if the check failed for * some other reason. */ protected void checkForReferencesInContent(Name nodeTypeName) throws RepositoryException { if (!disableCheckForReferencesInContentException) { throw new RepositoryException( "The check for the existence of content using the" + " given node type is not yet implemented, so to" + " guarantee repository consistency the request to" + " unregister the type is denied. Contributions to" + " implement this feature would be welcome! To restore" + " the broken behavior of previous Jackrabbit versions" + " where this check was simply skipped, please set the" + " disableCheckForReferencesInContentException system" + " property to true."); } } //-------------------------------------------------------< implementation > /** * @return the definition of the root node */ public QNodeDefinition getRootNodeDef() { return rootNodeDef; } /** * Set an event channel to inform about changes. * * @param eventChannel event channel */ public void setEventChannel(NodeTypeEventChannel eventChannel) { this.eventChannel = eventChannel; eventChannel.setListener(this); } /** * @param ntName node type name * @param entCache cache of already-built effective node types * @param ntdCache cache of node type definitions * @return the effective node type * @throws NoSuchNodeTypeException if a node type reference (e.g. a supertype) * could not be resolved. */ static EffectiveNodeType getEffectiveNodeType(Name ntName, EffectiveNodeTypeCache entCache, Map<Name, QNodeTypeDefinition> ntdCache) throws NoSuchNodeTypeException { // 1. check if effective node type has already been built EffectiveNodeTypeCache.Key key = entCache.getKey(new Name[]{ntName}); EffectiveNodeType ent = entCache.get(key); if (ent != null) { return ent; } // 2. make sure we've got the definition of the specified node type QNodeTypeDefinition ntd = ntdCache.get(ntName); if (ntd == null) { throw new NoSuchNodeTypeException(ntName.toString()); } // 3. build effective node type synchronized (entCache) { try { ent = EffectiveNodeType.create(ntd, entCache, ntdCache); // store new effective node type entCache.put(ent); return ent; } catch (NodeTypeConflictException ntce) { // should never get here as all known node types should be valid! String msg = "internal error: encountered invalid registered node type " + ntName; log.debug(msg); throw new NoSuchNodeTypeException(msg, ntce); } } } /** * Returns an effective node type representation of the given node types. * * @param ntNames array of node type names * @param entCache cache of already-built effective node types * @param ntdCache cache of node type definitions * @return the desired effective node type * @throws NodeTypeConflictException if the effective node type representation * could not be built due to conflicting * node type definitions. * @throws NoSuchNodeTypeException if a node type reference (e.g. a supertype) * could not be resolved. */ static EffectiveNodeType getEffectiveNodeType(Name[] ntNames, EffectiveNodeTypeCache entCache, Map<Name, QNodeTypeDefinition> ntdCache) throws NodeTypeConflictException, NoSuchNodeTypeException { EffectiveNodeTypeCache.Key key = entCache.getKey(ntNames); // 1. check if aggregate has already been built if (entCache.contains(key)) { return entCache.get(key); } // 2. make sure we've got the definitions of the specified node types for (Name ntName : ntNames) { if (!ntdCache.containsKey(ntName)) { throw new NoSuchNodeTypeException(ntName.toString()); } } // 3. build aggregate EffectiveNodeTypeCache.Key requested = key; EffectiveNodeType result = null; synchronized (entCache) { // build list of 'best' existing sub-aggregates while (key.getNames().length > 0) { // find the (sub) key that matches the current key the best EffectiveNodeTypeCache.Key subKey = entCache.findBest(key); if (subKey != null) { EffectiveNodeType ent = entCache.get(subKey); if (result == null) { result = ent; } else { result = result.merge(ent); // store intermediate result entCache.put(result); } // subtract the result from the temporary key key = key.subtract(subKey); } else { /** * no matching sub-aggregates found: * build aggregate of remaining node types through iteration */ Name[] remainder = key.getNames(); for (Name aRemainder : remainder) { QNodeTypeDefinition ntd = ntdCache.get(aRemainder); EffectiveNodeType ent = EffectiveNodeType.create(ntd, entCache, ntdCache); // store new effective node type entCache.put(ent); if (result == null) { result = ent; } else { result = result.merge(ent); // store intermediate result (sub-aggregate) entCache.put(result); } } break; } } } // also put the requested key, since the merge could have removed some // the redundant nodetypes if (!entCache.contains(requested)) { entCache.put(requested, result); } // we're done return result; } static void checkForCircularInheritance(Name[] supertypes, Stack<Name> inheritanceChain, Map<Name, QNodeTypeDefinition> ntDefCache) throws InvalidNodeTypeDefException, RepositoryException { for (Name nt : supertypes) { int pos = inheritanceChain.lastIndexOf(nt); if (pos >= 0) { StringBuilder buf = new StringBuilder(); for (int j = 0; j < inheritanceChain.size(); j++) { if (j == pos) { buf.append("--> "); } buf.append(inheritanceChain.get(j)); buf.append(" extends "); } buf.append("--> "); buf.append(nt); throw new InvalidNodeTypeDefException("circular inheritance detected: " + buf.toString()); } try { QNodeTypeDefinition ntd = ntDefCache.get(nt); Name[] sta = ntd.getSupertypes(); if (sta.length > 0) { // check recursively inheritanceChain.push(nt); checkForCircularInheritance(sta, inheritanceChain, ntDefCache); inheritanceChain.pop(); } } catch (NoSuchNodeTypeException nsnte) { String msg = "unknown supertype: " + nt; log.debug(msg); throw new InvalidNodeTypeDefException(msg, nsnte); } } } static void checkForCircularNodeAutoCreation(EffectiveNodeType childNodeENT, Stack<Name> definingParentNTs, EffectiveNodeTypeCache anEntCache, Map<Name, QNodeTypeDefinition> ntDefCache) throws InvalidNodeTypeDefException { // check for circularity through default node types of auto-created child nodes // (node type 'a' defines auto-created child node with default node type 'a') Name[] childNodeNTs = childNodeENT.getAllNodeTypes(); for (Name nt : childNodeNTs) { int pos = definingParentNTs.lastIndexOf(nt); if (pos >= 0) { StringBuilder buf = new StringBuilder(); for (int j = 0; j < definingParentNTs.size(); j++) { if (j == pos) { buf.append("--> "); } buf.append("node type "); buf.append(definingParentNTs.get(j)); buf.append(" defines auto-created child node with default "); } buf.append("--> "); buf.append("node type "); buf.append(nt); throw new InvalidNodeTypeDefException("circular node auto-creation detected: " + buf.toString()); } } QNodeDefinition[] nodeDefs = childNodeENT.getAutoCreateNodeDefs(); for (QNodeDefinition nodeDef : nodeDefs) { Name dnt = nodeDef.getDefaultPrimaryType(); Name definingNT = nodeDef.getDeclaringNodeType(); try { if (dnt != null) { // check recursively definingParentNTs.push(definingNT); checkForCircularNodeAutoCreation(getEffectiveNodeType(dnt, anEntCache, ntDefCache), definingParentNTs, anEntCache, ntDefCache); definingParentNTs.pop(); } } catch (NoSuchNodeTypeException nsnte) { String msg = definingNT + " defines invalid default node type for child node " + nodeDef.getName(); log.debug(msg); throw new InvalidNodeTypeDefException(msg, nsnte); } } } private EffectiveNodeType internalRegister(QNodeTypeDefinition ntd) throws InvalidNodeTypeDefException, RepositoryException { Name name = ntd.getName(); if (name != null && registeredNTDefs.containsKey(name)) { String msg = name + " already exists"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } ntd = checkNtBaseSubtyping(ntd, registeredNTDefs); EffectiveNodeType ent = validateNodeTypeDef(ntd, entCache, registeredNTDefs, nsReg, false); // store new effective node type instance entCache.put(ent); registeredNTDefs.put(name, ntd); return ent; } /** * Validates and registers the specified collection of <code>NodeTypeDef</code> * objects. An <code>InvalidNodeTypeDefException</code> is thrown if the * validation of any of the contained <code>NodeTypeDef</code> objects fails. * <p> * Note that in the case an exception is thrown no node type will be * eventually registered. * * @param ntDefs collection of <code>NodeTypeDef</code> objects * @throws InvalidNodeTypeDefException if the node type is not valid * @throws RepositoryException if an error occurs * @see #registerNodeType */ private void internalRegister(Collection<QNodeTypeDefinition> ntDefs, boolean external) throws InvalidNodeTypeDefException, RepositoryException { internalRegister(ntDefs, external, false); } /** * Same as {@link #internalRegister(java.util.Collection, boolean)} except for the * additional <code>lenient</code> parameter which governs whether * validation can be lenient (e.g. for built-in node types) or has to be * strict (such as in the case of custom node types). This differentiation * is unfortunately required as there are e.g. properties defined in built-in * node types which are auto-created but don't have a fixed default value * that can be exposed in a property definition because it is * system-generated (such as jcr:primaryType in nt:base). */ private void internalRegister(Collection<QNodeTypeDefinition> ntDefs, boolean external, boolean lenient) throws InvalidNodeTypeDefException, RepositoryException { // need a list/collection that can be modified List<QNodeTypeDefinition> defs = new ArrayList<QNodeTypeDefinition>(ntDefs); // map of node type names and node type definitions Map<Name, QNodeTypeDefinition> tmpNTDefCache = new HashMap<Name, QNodeTypeDefinition>(registeredNTDefs); // temporarily register the node type definition // and do some preliminary checks for (QNodeTypeDefinition ntd : defs) { Name name = ntd.getName(); if (!external && name != null && tmpNTDefCache.containsKey(name)) { String msg = name + " already exists locally"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } // add definition to temporary cache tmpNTDefCache.put(ntd.getName(), ntd); } // check if all node type defs have proper nt:base subtyping for (int i = 0; i < defs.size(); i++) { QNodeTypeDefinition ntd = defs.get(i); QNodeTypeDefinition mod = checkNtBaseSubtyping(ntd, tmpNTDefCache); if (mod != ntd) { // check fixed subtyping // -> update cache and list of defs tmpNTDefCache.put(mod.getName(), mod); defs.set(i, mod); } } // create working copies of current ent & ntd caches: // cache of pre-built aggregations of node types EffectiveNodeTypeCache tmpENTCache = (EffectiveNodeTypeCache) entCache.clone(); for (QNodeTypeDefinition ntd : defs) { EffectiveNodeType ent = validateNodeTypeDef(ntd, tmpENTCache, tmpNTDefCache, nsReg, lenient); // store new effective node type instance tmpENTCache.put(ent); } // since no exception was thrown so far the definitions are assumed to // be valid for (QNodeTypeDefinition ntd : defs) { registeredNTDefs.put(ntd.getName(), ntd); } // finally add newly created effective node types to entCache entCache = tmpENTCache; } private void internalUnregister(Name name) throws NoSuchNodeTypeException { QNodeTypeDefinition ntd = registeredNTDefs.get(name); if (ntd == null) { throw new NoSuchNodeTypeException(name.toString()); } registeredNTDefs.remove(name); entCache.invalidate(name); } private void internalUnregister(Collection<Name> ntNames) throws NoSuchNodeTypeException { for (Name name : ntNames) { internalUnregister(name); } } /** * Utility method for verifying that the namespace of a <code>Name</code> * is registered; a <code>null</code> argument is silently ignored. * * @param name name whose namespace is to be checked * @param nsReg namespace registry to be used for checking * @throws RepositoryException if the namespace of the given name is not * registered or if an unspecified error occured */ private static void checkNamespace(Name name, NamespaceRegistry nsReg) throws RepositoryException { if (name != null) { // make sure namespace uri denotes a registered namespace nsReg.getPrefix(name.getNamespaceURI()); } } /** * Checks if the given node type def has the correct supertypes in respect * to nt:base. all mixin nodetypes must not have a nt:base, the primary * ones only if they don't inherit it from another supertype. * * @param ntd the node type def to check * @param ntdCache cache for lookup * @return the node type definition that was given to check or a new * instance if it had to be fixed up. */ private static QNodeTypeDefinition checkNtBaseSubtyping(QNodeTypeDefinition ntd, Map<Name, QNodeTypeDefinition> ntdCache) { if (NameConstants.NT_BASE.equals(ntd.getName())) { return ntd; } Set<Name> supertypes = new TreeSet<Name>(Arrays.asList(ntd.getSupertypes())); if (supertypes.isEmpty()) { return ntd; } boolean modified; if (ntd.isMixin()) { // if mixin, remove possible nt:base supertype modified = supertypes.remove(NameConstants.NT_BASE); } else { // check if all supertypes (except nt:base) are mixins boolean allMixins = true; for (Name name: supertypes) { if (!name.equals(NameConstants.NT_BASE)) { QNodeTypeDefinition def = ntdCache.get(name); if (def != null && !def.isMixin()) { allMixins = false; break; } } } if (allMixins) { // ntd is a primary node type and has only mixins as supertypes, // so it needs a nt:base modified = supertypes.add(NameConstants.NT_BASE); } else { // ntd is a primary node type and at least one of the supertypes // is too, so ensure that no nt:base is added. note that the // trivial case, where there would be no supertype left is handled // in the QNodeTypeDefinition directly modified = supertypes.remove(NameConstants.NT_BASE); } } if (modified) { ntd = new QNodeTypeDefinitionImpl(ntd.getName(), supertypes.toArray(new Name[supertypes.size()]), ntd.getSupportedMixinTypes(), ntd.isMixin(), ntd.isAbstract(), ntd.isQueryable(), ntd.hasOrderableChildNodes(), ntd.getPrimaryItemName(), ntd.getPropertyDefs(), ntd.getChildNodeDefs()); } return ntd; } /** * Validates the specified <code>NodeTypeDef</code> within the context of * the two other given collections and returns an <code>EffectiveNodeType</code>. * * @param ntd node type definition * @param entCache effective node type cache * @param ntdCache cache of 'known' node type definitions, used to resolve dependencies * @param nsReg namespace registry used for validatingatch names * @param lenient flag governing whether validation can be lenient or has to be strict * @return an effective node type representation of the specified <code>QNodeTypeDefinition</code> * @throws InvalidNodeTypeDefException if the node type is not valid * @throws RepositoryException if another error occurs */ private static EffectiveNodeType validateNodeTypeDef(QNodeTypeDefinition ntd, EffectiveNodeTypeCache entCache, Map<Name, QNodeTypeDefinition> ntdCache, NamespaceRegistry nsReg, boolean lenient) throws InvalidNodeTypeDefException, RepositoryException { /** * the effective (i.e. merged and resolved) node type resulting from * the specified node type definition; * the effective node type will finally be created after the definition * has been verified and checked for conflicts etc.; in some cases it * will be created already at an earlier stage during the validation * of child node definitions */ EffectiveNodeType ent = null; Name name = ntd.getName(); if (name == null) { String msg = "no name specified"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } checkNamespace(name, nsReg); // validate supertypes Name[] supertypes = ntd.getSupertypes(); if (supertypes.length > 0) { for (Name supertype : supertypes) { checkNamespace(supertype, nsReg); /** * simple check for infinite recursion * (won't trap recursion on a deeper inheritance level) */ if (name.equals(supertype)) { String msg = "[" + name + "] invalid supertype: " + supertype + " (infinite recursion))"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } if (!ntdCache.containsKey(supertype)) { String msg = "[" + name + "] invalid supertype: " + supertype; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } } /** * check for circularity in inheritance chain * ('a' extends 'b' extends 'a') */ Stack<Name> inheritanceChain = new Stack<Name>(); inheritanceChain.push(name); checkForCircularInheritance(supertypes, inheritanceChain, ntdCache); } /** * note that infinite recursion through inheritance is automatically * being checked by the following call to getEffectiveNodeType(...) * as it's impossible to register a node type definition which * references a supertype that isn't registered yet... */ /** * build effective (i.e. merged and resolved) node type from supertypes * and check for conflicts */ if (supertypes.length > 0) { try { EffectiveNodeType est = getEffectiveNodeType(supertypes, entCache, ntdCache); // check whether specified node type definition overrides // a supertypes's primaryItem -> illegal (JCR-1947) if (ntd.getPrimaryItemName() != null && est.getPrimaryItemName() != null) { String msg = "[" + name + "] primaryItemName is already specified by a supertype and must therefore not be overridden."; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } } catch (NodeTypeConflictException ntce) { String msg = "[" + name + "] failed to validate supertypes"; log.debug(msg); throw new InvalidNodeTypeDefException(msg, ntce); } catch (NoSuchNodeTypeException nsnte) { String msg = "[" + name + "] failed to validate supertypes"; log.debug(msg); throw new InvalidNodeTypeDefException(msg, nsnte); } } checkNamespace(ntd.getPrimaryItemName(), nsReg); // validate property definitions QPropertyDefinition[] pda = ntd.getPropertyDefs(); for (QPropertyDefinition pd : pda) { /** * sanity check: * make sure declaring node type matches name of node type definition */ if (!name.equals(pd.getDeclaringNodeType())) { String msg = "[" + name + "#" + pd.getName() + "] invalid declaring node type specified"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } checkNamespace(pd.getName(), nsReg); // check that auto-created properties specify a name if (pd.definesResidual() && pd.isAutoCreated()) { String msg = "[" + name + "#" + pd.getName() + "] auto-created properties must specify a name"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } // check that auto-created properties specify a type if (pd.getRequiredType() == PropertyType.UNDEFINED && pd.isAutoCreated()) { String msg = "[" + name + "#" + pd.getName() + "] auto-created properties must specify a type"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } /** * check default values: * make sure type of value is consistent with required property type */ QValue[] defVals = pd.getDefaultValues(); if (defVals != null && defVals.length != 0) { int reqType = pd.getRequiredType(); for (QValue defVal : defVals) { if (reqType == PropertyType.UNDEFINED) { reqType = defVal.getType(); } else { if (defVal.getType() != reqType) { String msg = "[" + name + "#" + pd.getName() + "] type of default value(s) is not consistent with required property type"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } } } } else { // no default values specified if (!lenient) { // auto-created properties must have a default value if (pd.isAutoCreated()) { String msg = "[" + name + "#" + pd.getName() + "] auto-created property must have a default value"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } } } // check that default values satisfy value constraints QValueConstraint[] constraints = pd.getValueConstraints(); if (constraints != null && constraints.length > 0) { if (defVals != null && defVals.length > 0) { // check value constraints on every value for (QValue defVal : defVals) { // constraints are OR-ed together boolean satisfied = false; ConstraintViolationException cve = null; for (QValueConstraint constraint : constraints) { try { constraint.check(defVal); // at least one constraint is satisfied satisfied = true; break; } catch (ConstraintViolationException e) { cve = e; } } if (!satisfied) { // report last exception we encountered String msg = "[" + name + "#" + pd.getName() + "] default value does not satisfy value constraint"; log.debug(msg); throw new InvalidNodeTypeDefException(msg, cve); } } } /** * ReferenceConstraint: * the specified node type must be registered, with one notable * exception: the node type just being registered */ if (pd.getRequiredType() == PropertyType.REFERENCE || pd.getRequiredType() == PropertyType.WEAKREFERENCE) { for (QValueConstraint constraint : constraints) { Name ntName = NameFactoryImpl.getInstance().create(constraint.getString()); if (!name.equals(ntName) && !ntdCache.containsKey(ntName)) { String msg = "[" + name + "#" + pd.getName() + "] invalid " + (pd.getRequiredType() == PropertyType.REFERENCE ? "REFERENCE" : "WEAKREFERENCE") + " value constraint '" + ntName + "' (unknown node type)"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } } } } } // validate child-node definitions QNodeDefinition[] cnda = ntd.getChildNodeDefs(); for (QNodeDefinition cnd : cnda) { /** * sanity check: * make sure declaring node type matches name of node type definition */ if (!name.equals(cnd.getDeclaringNodeType())) { String msg = "[" + name + "#" + cnd.getName() + "] invalid declaring node type specified"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } checkNamespace(cnd.getName(), nsReg); // check that auto-created child-nodes specify a name if (cnd.definesResidual() && cnd.isAutoCreated()) { String msg = "[" + name + "#" + cnd.getName() + "] auto-created child-nodes must specify a name"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } // check that auto-created child-nodes specify a default primary type if (cnd.getDefaultPrimaryType() == null && cnd.isAutoCreated()) { String msg = "[" + name + "#" + cnd.getName() + "] auto-created child-nodes must specify a default primary type"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } // check default primary type Name dpt = cnd.getDefaultPrimaryType(); checkNamespace(dpt, nsReg); boolean referenceToSelf = false; EffectiveNodeType defaultENT = null; if (dpt != null) { // check if this node type specifies itself as default primary type if (name.equals(dpt)) { referenceToSelf = true; } /** * the default primary type must be registered, with one notable * exception: the node type just being registered */ if (!name.equals(dpt) && !ntdCache.containsKey(dpt)) { String msg = "[" + name + "#" + cnd.getName() + "] invalid default primary type '" + dpt + "'"; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } /** * build effective (i.e. merged and resolved) node type from * default primary type and check for conflicts */ try { if (!referenceToSelf) { defaultENT = getEffectiveNodeType(dpt, entCache, ntdCache); } else { /** * the default primary type is identical with the node * type just being registered; we have to instantiate it * 'manually' */ ent = EffectiveNodeType.create(ntd, entCache, ntdCache); defaultENT = ent; } if (cnd.isAutoCreated()) { /** * check for circularity through default primary types * of auto-created child nodes (node type 'a' defines * auto-created child node with default primary type 'a') */ Stack<Name> definingNTs = new Stack<Name>(); definingNTs.push(name); checkForCircularNodeAutoCreation(defaultENT, definingNTs, entCache, ntdCache); } } catch (NodeTypeConflictException ntce) { String msg = "[" + name + "#" + cnd.getName() + "] failed to validate default primary type"; log.debug(msg); throw new InvalidNodeTypeDefException(msg, ntce); } catch (NoSuchNodeTypeException nsnte) { String msg = "[" + name + "#" + cnd.getName() + "] failed to validate default primary type"; log.debug(msg); throw new InvalidNodeTypeDefException(msg, nsnte); } } // check required primary types Name[] reqTypes = cnd.getRequiredPrimaryTypes(); if (reqTypes != null && reqTypes.length > 0) { for (Name rpt : reqTypes) { // skip nt:base required types if (NameConstants.NT_BASE.equals(rpt)) { continue; } checkNamespace(rpt, nsReg); referenceToSelf = false; /** * check if this node type specifies itself as required * primary type */ if (name.equals(rpt)) { referenceToSelf = true; } /** * the required primary type must be registered, with one * notable exception: the node type just being registered */ if (!name.equals(rpt) && !ntdCache.containsKey(rpt)) { String msg = "[" + name + "#" + cnd.getName() + "] invalid required primary type: " + rpt; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } /** * check if default primary type satisfies the required * primary type constraint */ if (defaultENT != null && !defaultENT.includesNodeType(rpt)) { String msg = "[" + name + "#" + cnd.getName() + "] default primary type does not satisfy required primary type constraint " + rpt; log.debug(msg); throw new InvalidNodeTypeDefException(msg); } /** * build effective (i.e. merged and resolved) node type from * required primary type constraint and check for conflicts */ try { if (!referenceToSelf) { getEffectiveNodeType(rpt, entCache, ntdCache); } else { /** * the required primary type is identical with the * node type just being registered; we have to * instantiate it 'manually' */ if (ent == null) { ent = EffectiveNodeType.create(ntd, entCache, ntdCache); } } } catch (NodeTypeConflictException ntce) { String msg = "[" + name + "#" + cnd.getName() + "] failed to validate required primary type constraint"; log.debug(msg); throw new InvalidNodeTypeDefException(msg, ntce); } catch (NoSuchNodeTypeException nsnte) { String msg = "[" + name + "#" + cnd.getName() + "] failed to validate required primary type constraint"; log.debug(msg); throw new InvalidNodeTypeDefException(msg, nsnte); } } } } /** * now build effective (i.e. merged and resolved) node type from * this node type definition; this will potentially detect more * conflicts or problems */ if (ent == null) { try { ent = EffectiveNodeType.create(ntd, entCache, ntdCache); } catch (NodeTypeConflictException ntce) { String msg = "[" + name + "] failed to resolve node type definition"; log.debug(msg); throw new InvalidNodeTypeDefException(msg, ntce); } catch (NoSuchNodeTypeException nsnte) { String msg = "[" + name + "] failed to resolve node type definition"; log.debug(msg); throw new InvalidNodeTypeDefException(msg, nsnte); } } return ent; } private static QNodeDefinition createRootNodeDef() { QNodeDefinitionBuilder def = new QNodeDefinitionBuilder(); // FIXME need a fake declaring node type: // rep:root is not quite correct but better than a non-existing node type def.setDeclaringNodeType(NameConstants.REP_ROOT); def.setRequiredPrimaryTypes(new Name[]{NameConstants.REP_ROOT}); def.setDefaultPrimaryType(NameConstants.REP_ROOT); def.setMandatory(true); def.setProtected(false); def.setOnParentVersion(OnParentVersionAction.VERSION); def.setAllowsSameNameSiblings(false); def.setAutoCreated(true); return def.build(); } /** * Notify the listeners that a node type <code>ntName</code> has been registered. * @param ntName node type name */ private void notifyRegistered(Name ntName) { // copy listeners to array to avoid ConcurrentModificationException NodeTypeRegistryListener[] la = listeners.values().toArray( new NodeTypeRegistryListener[listeners.size()]); for (NodeTypeRegistryListener aLa : la) { if (aLa != null) { aLa.nodeTypeRegistered(ntName); } } } /** * Notify the listeners that a node type <code>ntName</code> has been re-registered. * @param ntName node type name */ private void notifyReRegistered(Name ntName) { // copy listeners to array to avoid ConcurrentModificationException NodeTypeRegistryListener[] la = listeners.values().toArray( new NodeTypeRegistryListener[listeners.size()]); for (NodeTypeRegistryListener aLa : la) { if (aLa != null) { aLa.nodeTypeReRegistered(ntName); } } } /** * Notify the listeners that oone or more node types have been unregistered. * @param names node type names */ private void notifyUnregistered(Collection<Name> names) { // copy listeners to array to avoid ConcurrentModificationException NodeTypeRegistryListener[] la = listeners.values().toArray( new NodeTypeRegistryListener[listeners.size()]); for (NodeTypeRegistryListener aLa : la) { if (aLa != null) { aLa.nodeTypesUnregistered(names); } } } }