/*
* (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed 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.
*
* Contributors:
* bstefanescu
*/
package org.nuxeo.ecm.automation.core.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* A registry which is inheriting values from super keys. The super key relation is defined by the derived classes by
* overriding {@link #getSuperKeys(Object)} method. The registry is thread safe and is optimized for lookups. A
* concurrent cache is dynamically updated when a value is retrieved from a super entry. The cache is removed each time
* a modification is made on the registry using {@link #put(Object, Object)} or {@link #remove(Object)} methods. Thus,
* for maximum performance you need to avoid modifying the registry after lookups were done: at application startup
* build the registry, at runtime perform lookups, at shutdown remove entries. The root key is passed in the constructor
* and is used to stop looking in super entries.
*
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public abstract class SuperKeyedRegistry<K, V> {
private static final Object NULL = new Object();
protected Map<K, V> registry;
/**
* the cache map used for lookups. Object is used for the value to be able to insert NULL values.
*/
protected volatile ConcurrentMap<K, Object> lookup;
/**
* the lock used to update the registry
*/
private final Object lock = new Object();
public SuperKeyedRegistry() {
registry = new HashMap<K, V>();
}
public void put(K key, V value) {
synchronized (lock) {
registry.put(key, value);
lookup = null;
}
}
public V remove(K key) {
V value;
synchronized (lock) {
value = registry.remove(key);
lookup = null;
}
return value;
}
public void flushCache() {
synchronized (lock) {
lookup = null;
}
}
protected abstract boolean isRoot(K key);
protected abstract List<K> getSuperKeys(K key);
/**
* Override this in order to disable caching some specific keys. For example when using java classes as keys you may
* want to avoid caching proxy classes. The default is to return true. (cache is enabled)
*/
protected boolean isCachingEnabled(K key) {
return true;
}
@SuppressWarnings("unchecked")
public V get(K key) {
Map<K, Object> _lookup = lookup;
if (_lookup == null) {
synchronized (lock) {
lookup = new ConcurrentHashMap<K, Object>(registry);
_lookup = lookup;
}
}
Object v = _lookup.get(key);
if (v == null && !isRoot(key)) {
// System.out.println("cache missed: "+key);
for (K sk : getSuperKeys(key)) {
v = get(sk);
if (v != null && v != NULL) {
// we found what we need so abort scanning interfaces /
// subclasses
if (isCachingEnabled(sk)) {
_lookup.put(key, v); // update cache
return (V) v;
}
} else {
if (isCachingEnabled(sk)) {
if (v != null) { // add inherited binding
_lookup.put(key, v);
} else {
_lookup.put(key, NULL);
}
}
}
}
}
return (V) (v == NULL ? null : v);
}
}