Index: CachingNamespaceResolver.java =================================================================== --- CachingNamespaceResolver.java (revision 489007) +++ CachingNamespaceResolver.java (working copy) @@ -30,6 +30,10 @@ import javax.jcr.NamespaceException; import java.util.Map; +import EDU.oswego.cs.dl.util.concurrent.Sync; +import EDU.oswego.cs.dl.util.concurrent.WriterPreferenceReadWriteLock; +import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock; + /** * Implements a {@link NamespaceResolver} that caches QName to resolved jcr names * and vice versa. The cache is invalidated when a namespace uri to prefix @@ -54,6 +58,11 @@ private final Map jcrNameToQName; /** + * ReadWriteLock to synchronize access to the cache maps. + */ + private final ReadWriteLock rwLock = new WriterPreferenceReadWriteLock(); + + /** * Creates a new CachingNamespaceResolver. * * @param base a base namespace resolver with support for listener @@ -62,8 +71,8 @@ */ public CachingNamespaceResolver(AbstractNamespaceResolver base, int cacheSize) { this.base = base; - qnameToJCRName = new LRUMap(cacheSize); - jcrNameToQName = new LRUMap(cacheSize); + qnameToJCRName = new ConcurrentLRUMap(cacheSize); + jcrNameToQName = new ConcurrentLRUMap(cacheSize); this.base.addListener(this); } @@ -109,31 +118,71 @@ /** * @inheritDoc */ - public synchronized QName retrieveName(String jcrName) { - return (QName) jcrNameToQName.get(jcrName); + public QName retrieveName(String jcrName) { + Sync readLock = rwLock.readLock(); + try { + readLock.acquire(); + } catch (InterruptedException e) { + return null; + } + try { + return (QName) jcrNameToQName.get(jcrName); + } finally { + readLock.release(); + } } /** * @inheritDoc */ - public synchronized String retrieveName(QName name) { - return (String) qnameToJCRName.get(name); + public String retrieveName(QName name) { + Sync readLock = rwLock.readLock(); + try { + readLock.acquire(); + } catch (InterruptedException e) { + return null; + } + try { + return (String) qnameToJCRName.get(name); + } finally { + readLock.release(); + } } /** * @inheritDoc */ - public synchronized void cacheName(String jcrName, QName name) { - qnameToJCRName.put(name, jcrName); - jcrNameToQName.put(jcrName, name); + public void cacheName(String jcrName, QName name) { + Sync writeLock = rwLock.writeLock(); + try { + writeLock.acquire(); + } catch (InterruptedException e) { + return; + } + try { + qnameToJCRName.put(name, jcrName); + jcrNameToQName.put(jcrName, name); + } finally { + writeLock.release(); + } } /** * @inheritDoc */ - public synchronized void evictAllNames() { - qnameToJCRName.clear(); - jcrNameToQName.clear(); + public void evictAllNames() { + Sync writeLock = rwLock.writeLock(); + try { + writeLock.acquire(); + } catch (InterruptedException e) { + return; + } + try { + qnameToJCRName.clear(); + jcrNameToQName.clear(); + } finally { + writeLock.release(); + } } //----------------------------------------------------< NamespaceListener > @@ -161,4 +210,35 @@ public void namespaceRemoved(String uri) { evictAllNames(); } + + //---------------------------------------------------------- + + /** + * Extends the default Commons Collection LRUMap and synchronizes the method + * {@link #moveToMRU} which is the only method that modifies the internal + * data structures when a lookup is done on the LRUMap (e.g. a get()). This + * allows to use the read methods of this implementation from multiple + * threads concurrently with only little synchronization overhead. + */ + private static final class ConcurrentLRUMap extends LRUMap { + + /** + * Creates a new ConcurrentLRUMap with a given + * maxSize. + * + * @param maxSize the max size. + */ + public ConcurrentLRUMap(int maxSize) { + super(maxSize); + } + + /** + * The method of this implementation is synchronized. + * + * @param entry the link entry. + */ + protected synchronized void moveToMRU(LinkEntry entry) { + super.moveToMRU(entry); + } + } }