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