Index: dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java (revision 731) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java (working copy) @@ -26,6 +26,7 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.Calendar; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; @@ -48,6 +49,7 @@ import javax.jcr.version.VersionHistory; import net.jcip.annotations.NotThreadSafe; import org.jboss.dna.common.util.CheckArg; +import org.jboss.dna.graph.ExecutionContext; import org.jboss.dna.graph.property.Name; import org.jboss.dna.graph.property.Path.Segment; @@ -225,8 +227,20 @@ * @throws UnsupportedOperationException always * @see javax.jcr.Node#getMixinNodeTypes() */ - public NodeType[] getMixinNodeTypes() { - throw new UnsupportedOperationException(); + public NodeType[] getMixinNodeTypes() throws RepositoryException { + PropertyIterator mixinProperties = getProperties(JcrLexicon.MIXIN_TYPES.getString(session.getExecutionContext().getNamespaceRegistry())); + List mixinNodeTypes = new ArrayList((int) mixinProperties.getSize()); + + while (mixinProperties.hasNext()) { + Property property = mixinProperties.nextProperty(); + + String nodeTypeName = property.getValue().getString(); + NodeType nodeType = session.getWorkspace().getNodeTypeManager().getNodeType(nodeTypeName); + + mixinNodeTypes.add(nodeType); + } + + return mixinNodeTypes.toArray(new NodeType[0]); } /** @@ -308,8 +322,15 @@ * @throws UnsupportedOperationException always * @see javax.jcr.Node#getPrimaryNodeType() */ - public NodeType getPrimaryNodeType() { - throw new UnsupportedOperationException(); + public NodeType getPrimaryNodeType() throws RepositoryException { + Property primaryNodeTypeProperty = getProperty(JcrLexicon.PRIMARY_TYPE.getString(session.getExecutionContext().getNamespaceRegistry())); + Value nodeValue = primaryNodeTypeProperty.getValue(); + + ExecutionContext context = session.getExecutionContext(); + Name nodeValueAsName = context.getValueFactories().getNameFactory().create(nodeValue.getString()); + + String nodeTypeName = nodeValueAsName.getString(context.getNamespaceRegistry()); + return session.getWorkspace().getNodeTypeManager().getNodeType(nodeTypeName); } /** @@ -324,12 +345,20 @@ /** * {@inheritDoc} * - * @throws UnsupportedOperationException always * @see javax.jcr.Node#getProperties(java.lang.String) */ - public PropertyIterator getProperties( String namePattern ) { + public PropertyIterator getProperties( String namePattern ) throws RepositoryException { // TODO: Implement after changing impl to delegate to Graph API - throw new UnsupportedOperationException(); + + // Implementing exact-matching only for now to prototype types as properties + Set matchingProps = new HashSet(); + for (Property prop : properties) { + String propName = prop.getName(); + if (propName.equals(namePattern)) + matchingProps.add(prop); + } + + return new JcrPropertyIterator(matchingProps); } /** @@ -522,7 +551,20 @@ * @return false * @see javax.jcr.Node#isNodeType(java.lang.String) */ - public boolean isNodeType( String nodeTypeName ) { + public boolean isNodeType( String nodeTypeName ) throws RepositoryException { + NodeType nodeType = getPrimaryNodeType(); + + if (nodeType.isNodeType(nodeTypeName)) { + return true; + } + + NodeType[] mixinNodeTypes = getMixinNodeTypes(); + for (int i = 0; i < mixinNodeTypes.length; i++) { + if (mixinNodeTypes[i].isNodeType(nodeTypeName)) { + return true; + } + } + return false; } Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrBuiltinNodeTypeSource.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrBuiltinNodeTypeSource.java (revision 0) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrBuiltinNodeTypeSource.java (revision 0) @@ -0,0 +1,78 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you 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. + * + * JBoss DNA 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.jboss.dna.jcr; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import javax.jcr.nodetype.NodeDefinition; +import javax.jcr.nodetype.NodeType; +import javax.jcr.nodetype.PropertyDefinition; + +import org.jboss.dna.graph.JcrNtLexicon; + +/** + * {@link JcrNodeTypeSource} that provides built-in node types per spec. + * NOTE: This class is not complete and does not yet provide all built-in + * types. + */ +class JcrBuiltinNodeTypeSource implements JcrNodeTypeSource { + + private final List primaryNodeTypes; + + JcrBuiltinNodeTypeSource(JcrSession session) { + primaryNodeTypes = new ArrayList(); + + // Stubbing in child node and property definitions for now + JcrNodeType base = new JcrNodeType(session, JcrNtLexicon.BASE, null, Collections.emptyList(), Collections.emptyList(), false, false); + JcrNodeType unstructured = new JcrNodeType(session, JcrNtLexicon.UNSTRUCTURED, + Arrays.asList(new NodeType[] { base }), Collections.emptyList(), Collections.emptyList(), false, false); + + primaryNodeTypes.add(base); + primaryNodeTypes.add(unstructured); + } + + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.jcr.JcrNodeTypeSource#getMixinNodeTypes() + */ + public Collection getMixinNodeTypes() { + return Collections.emptyList(); + } + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.jcr.JcrNodeTypeSource#getPrimaryNodeTypes() + */ + public Collection getPrimaryNodeTypes() { + return primaryNodeTypes; + } + +} Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java (revision 0) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java (revision 0) @@ -0,0 +1,284 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you 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. + * + * JBoss DNA 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.jboss.dna.jcr; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +import javax.jcr.Value; +import javax.jcr.nodetype.NodeDefinition; +import javax.jcr.nodetype.NodeType; +import javax.jcr.nodetype.PropertyDefinition; + +import net.jcip.annotations.Immutable; + +import org.jboss.dna.graph.property.Name; + +/** + * DNA implementation of JCR {@link NodeType}s. + */ +@Immutable +class JcrNodeType implements NodeType { + + private final Name name; + private String primaryItemName; + private Map childNodeDefinitions; + private Map propertyDefinitions; + private List declaredSupertypes; + + private boolean mixin; + private boolean orderableChildNodes; + + // Somewhat oddly, we need a reference to the session to support namespace remapping + private JcrSession session; + + JcrNodeType(JcrSession session, Name name, List declaredSupertypes, Collection childNodeDefinitions, Collection propertyDefinitions, boolean mixin, boolean orderableChildNodes) { + this.session = session; + this.name = name; // name.getString(Path.NO_OP_ENCODER); + this.declaredSupertypes = declaredSupertypes != null ? declaredSupertypes : Collections.emptyList(); + this.mixin = mixin; + this.orderableChildNodes = orderableChildNodes; + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#canAddChildNode(java.lang.String) + */ + public boolean canAddChildNode(String arg0) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#canAddChildNode(java.lang.String, java.lang.String) + */ + public boolean canAddChildNode(String arg0, String arg1) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#canRemoveItem(java.lang.String) + */ + public boolean canRemoveItem(String arg0) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#canSetProperty(java.lang.String, javax.jcr.Value) + */ + public boolean canSetProperty(String propertyName, Value value) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#canSetProperty(java.lang.String, javax.jcr.Value[]) + */ + public boolean canSetProperty(String propertyName, Value[] values) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#getChildNodeDefinitions() + */ + public NodeDefinition[] getChildNodeDefinitions() { + return (NodeDefinition[]) childNodeDefinitions.values().toArray(); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#getDeclaredChildNodeDefinitions() + */ + public NodeDefinition[] getDeclaredChildNodeDefinitions() { + Set nodeDefs = new HashSet(); + NodeType[] supertypes = getSupertypes(); + + // TODO: This could be cached after being calculated once + for (int i = 0; i < supertypes.length; i++) { + NodeDefinition[] childNodeDefinitions = supertypes[i].getChildNodeDefinitions(); + for (int j = 0; j < childNodeDefinitions.length; i++) { + + //TODO: Could add sanity check here (assertion?) that definitions of the same child node in multiple supertypes are consistent + nodeDefs.add(childNodeDefinitions[j]); + } + } + + nodeDefs.addAll(childNodeDefinitions.values()); + + return (NodeDefinition[]) nodeDefs.toArray(); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#getDeclaredPropertyDefinitions() + */ + public PropertyDefinition[] getDeclaredPropertyDefinitions() { + Set propDefs = new HashSet(); + NodeType[] supertypes = getSupertypes(); + + // TODO: This could be cached after being calculated once + for (int i = 0; i < supertypes.length; i++) { + PropertyDefinition[] childPropertyDefinitions = supertypes[i].getPropertyDefinitions(); + for (int j = 0; j < childPropertyDefinitions.length; i++) { + + //TODO: Could add sanity check here (assertion?) that definitions of the same child node in multiple supertypes are consistent + propDefs.add(childPropertyDefinitions[j]); + } + } + propDefs.addAll(propertyDefinitions.values()); + + return (PropertyDefinition[]) propDefs.toArray(); } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#getDeclaredSupertypes() + */ + public NodeType[] getDeclaredSupertypes() { + return declaredSupertypes.toArray(new NodeType[declaredSupertypes.size()]); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#getName() + */ + public String getName() { + // Translate the name to the correct prefix. Need to check the session to support url-remapping. + return name.getString(session.getExecutionContext().getNamespaceRegistry()); + } + + /** + * Returns the internal {@link Name} object for the note type. + * This method exists outside the JCR API and should not be exposed outside + * of the package. + * + * @return the internal {@link Name} object for the note type. + */ + Name getInternalName() { + return name; + } + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#getPrimaryItemName() + */ + public String getPrimaryItemName() { + return primaryItemName; + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#getPropertyDefinitions() + */ + public PropertyDefinition[] getPropertyDefinitions() { + return (PropertyDefinition[]) propertyDefinitions.values().toArray(); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#getSupertypes() + */ + public NodeType[] getSupertypes() { + Set supertypes = new HashSet(); + Stack unvisitedSupertypes = new Stack(); + + assert declaredSupertypes != null; + unvisitedSupertypes.addAll(declaredSupertypes); + + //TODO: If this ends up getting called frequently, it should probably be executed once in the constructor and have the results cached. + while (!unvisitedSupertypes.isEmpty()) { + NodeType nodeType = unvisitedSupertypes.pop(); + + /* + * If we haven't already visited this nodeType (which we can + * infer by whether or not it was already added to the return set), + * then add the supertypes of this new node to the unvisited set for + * further inspection. + */ + if (!supertypes.add(nodeType)) { + // Violating encapsulation to avoid going from List to array back to List + unvisitedSupertypes.addAll(((JcrNodeType) nodeType).declaredSupertypes); + } + } + + return supertypes.toArray(new NodeType[0]); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#hasOrderableChildNodes() + */ + public boolean hasOrderableChildNodes() { + return orderableChildNodes; + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#isMixin() + */ + public boolean isMixin() { + return mixin; + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeType#isNodeType(java.lang.String) + */ + public boolean isNodeType(String nodeTypeName) { + if (this.getName().equals(nodeTypeName)) + return true; + + //TODO: This could be optimized + NodeType[] supertypes = getSupertypes(); + for (int i = 0; i < supertypes.length; i++) { + if (supertypes[i].getName().equals(nodeTypeName)) { + return true; + } + } + + return false; + } +} Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeIterator.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeIterator.java (revision 0) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeIterator.java (revision 0) @@ -0,0 +1,117 @@ +/* + * JBoss DNA (http://www.jboss.org/dna) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA + * is licensed to you 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. + * + * JBoss DNA 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.jboss.dna.jcr; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; + +import javax.jcr.nodetype.NodeType; +import javax.jcr.nodetype.NodeTypeIterator; + +/** + * Type-safe {@link Iterator} implementation for NodeTypes, as per the JCR specification. + */ +final class JcrNodeTypeIterator implements NodeTypeIterator { + + private int size; + private int position; + private Iterator iterator; + + JcrNodeTypeIterator(Collection values) { + this.iterator = Collections.unmodifiableCollection(values).iterator(); + this.size = values.size(); + this.position = 0; + + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeTypeIterator#nextNodeType() + */ + public NodeType nextNodeType() { + //TODO: Does this really need to return a copy of the node type to prevent manipulation? + position++; + return iterator.next(); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.RangeIterator#getPosition() + */ + public long getPosition() { + return position; + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.RangeIterator#getSize() + */ + public long getSize() { + return size; + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.RangeIterator#skip(long) + */ + public void skip(long count) { + position += count; + while (count-- > 0) + iterator.next(); + } + + /** + * {@inheritDoc} + * + * @see java.util.Iterator#hasNext() + */ + public boolean hasNext() { + return iterator.hasNext(); + } + + /** + * {@inheritDoc} + * + * @see java.util.Iterator#next() + */ + public Object next() { + position++; + return iterator.next(); + } + + /** + * {@inheritDoc} + * + * @see java.util.Iterator#remove() + */ + public void remove() { + throw new UnsupportedOperationException("Node types cannot be removed through their iterator"); + } + +} Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeSource.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeSource.java (revision 0) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeSource.java (revision 0) @@ -0,0 +1,22 @@ +package org.jboss.dna.jcr; + +import java.util.Collection; + +import javax.jcr.nodetype.NodeType; + +/** + * Interface for any potential provider of {@link JcrNodeType} definitions, the + * DNA implementation of {@link NodeType}. + * + * Possible sources of node type definitions include CND files, repository metadata, + * and mock types for testing. + * + * @see JcrWorkspace#getNodeTypeManager() + */ +public interface JcrNodeTypeSource { + + // For now, this should be sufficiently self-explanatory + public Collection getPrimaryNodeTypes(); + public Collection getMixinNodeTypes(); + +} Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyIterator.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyIterator.java (revision 731) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrPropertyIterator.java (working copy) @@ -36,10 +36,12 @@ private final Iterator iterator; private int ndx; + private int size; JcrPropertyIterator( Set properties ) { assert properties != null; iterator = properties.iterator(); + size = properties.size(); } /** @@ -54,11 +56,10 @@ /** * {@inheritDoc} * - * @return -1L * @see javax.jcr.RangeIterator#getSize() */ public long getSize() { - return -1L; + return size; } /** Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java (revision 731) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java (working copy) @@ -28,11 +28,14 @@ import java.security.AccessControlException; import java.security.Principal; import java.util.Calendar; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.UUID; import javax.jcr.Credentials; import javax.jcr.Item; +import javax.jcr.NamespaceException; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; @@ -52,7 +55,9 @@ import org.jboss.dna.graph.DnaLexicon; import org.jboss.dna.graph.ExecutionContext; import org.jboss.dna.graph.Graph; +import org.jboss.dna.graph.JcrNtLexicon; import org.jboss.dna.graph.property.Name; +import org.jboss.dna.graph.property.NamespaceRegistry; import org.jboss.dna.graph.property.Path; import org.jboss.dna.graph.property.UuidFactory; import org.jboss.dna.graph.property.ValueFactories; @@ -72,6 +77,7 @@ private final ExecutionContext executionContext; private final ReferenceMap nodesByUuid; private final ReferenceMap nodesByJcrUuid; + private final JcrNamespaceRegistry sessionNamespaceRegistry; private boolean isLive; private Workspace workspace; private JcrRootNode rootNode; @@ -86,17 +92,41 @@ assert nodesByUuid != null; this.repository = repository; this.graph = graph; - this.executionContext = graph.getContext(); - assert this.executionContext != null; + ExecutionContext executionContext = graph.getContext(); + assert executionContext != null; this.nodesByUuid = nodesByUuid; this.nodesByJcrUuid = new ReferenceMap(ReferenceType.STRONG, ReferenceType.SOFT); this.isLive = true; // Following must be initialized after session's state is initialized this.workspace = new JcrWorkspace(this, workspaceName); + + // Following must be set after the workspace's state is initialized + /* + * The workspace must be created before the namespace registry in the execution context + * is redirected. This allows the workspace namespace registry to point to the permanent + * DNA namespace registry while the execution context also supports the transient + * namespace remappings from the session/ + */ + DelegatingNamespaceRegistry dnr = new DelegatingNamespaceRegistry(executionContext.getNamespaceRegistry()); + this.executionContext = executionContext.with(dnr); + assert this.executionContext != null; + + /* + * Create a session-scoped namespace registry to ensure that the session namespace methods have the same semantics + * as the workspace namespace methods. + */ + + sessionNamespaceRegistry = new JcrNamespaceRegistry(dnr); + } ExecutionContext getExecutionContext() { - return this.executionContext; + //TODO: This is a hack to create the JcrWorkspace before the final execution context is set. + if (this.executionContext == null) { + return graph.getContext(); + } + + return this.executionContext; } /** @@ -261,7 +291,7 @@ * @see javax.jcr.Session#getNamespacePrefix(java.lang.String) */ public String getNamespacePrefix( String uri ) throws RepositoryException { - return workspace.getNamespaceRegistry().getPrefix(uri); + return sessionNamespaceRegistry.getPrefix(uri); } /** @@ -269,8 +299,8 @@ * * @see javax.jcr.Session#getNamespacePrefixes() */ - public String[] getNamespacePrefixes() throws RepositoryException { - return workspace.getNamespaceRegistry().getPrefixes(); + public String[] getNamespacePrefixes() { + return sessionNamespaceRegistry.getPrefixes(); } /** @@ -279,7 +309,7 @@ * @see javax.jcr.Session#getNamespaceURI(java.lang.String) */ public String getNamespaceURI( String prefix ) throws RepositoryException { - return workspace.getNamespaceRegistry().getURI(prefix); + return sessionNamespaceRegistry.getURI(prefix); } private Node getNode( Path path ) throws RepositoryException { @@ -344,6 +374,15 @@ rootNode = new JcrRootNode(this); // Get root node from source populateNode(rootNode, graph.getNodeAt(executionContext.getValueFactories().getPathFactory().createRootPath())); + + // Root nodes need to have a type in JCR land + // JcrProperty primaryType = new JcrProperty(rootNode, getExecutionContext(), JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.BASE); + String typeValue = JcrNtLexicon.BASE.getString(executionContext.getNamespaceRegistry()); + JcrProperty primaryType = new JcrProperty(rootNode, getExecutionContext(), JcrLexicon.PRIMARY_TYPE, typeValue); + + //TODO: This is ugly + rootNode.properties.add(primaryType); + return rootNode; } @@ -577,11 +616,138 @@ /** * {@inheritDoc} * - * @throws UnsupportedOperationException always * @see javax.jcr.Session#setNamespacePrefix(java.lang.String, java.lang.String) */ public void setNamespacePrefix( String newPrefix, - String existingUri ) { - throw new UnsupportedOperationException(); + String existingUri ) + throws NamespaceException, RepositoryException + { + + try { + String preexistingUri = sessionNamespaceRegistry.getURI(newPrefix); + throw new NamespaceException("Prefix " + newPrefix + " is already mapped to " + + preexistingUri); + } + catch (NamespaceException ne) { + // No error. This indicates that the prefix is not already mapped. + } + + // This will throw a NamespaceException for us if the URI is NOT already mapped. + sessionNamespaceRegistry.getPrefix(existingUri); + + if (newPrefix.length() > 2 && "xml".equalsIgnoreCase(newPrefix.substring(0, 3))) { + throw new NamespaceException("Invalid attempt to remap URL to a prefix beginning with 'xml'."); + } + + // Get the delegating namespace registry so that the change is transient. + executionContext.getNamespaceRegistry().register(newPrefix, existingUri); } + + class DelegatingNamespaceRegistry implements NamespaceRegistry { + private final NamespaceRegistry delegate; + private final Map remappedPrefixes; + private final Map remappedURIs; + + DelegatingNamespaceRegistry(NamespaceRegistry delegate) { + this.delegate = delegate; + + this.remappedPrefixes = new HashMap(); + this.remappedURIs = new HashMap(); + } + + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.graph.property.NamespaceRegistry#getDefaultNamespaceUri() + */ + public String getDefaultNamespaceUri() { + return delegate.getDefaultNamespaceUri(); + } + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.graph.property.NamespaceRegistry#getNamespaceForPrefix(String) + */ + public String getNamespaceForPrefix( String prefix ) { + String remappedUri = remappedPrefixes.get(prefix); + if (remappedUri != null) { + return remappedUri; + } + + return delegate.getNamespaceForPrefix(prefix); + } + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.graph.property.NamespaceRegistry#getPrefixForNamespaceUri(String,boolean) + */ + public String getPrefixForNamespaceUri( String namespaceUri, + boolean generateIfMissing ) { + String remappedPrefix = remappedURIs.get(namespaceUri); + if (remappedPrefix != null) { + return remappedPrefix; + } + + return delegate.getPrefixForNamespaceUri(namespaceUri, generateIfMissing); + } + + /** + * {@inheritDoc} + * + * @see org.jboss.dna.graph.property.NamespaceRegistry#isRegisteredNamespaceUri(String) + */ + public boolean isRegisteredNamespaceUri( String namespaceUri ) { + return delegate.isRegisteredNamespaceUri(namespaceUri); + } + + /** + * {@inheritDoc} + * Note: This implementation will NOT delegate registry updates to the underlying registry. + * @see org.jboss.dna.graph.property.NamespaceRegistry#register(String, String) + */ + public String register( String prefix, + String namespaceUri ) { + String oldPrefix = remappedURIs.get(namespaceUri); + if (oldPrefix == null) { + oldPrefix = delegate.getPrefixForNamespaceUri(namespaceUri, false); + } + + remappedPrefixes.put(prefix, namespaceUri); + remappedURIs.put(namespaceUri, prefix); + + return oldPrefix; + } + + /** + * {@inheritDoc} + * + * @throws UnsupportedOperationException always thrown + * @see org.jboss.dna.graph.property.NamespaceRegistry#unregister(String) + */ + public boolean unregister( String namespaceUri ) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * Note: This implementation returns these URIs directly from the delegate. + * @see org.jboss.dna.graph.property.NamespaceRegistry#getRegisteredNamespaceUris() + */ + public Set getRegisteredNamespaceUris() { + return delegate.getRegisteredNamespaceUris(); + } + + /** + * {@inheritDoc} + * + * @throws UnsupportedOperationException always thrown + * @see org.jboss.dna.graph.property.NamespaceRegistry#getNamespaces() + */ + public Set getNamespaces() { + throw new UnsupportedOperationException(); + } + } } Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java (revision 731) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java (working copy) @@ -24,14 +24,23 @@ package org.jboss.dna.jcr; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.jcr.NamespaceRegistry; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Workspace; +import javax.jcr.nodetype.NoSuchNodeTypeException; +import javax.jcr.nodetype.NodeType; +import javax.jcr.nodetype.NodeTypeIterator; import javax.jcr.nodetype.NodeTypeManager; import javax.jcr.observation.ObservationManager; import javax.jcr.query.QueryManager; import javax.jcr.version.Version; +import org.jboss.dna.graph.property.Name; import org.xml.sax.ContentHandler; /** @@ -40,15 +49,19 @@ */ final class JcrWorkspace implements Workspace { + // Kept at protected visibility so that it is visible to inner class (JcrNodeTypeManager) + protected final JcrSession session; + private final String name; - private final JcrSession session; private final NamespaceRegistry namespaceRegistry; + private final NodeTypeManager nodeTypeManager; /** * @param session the session that owns this workspace; may not be null * @param name the name of the workspace; may not be null * @throws RepositoryException */ + @SuppressWarnings("synthetic-access") JcrWorkspace( JcrSession session, String name ) throws RepositoryException { assert session != null; @@ -56,6 +69,7 @@ this.session = session; this.name = name; this.namespaceRegistry = new JcrNamespaceRegistry(session.getExecutionContext().getNamespaceRegistry()); + this.nodeTypeManager = new JcrNodeTypeManager(new JcrBuiltinNodeTypeSource(session)); // Ensure workspace with supplied name is accessible // if (name == null) name = session.getDnaRepository().getSource(session.getSubject()).getName(); // String matchedName = null; @@ -154,7 +168,7 @@ * {@inheritDoc} */ public NodeTypeManager getNodeTypeManager() { - throw new UnsupportedOperationException(); + return nodeTypeManager; } /** @@ -202,4 +216,95 @@ boolean removeExisting ) { throw new UnsupportedOperationException(); } -} + + /** + * Local implementation of @{link NodeTypeManager}. Initialized with {@link NodeType} + * source data when it is created (in the {@link JcrWorkspace} constructor. + */ + private class JcrNodeTypeManager implements NodeTypeManager { + + private final Map primaryNodeTypes; + private final Map mixinNodeTypes; + + private JcrNodeTypeManager(JcrNodeTypeSource source) { + Collection primary = source.getPrimaryNodeTypes(); + Collection mixins = source.getMixinNodeTypes(); + + primaryNodeTypes = new HashMap(primary.size()); + for(JcrNodeType nodeType : primary) { + primaryNodeTypes.put(nodeType.getInternalName(), nodeType); + } + + mixinNodeTypes = new HashMap(mixins.size()); + for(JcrNodeType nodeType : mixins) { + mixinNodeTypes.put(nodeType.getInternalName(), nodeType); + } + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeTypeManager#getAllNodeTypes() + */ + public NodeTypeIterator getAllNodeTypes() { + + //TODO: Can revisit this approach later if it becomes a performance issue + /* + * Note also that this creates a subtle difference in behavior for concurrent modification + * between this method and the specific get*NodeTypes methods. That is, if a type is added + * while an iterator from the corresponding specific get*NodeType method is being traversed, + * a ConcurrentModificationException will be thrown. Because this iterator is based on a copy + * of the underlying maps, no exception would be thrown in the same case. + */ + + List allTypes = new ArrayList(primaryNodeTypes.size() + mixinNodeTypes.size()); + allTypes.addAll(primaryNodeTypes.values()); + allTypes.addAll(mixinNodeTypes.values()); + return new JcrNodeTypeIterator(allTypes); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeTypeManager#getMixinNodeTypes() + */ + public NodeTypeIterator getMixinNodeTypes() { + return new JcrNodeTypeIterator(mixinNodeTypes.values()); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeTypeManager#getNodeType(java.lang.String) + */ + public NodeType getNodeType(String nodeTypeName) throws NoSuchNodeTypeException, + RepositoryException { + Name ntName = session.getExecutionContext().getValueFactories().getNameFactory().create(nodeTypeName); + + NodeType nodeType = primaryNodeTypes.get(ntName); + + if (nodeType != null) { + return nodeType; + } + + nodeType = mixinNodeTypes.get(ntName); + + if (nodeType != null) { + return nodeType; + } + + throw new NoSuchNodeTypeException("No node type named: " + nodeTypeName); + } + + /** + * {@inheritDoc} + * + * @see javax.jcr.nodetype.NodeTypeManager#getPrimaryNodeTypes() + */ + public NodeTypeIterator getPrimaryNodeTypes() { + return new JcrNodeTypeIterator(primaryNodeTypes.values()); + } + + } + +} \ No newline at end of file Index: dna-jcr/src/test/resources/repositoryJackRabbitTck.xml =================================================================== --- dna-jcr/src/test/resources/repositoryJackRabbitTck.xml (revision 736) +++ dna-jcr/src/test/resources/repositoryJackRabbitTck.xml (working copy) @@ -24,9 +24,9 @@ ~ 51 Franklin Street, Fifth Floor ~ Boston, MA 02110-1301 USA --> - - - - - + + + + + \ No newline at end of file