Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionImpl.java =================================================================== --- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionImpl.java (revision 1463428) +++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionImpl.java (working copy) @@ -19,11 +19,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Locale; import java.util.Map; -import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; @@ -32,7 +28,6 @@ import javax.jcr.InvalidSerializedDataException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; -import javax.jcr.NamespaceException; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; @@ -64,7 +59,6 @@ import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials; import org.apache.jackrabbit.oak.util.TODO; import org.apache.jackrabbit.util.Text; -import org.apache.jackrabbit.util.XMLChar; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.ContentHandler; @@ -79,18 +73,12 @@ private final SessionContext sessionContext; private final SessionDelegate sd; - /** - * Local namespace remappings. Prefixes as keys and namespace URIs as values. - *

- * This map is only accessed from synchronized methods (see - * JCR-1793). - */ - private final Map namespaces; + private final SessionNamespaces namespaces; SessionImpl(SessionContext sessionContext, Map namespaces) { this.sessionContext = sessionContext; this.sd = sessionContext.getSessionDelegate(); - this.namespaces = namespaces; + this.namespaces = new SessionNamespaces(namespaces, sessionContext); } static void checkProtectedNodes(Session session, String... absJcrPaths) throws RepositoryException { @@ -410,9 +398,7 @@ if (sd.isAlive()) { sessionContext.dispose(); sd.logout(); - synchronized (namespaces) { - namespaces.clear(); - } + namespaces.clear(); } } @@ -597,109 +583,25 @@ } //---------------------------------------------------------< Namespaces >--- - // The code below was initially copied from JCR Commons AbstractSession, but - // provides information the "hasRemappings" information @Override public void setNamespacePrefix(String prefix, String uri) throws RepositoryException { - if (prefix == null) { - throw new IllegalArgumentException("Prefix must not be null"); - } else if (uri == null) { - throw new IllegalArgumentException("Namespace must not be null"); - } else if (prefix.isEmpty()) { - throw new NamespaceException( - "Empty prefix is reserved and can not be remapped"); - } else if (uri.isEmpty()) { - throw new NamespaceException( - "Default namespace is reserved and can not be remapped"); - } else if (prefix.toLowerCase(Locale.ENGLISH).startsWith("xml")) { - throw new NamespaceException( - "XML prefixes are reserved: " + prefix); - } else if (!XMLChar.isValidNCName(prefix)) { - throw new NamespaceException( - "Prefix is not a valid XML NCName: " + prefix); - } - - synchronized (namespaces) { - // Remove existing mapping for the given prefix - namespaces.remove(prefix); - - // Remove existing mapping(s) for the given URI - Set prefixes = new HashSet(); - for (Map.Entry entry : namespaces.entrySet()) { - if (entry.getValue().equals(uri)) { - prefixes.add(entry.getKey()); - } - } - namespaces.keySet().removeAll(prefixes); - - // Add the new mapping - namespaces.put(prefix, uri); - } + namespaces.setNamespacePrefix(prefix, uri); } @Override public String[] getNamespacePrefixes() throws RepositoryException { - synchronized (namespaces) { - if (namespaces.isEmpty()) { - return getWorkspace().getNamespaceRegistry().getPrefixes(); - } - } - Set uris = new HashSet(); - uris.addAll(Arrays.asList(getWorkspace().getNamespaceRegistry().getURIs())); - synchronized (namespaces) { - // Add namespace uris only visible to session - uris.addAll(namespaces.values()); - } - Set prefixes = new HashSet(); - for (String uri : uris) { - prefixes.add(getNamespacePrefix(uri)); - } - return prefixes.toArray(new String[prefixes.size()]); + return namespaces.getNamespacePrefixes(); } @Override public String getNamespaceURI(String prefix) throws RepositoryException { - synchronized (namespaces) { - String uri = namespaces.get(prefix); - - if (uri == null) { - // Not in local mappings, try the global ones - uri = getWorkspace().getNamespaceRegistry().getURI(prefix); - if (namespaces.containsValue(uri)) { - // The global URI is locally mapped to some other prefix, - // so there are no mappings for this prefix - throw new NamespaceException("Namespace not found: " + prefix); - } - } - - return uri; - } + return namespaces.getNamespaceURI(prefix); } @Override public String getNamespacePrefix(String uri) throws RepositoryException { - synchronized (namespaces) { - for (Map.Entry entry : namespaces.entrySet()) { - if (entry.getValue().equals(uri)) { - return entry.getKey(); - } - } - - // The following throws an exception if the URI is not found, that's OK - String prefix = getWorkspace().getNamespaceRegistry().getPrefix(uri); - - // Generate a new prefix if the global mapping is already taken - String base = prefix; - for (int i = 2; namespaces.containsKey(prefix); i++) { - prefix = base + i; - } - - if (!base.equals(prefix)) { - namespaces.put(prefix, uri); - } - return prefix; - } + return namespaces.getNamespacePrefix(uri); } //--------------------------------------------------< JackrabbitSession >--- Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionNamespaces.java =================================================================== --- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionNamespaces.java (revision 0) +++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionNamespaces.java (revision 0) @@ -0,0 +1,216 @@ +/* + * 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.oak.jcr; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nonnull; +import javax.jcr.NamespaceException; +import javax.jcr.NamespaceRegistry; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.jackrabbit.util.XMLChar; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * SessionNamespaces implements namespace handling on the JCR + * Session level. That is, it maintains a map of session local namespace + * re-mappings and takes a snapshot of the namespace registry when initialized + * (see JCR 2.0 specification, section 3.5.1). + */ +class SessionNamespaces { + + /** + * Local namespace remappings. Prefixes as keys and namespace URIs as values. + *

+ * This map is only accessed from synchronized methods (see + * JCR-1793). + */ + private final Map namespaces; + + /** + * A snapshot of the namespace registry when these SessionNamespaces + * are first accessed. + */ + private Map snapshot; + + private final SessionContext sessionContext; + + SessionNamespaces(@Nonnull Map namespaces, + @Nonnull SessionContext sessionContext) { + this.namespaces = checkNotNull(namespaces); + this.sessionContext = checkNotNull(sessionContext); + } + + // The code below was initially copied from JCR Commons AbstractSession, but + // provides information the "hasRemappings" information + + /** + * @see Session#setNamespacePrefix(String, String) + */ + void setNamespacePrefix(String prefix, String uri) throws RepositoryException { + init(); + if (prefix == null) { + throw new IllegalArgumentException("Prefix must not be null"); + } else if (uri == null) { + throw new IllegalArgumentException("Namespace must not be null"); + } else if (prefix.isEmpty()) { + throw new NamespaceException( + "Empty prefix is reserved and can not be remapped"); + } else if (uri.isEmpty()) { + throw new NamespaceException( + "Default namespace is reserved and can not be remapped"); + } else if (prefix.toLowerCase(Locale.ENGLISH).startsWith("xml")) { + throw new NamespaceException( + "XML prefixes are reserved: " + prefix); + } else if (!XMLChar.isValidNCName(prefix)) { + throw new NamespaceException( + "Prefix is not a valid XML NCName: " + prefix); + } + + synchronized (namespaces) { + // Remove existing mapping for the given prefix + namespaces.remove(prefix); + + // Remove existing mapping(s) for the given URI + Set prefixes = new HashSet(); + for (Map.Entry entry : namespaces.entrySet()) { + if (entry.getValue().equals(uri)) { + prefixes.add(entry.getKey()); + } + } + namespaces.keySet().removeAll(prefixes); + + // Add the new mapping + namespaces.put(prefix, uri); + } + } + + /** + * @see Session#getNamespacePrefixes() + */ + String[] getNamespacePrefixes() throws RepositoryException { + init(); + synchronized (namespaces) { + if (namespaces.isEmpty()) { + Set prefixes = snapshot.keySet(); + return prefixes.toArray(new String[prefixes.size()]); + } + } + Set uris = new HashSet(); + uris.addAll(snapshot.values()); + synchronized (namespaces) { + // Add namespace uris only visible to session + uris.addAll(namespaces.values()); + } + Set prefixes = new HashSet(); + for (String uri : uris) { + prefixes.add(getNamespacePrefix(uri)); + } + return prefixes.toArray(new String[prefixes.size()]); + } + + /** + * @see Session#getNamespaceURI(String) + */ + String getNamespaceURI(String prefix) throws RepositoryException { + init(); + synchronized (namespaces) { + String uri = namespaces.get(prefix); + + if (uri == null) { + // Not in local mappings, try snapshot ones + uri = snapshot.get(prefix); + if (uri == null) { + // Not in snapshot mappings, try the global ones + uri = getNamespaceRegistry().getURI(prefix); + if (namespaces.containsValue(uri)) { + // The global URI is locally mapped to some other prefix, + // so there are no mappings for this prefix + throw new NamespaceException("Namespace not found: " + prefix); + } + } + } + + return uri; + } + } + + /** + * @see Session#getNamespacePrefix(String) + */ + String getNamespacePrefix(String uri) throws RepositoryException { + init(); + synchronized (namespaces) { + for (Map.Entry entry : namespaces.entrySet()) { + if (entry.getValue().equals(uri)) { + return entry.getKey(); + } + } + + // try snapshot + for (Map.Entry entry : snapshot.entrySet()) { + if (entry.getValue().equals(uri)) { + return entry.getKey(); + } + } + + // The following throws an exception if the URI is not found, that's OK + String prefix = getNamespaceRegistry().getPrefix(uri); + + // Generate a new prefix if the global mapping is already taken + String base = prefix; + for (int i = 2; namespaces.containsKey(prefix); i++) { + prefix = base + i; + } + + if (!base.equals(prefix)) { + namespaces.put(prefix, uri); + } + return prefix; + } + } + + /** + * Clears the re-mapped namespaces map. + */ + void clear() { + namespaces.clear(); + } + + private NamespaceRegistry getNamespaceRegistry() + throws RepositoryException { + return sessionContext.getWorkspace().getNamespaceRegistry(); + } + + private void init() throws RepositoryException { + if (snapshot == null) { + NamespaceRegistry registry = getNamespaceRegistry(); + Map map = new HashMap(); + for (String prefix : registry.getPrefixes()) { + map.put(prefix, registry.getURI(prefix)); + } + snapshot = map; + } + } +} Property changes on: oak-jcr\src\main\java\org\apache\jackrabbit\oak\jcr\SessionNamespaces.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision Rev URL Added: svn:eol-style + native