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}