Index: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/NameResolver.java =================================================================== --- jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/NameResolver.java (revision 0) +++ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/NameResolver.java (revision 0) @@ -0,0 +1,46 @@ +/* + * 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.name; + +import javax.jcr.NamespaceException; + +/** + * Resolver for prefixed JCR names and namespace-qualified + * {@link QName QNames}. + */ +public interface NameResolver { + + /** + * Returns the qualified name for the given prefixed JCR name. + * + * @param name prefixed JCR name + * @return qualified name + * @throws NameException if the JCR name format is invalid + * @throws NamespaceException if the namespace prefix can not be resolved + */ + QName getQName(String name) throws NameException, NamespaceException; + + /** + * Returns the prefixed JCR name for the given qualified name. + * + * @param name qualified name + * @return prefixed JCR name + * @throws NamespaceException if the namespace URI can not be resolved + */ + String getJCRName(QName name) throws NamespaceException; + +} Property changes on: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/NameResolver.java ___________________________________________________________________ Name: svn:eol-style + native Index: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/CachingNameResolver.java =================================================================== --- jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/CachingNameResolver.java (revision 0) +++ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/CachingNameResolver.java (revision 0) @@ -0,0 +1,208 @@ +/* + * 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.name; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.jcr.NamespaceException; + +/** + * Name resolver decorator that uses a generational cache to speed up + * parsing of prefixed JCR names. Uncached names are resolved using the + * decorated name resolver. + *

+ * The cache consists of three parts: a long term cache and two generations + * of recent cache entries. The two generations are used to collect recent new + * entries, and those entries that are used within two successive generations + * get promoted to the long term cache. The entries within the long term cache + * are discarded only when the size of the cache exceeds the given maximum + * cache size. + */ +public class CachingNameResolver implements NameResolver { + + /** + * Default maximum cache size. + */ + private static final int DEFAULT_MAX_CACHE_SIZE = 1000; + + /** + * Divisor used to determine the default generation age from the + * maximum cache size. + */ + private static final int DEFAULT_AGE_DIVISOR = 10; + + /** + * Decorated name resolver. + */ + private final NameResolver resolver; + + /** + * Maximum size of the name cache. + */ + private final int maxCacheSize; + + /** + * Maximum age of a cache generation. + */ + private final int maxGenerationAge; + + /** + * Long term name cache. The cache map is always read-only, cache updates + * are performed by atomically replacing the map reference. + */ + private Map cache = new HashMap(); + + /** + * Old cache generation. + */ + private Map old = new HashMap(); + + /** + * Young cache generation. + */ + private Map young = Collections.synchronizedMap(new HashMap()); + + /** + * Age of the young cache generation. + */ + private int generationAge = 0; + + /** + * Creates a caching name resolver. + * + * @param resolver decorated name resolver + * @param maxCacheSize maximum size of the long term cache + * @param maxGenerationAge maximum age of a cache generation + */ + public CachingNameResolver( + NameResolver resolver, int maxCacheSize, int maxGenerationAge) { + this.resolver = resolver; + this.maxCacheSize = maxCacheSize; + this.maxGenerationAge = maxGenerationAge; + } + + /** + * Creates a caching name resolver using the default generation age for + * the given cache size. + * + * @param resolver decorated name resolver + * @param maxCacheSize maximum size of the long term cache + */ + public CachingNameResolver(NameResolver resolver, int maxCacheSize) { + this(resolver, maxCacheSize, maxCacheSize / DEFAULT_AGE_DIVISOR); + } + + /** + * Creates a caching name resolver using the default size and + * generation age. + * + * @param resolver decorated name resolver + */ + public CachingNameResolver(NameResolver resolver) { + this(resolver, DEFAULT_MAX_CACHE_SIZE); + } + + /** + * Increases the age of the current cache generation. When the maximum + * age of a generation is reached, the following steps are taken: + *

    + *
  1. The union of the two cache generations is calculated
  2. + *
  3. The union is added to the long term name cache
  4. + *
  5. If the cache size exceeds the maximum, only the union is kept
  6. + *
  7. A new cache generation is started
  8. + *
+ */ + private synchronized void increaseGenerationAge() { + if (++generationAge == maxGenerationAge) { + Map persistentEntries = new HashMap(); + synchronized (old) { + Iterator iterator = old.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry) iterator.next(); + if (young.containsKey(entry.getKey())) { + persistentEntries.put(entry.getKey(), entry.getValue()); + } + } + } + + if (!persistentEntries.isEmpty()) { + Map newCache = new HashMap(); + newCache.putAll(cache); + newCache.putAll(persistentEntries); + if (newCache.size() <= maxCacheSize) { + cache = newCache; + } else { + cache = persistentEntries; + } + } + + old = young; + young = Collections.synchronizedMap(new HashMap()); + generationAge = 0; + } + } + + //--------------------------------------------------------< NameResolver > + + /** + * Returns the qualified name for the given prefixed JCR name. The name + * is first looked up form the generational cache and the call gets + * delegated to the decorated name resolver only if the cache misses. + * + * @param name prefixed JCR name + * @return qualified name + * @throws NameException if the JCR name format is invalid + * @throws NamespaceException if the namespace prefix can not be resolved + */ + public QName getQName(String name) + throws NameException, NamespaceException { + // Check for the name in the long-time cache + QName qname = (QName) cache.get(name); + if (qname == null) { + // Have we already seen this name during this generation? + qname = (QName) young.get(name); + if (qname == null) { + // No, check the old generation or delegate the call + qname = (QName) old.get(name); + if (qname == null) { + qname = resolver.getQName(name); + } + // Mark the name as resolved during this generation + young.put(name, qname); + } + increaseGenerationAge(); + } + return qname; + } + + + /** + * Returns the prefixed JCR name for the given qualified name. + * The call is delegated to the decorated name resolver. + * + * @param name qualified name + * @return prefixed JCR name + * @throws NamespaceException if the namespace URI can not be resolved + */ + public String getJCRName(QName name) throws NamespaceException { + return resolver.getJCRName(name); + } + +} Property changes on: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/CachingNameResolver.java ___________________________________________________________________ Name: svn:eol-style + native Index: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/ParsingNameResolver.java =================================================================== --- jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/ParsingNameResolver.java (revision 0) +++ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/ParsingNameResolver.java (revision 0) @@ -0,0 +1,105 @@ +/* + * 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.name; + +import javax.jcr.NamespaceException; + +/** + * Name resolver that parsers and formats prefixed JCR names. + * A {@link NamespaceResolver} is used for resolving the namespace prefixes. + */ +public class ParsingNameResolver implements NameResolver { + + /** + * Namespace resolver. + */ + private final NamespaceResolver resolver; + + /** + * Creates a parsing name resolver. + * + * @param resolver namespace resolver + */ + public ParsingNameResolver(NamespaceResolver resolver) { + this.resolver = resolver; + } + + //--------------------------------------------------------< NameResolver > + + /** + * Parses the prefixed JCR name and returns the resolved qualified name. + * + * @param name prefixed JCR name + * @return qualified name + * @throws NameException if the JCR name format is invalid + * @throws NamespaceException if the namespace prefix can not be resolved + */ + public QName getQName(String name) + throws NameException, NamespaceException { + // Check for invalid names + if (name == null || ".".equals(name) || "..".equals(name)) { + throw new IllegalNameException("Illegal name: " + name); + } + + // Find the prefix colon, and check for an empty name + int colon = name.indexOf(':'); + int length = name.length(); + if (colon + 1 == length) { + throw new IllegalNameException("Empty local name: " + name); + } + + // Check for invalid characters within the local part of the name + for (int i = colon + 1; i < length; i++) { + char ch = name.charAt(i); + if (ch == '/' || ch == ':' || ch == '[' || ch == ']' + || ch == '*' || ch == '\'' || ch == '"' || ch == '|' + || (Character.isSpaceChar(ch) + && !(ch == ' ' && i > colon + 1 && i < length - 1))) { + throw new IllegalNameException( + "Invalid character in name: " + name); + } + } + + // Construct and return the QName + if (colon == -1) { + return new QName(QName.NS_DEFAULT_URI, name); + } else { + String uri = resolver.getURI(name.substring(0, colon)); + return new QName(uri, name.substring(colon + 1)); + } + } + + /** + * Returns the prefixed JCR name for the given qualified name. + * If the name is in the default namespace, then the local name + * is returned without a prefix. Otherwise the prefix for the + * namespace is resolved and used to construct returned the JCR name. + * + * @param name qualified name + * @return prefixed JCR name + * @throws NamespaceException if the namespace URI can not be resolved + */ + public String getJCRName(QName name) throws NamespaceException { + String uri = name.getNamespaceURI(); + if (QName.NS_DEFAULT_URI.equals(uri)) { + return name.getLocalName(); + } else { + return resolver.getPrefix(uri) + ":" + name.getLocalName(); + } + } + +} Property changes on: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/ParsingNameResolver.java ___________________________________________________________________ Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java (revision 493464) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java (working copy) @@ -21,7 +21,11 @@ import org.apache.jackrabbit.core.fs.FileSystem; import org.apache.jackrabbit.core.fs.FileSystemResource; import org.apache.jackrabbit.name.AbstractNamespaceResolver; +import org.apache.jackrabbit.name.CachingNameResolver; import org.apache.jackrabbit.name.NameCache; +import org.apache.jackrabbit.name.NameException; +import org.apache.jackrabbit.name.NameResolver; +import org.apache.jackrabbit.name.ParsingNameResolver; import org.apache.jackrabbit.name.QName; import org.apache.jackrabbit.util.XMLChar; import org.slf4j.Logger; @@ -83,7 +87,7 @@ private int lastIndex = 0; - private final CachingNamespaceResolver resolver; + private NameResolver resolver; private final FileSystem nsRegStore; @@ -102,7 +106,7 @@ throws RepositoryException { super(true); // enable listener support this.nsRegStore = nsRegStore; - resolver = new CachingNamespaceResolver(this, 1000); + resolver = new CachingNameResolver(new ParsingNameResolver(this)); load(); } @@ -383,7 +387,7 @@ } return uri; } - + //----------------------------------------------------< NamespaceRegistry > /** * {@inheritDoc} @@ -524,23 +528,28 @@ * {@inheritDoc} */ public QName retrieveName(String jcrName) { - // just delegate to internal cache - return resolver.retrieveName(jcrName); + try { + return resolver.getQName(jcrName); + } catch (NameException e) { + return null; + } catch (NamespaceException e) { + return null; + } } public String retrieveName(QName name) { - // just delegate to internal cache - return resolver.retrieveName(name); + try { + return resolver.getJCRName(name); + } catch (NamespaceException e) { + return null; + } } public void cacheName(String jcrName, QName name) { - // just delegate to internal cache - resolver.cacheName(jcrName, name); } public void evictAllNames() { - // just delegate to internal cache - resolver.evictAllNames(); + resolver = new CachingNameResolver(new ParsingNameResolver(this)); } //-----------------------------------------------< NamespaceEventListener > Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingNamespaceResolver.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingNamespaceResolver.java (revision 493464) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingNamespaceResolver.java (working copy) @@ -1,164 +0,0 @@ -/* - * 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; - -import org.apache.commons.collections.map.LRUMap; -import org.apache.jackrabbit.name.AbstractNamespaceResolver; -import org.apache.jackrabbit.name.IllegalNameException; -import org.apache.jackrabbit.name.NameFormat; -import org.apache.jackrabbit.name.NamespaceListener; -import org.apache.jackrabbit.name.NamespaceResolver; -import org.apache.jackrabbit.name.NoPrefixDeclaredException; -import org.apache.jackrabbit.name.QName; -import org.apache.jackrabbit.name.NameCache; -import org.apache.jackrabbit.name.UnknownPrefixException; - -import javax.jcr.NamespaceException; -import java.util.Map; - -/** - * Implements a {@link NamespaceResolver} that caches QName to resolved jcr names - * and vice versa. The cache is invalidated when a namespace uri to prefix - * mapping is changed. - */ -class CachingNamespaceResolver - implements NamespaceResolver, NamespaceListener, NameCache { - - /** - * The base namespace resolver. - */ - private final AbstractNamespaceResolver base; - - /** - * Maps QName instances to resolved jcr name Strings. - */ - private final Map qnameToJCRName; - - /** - * Maps resolved jcr name Strings to QName instances. - */ - private final Map jcrNameToQName; - - /** - * Creates a new CachingNamespaceResolver. - * - * @param base a base namespace resolver with support for listener - * registration. - * @param cacheSize number of mappings this resolver may cache. - */ - public CachingNamespaceResolver(AbstractNamespaceResolver base, int cacheSize) { - this.base = base; - qnameToJCRName = new LRUMap(cacheSize); - jcrNameToQName = new LRUMap(cacheSize); - this.base.addListener(this); - } - - /** - * @inheritDoc - */ - public String getURI(String prefix) throws NamespaceException { - return base.getURI(prefix); - } - - /** - * @inheritDoc - */ - public String getPrefix(String uri) throws NamespaceException { - return base.getPrefix(uri); - } - - /** - * @deprecated use {@link NameFormat#parse(String, NamespaceResolver)} - */ - public QName getQName(String name) - throws IllegalNameException, UnknownPrefixException { - return NameFormat.parse(name, this); - } - - /** - * @deprecated use {@link NameFormat#format(QName, NamespaceResolver)} - */ - public String getJCRName(QName name) - throws NoPrefixDeclaredException { - return NameFormat.format(name, this); - } - - /** - * Disposes this CachingNamespaceResolver. - */ - public void dispose() { - base.removeListener(this); - } - - //------------------------------------------------------------< NameCache > - - /** - * @inheritDoc - */ - public synchronized QName retrieveName(String jcrName) { - return (QName) jcrNameToQName.get(jcrName); - } - - /** - * @inheritDoc - */ - public synchronized String retrieveName(QName name) { - return (String) qnameToJCRName.get(name); - } - - /** - * @inheritDoc - */ - public synchronized void cacheName(String jcrName, QName name) { - qnameToJCRName.put(name, jcrName); - jcrNameToQName.put(jcrName, name); - } - - /** - * @inheritDoc - */ - public synchronized void evictAllNames() { - qnameToJCRName.clear(); - jcrNameToQName.clear(); - } - - //----------------------------------------------------< NamespaceListener > - - /** - * @inheritDoc - */ - public void namespaceAdded(String prefix, String uri) { - // since it is a new namespace there's no need to flush the - // cached mappings - } - - /** - * @inheritDoc - * Invalidates all cached mappings. - */ - public void namespaceRemapped(String oldPrefix, String newPrefix, String uri) { - evictAllNames(); - } - - /** - * @inheritDoc - * Invalidates all cached mappings. - */ - public void namespaceRemoved(String uri) { - evictAllNames(); - } -}