001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 */
019package org.nuxeo.ecm.automation.core.impl;
020
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.concurrent.ConcurrentHashMap;
025import java.util.concurrent.ConcurrentMap;
026
027/**
028 * A registry which is inheriting values from super keys. The super key relation is defined by the derived classes by
029 * overriding {@link #getSuperKeys(Object)} method. The registry is thread safe and is optimized for lookups. A
030 * concurrent cache is dynamically updated when a value is retrieved from a super entry. The cache is removed each time
031 * a modification is made on the registry using {@link #put(Object, Object)} or {@link #remove(Object)} methods. Thus,
032 * for maximum performance you need to avoid modifying the registry after lookups were done: at application startup
033 * build the registry, at runtime perform lookups, at shutdown remove entries. The root key is passed in the constructor
034 * and is used to stop looking in super entries.
035 *
036 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
037 */
038public abstract class SuperKeyedRegistry<K, V> {
039
040    private static final Object NULL = new Object();
041
042    protected Map<K, V> registry;
043
044    /**
045     * the cache map used for lookups. Object is used for the value to be able to insert NULL values.
046     */
047    protected volatile ConcurrentMap<K, Object> lookup;
048
049    /**
050     * the lock used to update the registry
051     */
052    private final Object lock = new Object();
053
054    public SuperKeyedRegistry() {
055        registry = new HashMap<K, V>();
056    }
057
058    public void put(K key, V value) {
059        synchronized (lock) {
060            registry.put(key, value);
061            lookup = null;
062        }
063    }
064
065    public V remove(K key) {
066        V value;
067        synchronized (lock) {
068            value = registry.remove(key);
069            lookup = null;
070        }
071        return value;
072    }
073
074    public void flushCache() {
075        synchronized (lock) {
076            lookup = null;
077        }
078    }
079
080    protected abstract boolean isRoot(K key);
081
082    protected abstract List<K> getSuperKeys(K key);
083
084    /**
085     * Override this in order to disable caching some specific keys. For example when using java classes as keys you may
086     * want to avoid caching proxy classes. The default is to return true. (cache is enabled)
087     */
088    protected boolean isCachingEnabled(K key) {
089        return true;
090    }
091
092    @SuppressWarnings("unchecked")
093    public V get(K key) {
094        Map<K, Object> _lookup = lookup;
095        if (_lookup == null) {
096            synchronized (lock) {
097                lookup = new ConcurrentHashMap<K, Object>(registry);
098                _lookup = lookup;
099            }
100        }
101        Object v = _lookup.get(key);
102        if (v == null && !isRoot(key)) {
103            // System.out.println("cache missed: "+key);
104            for (K sk : getSuperKeys(key)) {
105                v = get(sk);
106                if (v != null && v != NULL) {
107                    // we found what we need so abort scanning interfaces /
108                    // subclasses
109                    if (isCachingEnabled(sk)) {
110                        _lookup.put(key, v); // update cache
111                        return (V) v;
112                    }
113                } else {
114                    if (isCachingEnabled(sk)) {
115                        if (v != null) { // add inherited binding
116                            _lookup.put(key, v);
117                        } else {
118                            _lookup.put(key, NULL);
119                        }
120                    }
121                }
122            }
123        }
124        return (V) (v == NULL ? null : v);
125    }
126
127}