/*
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.jcr.NamespaceException;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.nodetype.InvalidNodeTypeDefinitionException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeDefinition;
import javax.jcr.nodetype.NodeTypeExistsException;
import javax.jcr.nodetype.NodeTypeIterator;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.jackrabbit.api.JackrabbitNodeTypeManager;
import org.apache.jackrabbit.commons.NamespaceHelper;
import org.apache.jackrabbit.commons.cnd.CompactNodeTypeDefReader;
import org.apache.jackrabbit.commons.cnd.ParseException;
import org.apache.jackrabbit.commons.iterator.NodeTypeIteratorAdapter;
import org.apache.jackrabbit.core.nodetype.xml.NodeTypeReader;
import org.apache.jackrabbit.core.session.SessionContext;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.QNodeDefinition;
import org.apache.jackrabbit.spi.QNodeTypeDefinition;
import org.apache.jackrabbit.spi.QPropertyDefinition;
import org.apache.jackrabbit.spi.commons.QNodeTypeDefinitionImpl;
import org.apache.jackrabbit.spi.commons.conversion.NameException;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
import org.apache.jackrabbit.spi.commons.namespace.NamespaceMapping;
import org.apache.jackrabbit.spi.commons.nodetype.AbstractNodeTypeManager;
import org.apache.jackrabbit.spi.commons.nodetype.NodeDefinitionImpl;
import org.apache.jackrabbit.spi.commons.nodetype.PropertyDefinitionImpl;
import org.apache.jackrabbit.spi.commons.nodetype.QDefinitionBuilderFactory;
import org.apache.jackrabbit.spi.commons.value.QValueFactoryImpl;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* A <code>NodeTypeManagerImpl</code> implements a session dependant
* NodeTypeManager.
*/
public class NodeTypeManagerImpl extends AbstractNodeTypeManager
implements JackrabbitNodeTypeManager, NodeTypeRegistryListener {
/**
* Component context of the current session.
*/
private final SessionContext context;
/**
* The root node definition.
*/
private final NodeDefinitionImpl rootNodeDef;
/**
* A cache for <code>NodeType</code> instances created by this
* <code>NodeTypeManager</code>
*/
private final Map<Name, NodeTypeImpl> ntCache;
/**
* A cache for <code>PropertyDefinition</code> instances created by this
* <code>NodeTypeManager</code>
*/
private final Map<QPropertyDefinition, PropertyDefinitionImpl> pdCache;
/**
* A cache for <code>NodeDefinition</code> instances created by this
* <code>NodeTypeManager</code>
*/
private final Map<QNodeDefinition, NodeDefinitionImpl> ndCache;
/**
* Creates a new <code>NodeTypeManagerImpl</code> instance.
*
* @param ntReg node type registry
* @param session current session
* @param store the data store
*/
@SuppressWarnings("unchecked")
public NodeTypeManagerImpl(SessionContext context) {
this.context = context;
// setup caches with soft references to node type
// & item definition instances
ntCache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.SOFT);
pdCache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.SOFT);
ndCache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.SOFT);
NodeTypeRegistry registry = context.getNodeTypeRegistry();
rootNodeDef = new NodeDefinitionImpl(
registry.getRootNodeDef(), this, context);
ndCache.put(rootNodeDef.unwrap(), rootNodeDef);
registry.addListener(this);
}
/**
* @return the root node definition
*/
public NodeDefinitionImpl getRootNodeDefinition() {
return rootNodeDef;
}
/**
* @param def the QNodeDefinition
* @return the node definition
*/
public NodeDefinitionImpl getNodeDefinition(QNodeDefinition def) {
synchronized (ndCache) {
NodeDefinitionImpl ndi = ndCache.get(def);
if (ndi == null) {
ndi = new NodeDefinitionImpl(def, this, context);
ndCache.put(def, ndi);
}
return ndi;
}
}
/**
* @param def prop def
* @return the property definition
*/
public PropertyDefinitionImpl getPropertyDefinition(QPropertyDefinition def) {
synchronized (pdCache) {
PropertyDefinitionImpl pdi = pdCache.get(def);
if (pdi == null) {
pdi = new PropertyDefinitionImpl(
def, this, context, context.getValueFactory());
pdCache.put(def, pdi);
}
return pdi;
}
}
/**
* @param name node type name
* @return node type
* @throws NoSuchNodeTypeException if the nodetype does not exit
*/
public NodeTypeImpl getNodeType(Name name) throws NoSuchNodeTypeException {
synchronized (ntCache) {
NodeTypeImpl nt = ntCache.get(name);
if (nt == null) {
NodeTypeRegistry registry = context.getNodeTypeRegistry();
EffectiveNodeType ent = registry.getEffectiveNodeType(name);
QNodeTypeDefinition def = registry.getNodeTypeDef(name);
nt = new NodeTypeImpl(
ent, def, this, context,
context.getValueFactory(), context.getDataStore());
ntCache.put(name, nt);
}
return nt;
}
}
/**
* @see org.apache.jackrabbit.spi.commons.nodetype.AbstractNodeTypeManager#getNamePathResolver()
*/
public NamePathResolver getNamePathResolver() {
return context;
}
/**
* @return the node type registry
*/
public NodeTypeRegistry getNodeTypeRegistry() {
return context.getNodeTypeRegistry();
}
/**
* Registers the node types defined in the given input stream depending
* on the content type specified for the stream. This will also register
* any namespaces identified in the input stream if they have not already
* been registered.
*
* @param in node type XML stream
* @param contentType type of the input stream
* @param reregisterExisting flag indicating whether node types should be
* reregistered if they already exist
* @return registered node types
* @throws IOException if the input stream could not be read or parsed
* @throws RepositoryException if the node types are invalid or another
* repository error occurs
*/
public NodeType[] registerNodeTypes(InputStream in, String contentType,
boolean reregisterExisting)
throws IOException, RepositoryException {
try {
Map<String, String> namespaceMap = new HashMap<String, String>();
List<QNodeTypeDefinition> nodeTypeDefs = new ArrayList<QNodeTypeDefinition>();
if (contentType.equalsIgnoreCase(TEXT_XML)
|| contentType.equalsIgnoreCase(APPLICATION_XML)) {
try {
NodeTypeReader ntr = new NodeTypeReader(in);
Properties namespaces = ntr.getNamespaces();
if (namespaces != null) {
Enumeration<?> prefixes = namespaces.propertyNames();
while (prefixes.hasMoreElements()) {
String prefix = (String) prefixes.nextElement();
String uri = namespaces.getProperty(prefix);
namespaceMap.put(prefix, uri);
}
}
QNodeTypeDefinition[] defs = ntr.getNodeTypeDefs();
nodeTypeDefs.addAll(Arrays.asList(defs));
} catch (NameException e) {
throw new RepositoryException("Illegal JCR name", e);
}
} else if (contentType.equalsIgnoreCase(TEXT_X_JCR_CND)) {
try {
NamespaceMapping mapping = new NamespaceMapping(context.getSessionImpl());
CompactNodeTypeDefReader<QNodeTypeDefinition, NamespaceMapping> reader =
new CompactNodeTypeDefReader<QNodeTypeDefinition, NamespaceMapping>(
new InputStreamReader(in), "cnd input stream", mapping,
new QDefinitionBuilderFactory());
namespaceMap.putAll(mapping.getPrefixToURIMapping());
for (QNodeTypeDefinition ntDef: reader.getNodeTypeDefinitions()) {
nodeTypeDefs.add(ntDef);
}
} catch (ParseException e) {
IOException e2 = new IOException(e.getMessage());
e2.initCause(e);
throw e2;
}
} else {
throw new UnsupportedRepositoryOperationException(
"Unsupported content type: " + contentType);
}
new NamespaceHelper(context.getSessionImpl()).registerNamespaces(namespaceMap);
if (reregisterExisting) {
NodeTypeRegistry registry = context.getNodeTypeRegistry();
// split the node types into new and already registered node types.
// this way we can register new node types together with already
// registered node types which make circular dependencies possible
List<QNodeTypeDefinition> newNodeTypeDefs = new ArrayList<QNodeTypeDefinition>();
List<QNodeTypeDefinition> registeredNodeTypeDefs = new ArrayList<QNodeTypeDefinition>();
for (QNodeTypeDefinition nodeTypeDef: nodeTypeDefs) {
if (registry.isRegistered(nodeTypeDef.getName())) {
registeredNodeTypeDefs.add(nodeTypeDef);
} else {
newNodeTypeDefs.add(nodeTypeDef);
}
}
ArrayList<NodeType> nodeTypes = new ArrayList<NodeType>();
// register new node types
nodeTypes.addAll(registerNodeTypes(newNodeTypeDefs));
// re-register already existing node types
for (QNodeTypeDefinition nodeTypeDef: registeredNodeTypeDefs) {
registry.reregisterNodeType(nodeTypeDef);
nodeTypes.add(getNodeType(nodeTypeDef.getName()));
}
return nodeTypes.toArray(new NodeType[nodeTypes.size()]);
} else {
Collection<NodeType> types = registerNodeTypes(nodeTypeDefs);
return types.toArray(new NodeType[types.size()]);
}
} catch (InvalidNodeTypeDefException e) {
throw new RepositoryException("Invalid node type definition", e);
}
}
//---------------------------------------------< NodeTypeRegistryListener >
/**
* {@inheritDoc}
*/
public void nodeTypeRegistered(Name ntName) {
// not interested, ignore
}
/**
* {@inheritDoc}
*/
public void nodeTypeReRegistered(Name ntName) {
// flush all affected cache entries
ntCache.remove(ntName);
synchronized (pdCache) {
Iterator<PropertyDefinitionImpl> iter = pdCache.values().iterator();
while (iter.hasNext()) {
PropertyDefinitionImpl pd = iter.next();
if (ntName.equals(pd.unwrap().getDeclaringNodeType())) {
iter.remove();
}
}
}
synchronized (ndCache) {
Iterator<NodeDefinitionImpl> iter = ndCache.values().iterator();
while (iter.hasNext()) {
NodeDefinitionImpl nd = iter.next();
if (ntName.equals(nd.unwrap().getDeclaringNodeType())) {
iter.remove();
}
}
}
}
/**
* {@inheritDoc}
*/
public void nodeTypesUnregistered(Collection<Name> names) {
// flush all affected cache entries
for (Name name : names) {
ntCache.remove(name);
}
synchronized (pdCache) {
Iterator<PropertyDefinitionImpl> iter = pdCache.values().iterator();
while (iter.hasNext()) {
PropertyDefinitionImpl pd = iter.next();
if (names.contains(pd.unwrap().getDeclaringNodeType())) {
iter.remove();
}
}
}
synchronized (ndCache) {
Iterator<NodeDefinitionImpl> iter = ndCache.values().iterator();
while (iter.hasNext()) {
NodeDefinitionImpl nd = iter.next();
if (names.contains(nd.unwrap().getDeclaringNodeType())) {
iter.remove();
}
}
}
}
//------------------------------------------------------< NodeTypeManager >
/**
* {@inheritDoc}
*/
public NodeTypeIterator getAllNodeTypes() throws RepositoryException {
Name[] ntNames = context.getNodeTypeRegistry().getRegisteredNodeTypes();
Arrays.sort(ntNames);
ArrayList<NodeType> list = new ArrayList<NodeType>(ntNames.length);
for (Name ntName : ntNames) {
list.add(getNodeType(ntName));
}
return new NodeTypeIteratorAdapter(list);
}
/**
* {@inheritDoc}
*/
public NodeTypeIterator getPrimaryNodeTypes() throws RepositoryException {
Name[] ntNames = context.getNodeTypeRegistry().getRegisteredNodeTypes();
Arrays.sort(ntNames);
ArrayList<NodeType> list = new ArrayList<NodeType>(ntNames.length);
for (Name ntName : ntNames) {
NodeType nt = getNodeType(ntName);
if (!nt.isMixin()) {
list.add(nt);
}
}
return new NodeTypeIteratorAdapter(list);
}
/**
* {@inheritDoc}
*/
public NodeTypeIterator getMixinNodeTypes() throws RepositoryException {
Name[] ntNames = context.getNodeTypeRegistry().getRegisteredNodeTypes();
Arrays.sort(ntNames);
ArrayList<NodeType> list = new ArrayList<NodeType>(ntNames.length);
for (Name ntName : ntNames) {
NodeType nt = getNodeType(ntName);
if (nt.isMixin()) {
list.add(nt);
}
}
return new NodeTypeIteratorAdapter(list);
}
/**
* {@inheritDoc}
*/
public NodeType getNodeType(String nodeTypeName)
throws NoSuchNodeTypeException {
try {
return getNodeType(context.getQName(nodeTypeName));
} catch (NameException e) {
throw new NoSuchNodeTypeException(nodeTypeName, e);
} catch (NamespaceException e) {
throw new NoSuchNodeTypeException(nodeTypeName, e);
}
}
//--------------------------------------------< JackrabbitNodeTypeManager >
/**
* Internal helper method for registering a list of node type definitions.
* Returns a collection containing the registered node types.
*
* @param defs a collection of <code>QNodeTypeDefinition<code> objects
* @return registered node types
* @throws InvalidNodeTypeDefException if a nodetype is invalid
* @throws RepositoryException if an error occurs
*/
private Collection<NodeType> registerNodeTypes(List<QNodeTypeDefinition> defs)
throws InvalidNodeTypeDefException, RepositoryException {
context.getNodeTypeRegistry().registerNodeTypes(defs);
Set<NodeType> types = new HashSet<NodeType>();
for (QNodeTypeDefinition def : defs) {
try {
types.add(getNodeType(def.getName()));
} catch (NoSuchNodeTypeException e) {
// ignore
}
}
return types;
}
/**
* Registers the node types defined in the given XML stream. This
* is a trivial implementation that just invokes the existing
* {@link NodeTypeReader} and {@link NodeTypeRegistry} methods and
* heuristically creates the returned node type array. It will also
* register any namespaces defined in the input source that have not
* already been registered.
*
* {@inheritDoc}
*/
public NodeType[] registerNodeTypes(InputSource in)
throws SAXException, RepositoryException {
try {
return registerNodeTypes(in.getByteStream(), TEXT_XML);
} catch (IOException e) {
throw new SAXException("Error reading node type stream", e);
}
}
private static final String APPLICATION_XML = "application/xml";
/**
* Registers the node types defined in the given input stream depending
* on the content type specified for the stream. This will also register
* any namespaces identified in the input stream if they have not already
* been registered.
*
* {@inheritDoc}
*/
public NodeType[] registerNodeTypes(InputStream in, String contentType)
throws IOException, RepositoryException {
return registerNodeTypes(in, contentType, false);
}
/**
* Checks whether a node type with the given name exists.
*
* @param name node type name
* @return <code>true</code> if the named node type exists,
* <code>false</code> otherwise
* @throws RepositoryException if the name format is invalid
*/
public boolean hasNodeType(String name) throws RepositoryException {
try {
Name qname = context.getQName(name);
return getNodeTypeRegistry().isRegistered(qname);
} catch (NamespaceException e) {
return false;
} catch (NameException e) {
throw new RepositoryException("Invalid name: " + name, e);
}
}
//--------------------------------------------------< new JSR 283 methods >
/**
* Registers or updates the specified <code>Collection</code> of
* <code>NodeTypeDefinition</code> objects. This method is used to register
* or update a set of node types with mutual dependencies. Returns an
* iterator over the resulting <code>NodeType</code> objects.
* <p/>
* The effect of the method is "all or nothing"; if an error occurs, no node
* types are registered or updated.
* <p/>
* Throws an <code>InvalidNodeTypeDefinitionException</code> if a
* <code>NodeTypeDefinition</code> within the <code>Collection</code> is
* invalid or if the <code>Collection</code> contains an object of a type
* other than <code>NodeTypeDefinition</code>.
* <p/>
* Throws a <code>NodeTypeExistsException</code> if <code>allowUpdate</code>
* is <code>false</code> and a <code>NodeTypeDefinition</code> within the
* <code>Collection</code> specifies a node type name that is already
* registered.
* <p/>
* Throws an <code>UnsupportedRepositoryOperationException</code> if this
* implementation does not support node type registration.
*
* @param definitions a collection of <code>NodeTypeDefinition</code>s
* @param allowUpdate a boolean
* @return the registered node types.
* @throws InvalidNodeTypeDefinitionException if a
* <code>NodeTypeDefinition</code> within the <code>Collection</code> is
* invalid or if the <code>Collection</code> contains an object of a type
* other than <code>NodeTypeDefinition</code>.
* @throws NodeTypeExistsException if <code>allowUpdate</code> is
* <code>false</code> and a <code>NodeTypeDefinition</code> within the
* <code>Collection</code> specifies a node type name that is already
* registered.
* @throws UnsupportedRepositoryOperationException if this implementation
* does not support node type registration.
* @throws RepositoryException if another error occurs.
* @since JCR 2.0
*/
public NodeTypeIterator registerNodeTypes(
NodeTypeDefinition[] definitions, boolean allowUpdate)
throws InvalidNodeTypeDefinitionException, NodeTypeExistsException,
UnsupportedRepositoryOperationException, RepositoryException {
NodeTypeRegistry registry = context.getNodeTypeRegistry();
// split the node types into new and already registered node types.
// this way we can register new node types together with already
// registered node types which make circular dependencies possible
List<QNodeTypeDefinition> addedDefs = new ArrayList<QNodeTypeDefinition>();
List<QNodeTypeDefinition> modifiedDefs = new ArrayList<QNodeTypeDefinition>();
for (NodeTypeDefinition definition : definitions) {
// convert to QNodeTypeDefinition
QNodeTypeDefinition def = toNodeTypeDef(definition);
if (registry.isRegistered(def.getName())) {
if (allowUpdate) {
modifiedDefs.add(def);
} else {
throw new NodeTypeExistsException(definition.getName());
}
} else {
addedDefs.add(def);
}
}
try {
ArrayList<NodeType> result = new ArrayList<NodeType>();
// register new node types
result.addAll(registerNodeTypes(addedDefs));
// re-register already existing node types
for (QNodeTypeDefinition nodeTypeDef: modifiedDefs) {
registry.reregisterNodeType(nodeTypeDef);
result.add(getNodeType(nodeTypeDef.getName()));
}
return new NodeTypeIteratorAdapter(result);
} catch (InvalidNodeTypeDefException e) {
throw new InvalidNodeTypeDefinitionException(e.getMessage(), e);
}
}
/**
* Unregisters the specified set of node types. Used to unregister a set of node types with mutual dependencies.
* <p/>
* Throws a <code>NoSuchNodeTypeException</code> if one of the names listed is not a registered node type.
* <p/>
* Throws an <code>UnsupportedRepositoryOperationException</code>
* if this implementation does not support node type registration.
*
* @param names a <code>String</code> array
* @throws UnsupportedRepositoryOperationException if this implementation does not support node type registration.
* @throws NoSuchNodeTypeException if one of the names listed is not a registered node type.
* @throws RepositoryException if another error occurs.
* @since JCR 2.0
*/
public void unregisterNodeTypes(String[] names)
throws UnsupportedRepositoryOperationException,
NoSuchNodeTypeException, RepositoryException {
Set<Name> ntNames = new HashSet<Name>();
for (String name : names) {
try {
ntNames.add(context.getQName(name));
} catch (NamespaceException e) {
throw new RepositoryException("Invalid name: " + name, e);
} catch (NameException e) {
throw new RepositoryException("Invalid name: " + name, e);
}
}
getNodeTypeRegistry().unregisterNodeTypes(ntNames);
}
/**
* Internal helper method for converting a <code>NodeTypeDefinition</code>
* (using prefixed JCR names) to a <code>NodeTypeDef</code> (using
* namespace-qualified names).
*
* @param definition the definition
* @return a <code>NodeTypeDef</code>
* @throws InvalidNodeTypeDefinitionException if the definition is invalid
* @throws RepositoryException if a repository error occurs
*/
private QNodeTypeDefinition toNodeTypeDef(NodeTypeDefinition definition)
throws InvalidNodeTypeDefinitionException, RepositoryException {
return new QNodeTypeDefinitionImpl(definition, context, QValueFactoryImpl.getInstance());
}
//--------------------------------------------------------------< Object >
/**
* {@inheritDoc}
*/
public String toString() {
return "NodeTypeManager(" + super.toString() + ")\n"
+ context.getNodeTypeRegistry();
}
}