001/* 002 * (C) Copyright 2007-2014 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Maxime Hilaire 016 */ 017 018package org.nuxeo.ecm.directory; 019 020import java.io.Serializable; 021import java.util.Arrays; 022import java.util.List; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.nuxeo.ecm.core.api.DocumentModel; 027import org.nuxeo.ecm.core.cache.Cache; 028import org.nuxeo.ecm.core.cache.CacheService; 029import org.nuxeo.runtime.api.Framework; 030import org.nuxeo.runtime.metrics.MetricsService; 031 032import com.codahale.metrics.Counter; 033import com.codahale.metrics.MetricRegistry; 034import com.codahale.metrics.SharedMetricRegistries; 035 036/** 037 * Very simple cache system to cache directory entry lookups (not search queries) on top of nuxeo cache 038 * <p> 039 * Beware that this cache is not transaction aware (which is not a problem for LDAP directories anyway). 040 */ 041public class DirectoryCache { 042 043 private static final Serializable CACHE_MISS = Boolean.FALSE; 044 045 protected final String name; 046 047 protected Cache entryCache; 048 049 protected String entryCacheName = null; 050 051 protected Cache entryCacheWithoutReferences; 052 053 protected String entryCacheWithoutReferencesName = null; 054 055 protected boolean negativeCaching; 056 057 protected final MetricRegistry metrics = SharedMetricRegistries.getOrCreate(MetricsService.class.getName()); 058 059 protected final Counter hitsCounter; 060 061 protected final Counter negativeHitsCounter; 062 063 protected final Counter missesCounter; 064 065 protected final Counter invalidationsCounter; 066 067 protected final Counter maxCounter; 068 069 protected final Counter sizeCounter; 070 071 private final static Log log = LogFactory.getLog(DirectoryCache.class); 072 073 protected DirectoryCache(String name) { 074 this.name = name; 075 hitsCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache", "hits")); 076 negativeHitsCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache", "neghits")); 077 missesCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache", "misses")); 078 invalidationsCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache", 079 "invalidations")); 080 sizeCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache", "size")); 081 maxCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache", "max")); 082 } 083 084 protected boolean isCacheEnabled() { 085 return (entryCacheName != null && entryCacheWithoutReferencesName != null); 086 } 087 088 public DocumentModel getEntry(String entryId, EntrySource source) throws DirectoryException { 089 return getEntry(entryId, source, true); 090 } 091 092 public DocumentModel getEntry(String entryId, EntrySource source, boolean fetchReferences) 093 throws DirectoryException { 094 if (!isCacheEnabled()) { 095 return source.getEntryFromSource(entryId, fetchReferences); 096 } else if (isCacheEnabled() && (getEntryCache() == null || getEntryCacheWithoutReferences() == null)) { 097 098 log.warn("Your directory configuration for cache is wrong, directory cache will not be used."); 099 if (getEntryCache() == null) { 100 log.warn(String.format( 101 "The cache for entry '%s' has not been found, please check the cache name or make sure you have deployed it", 102 entryCacheName)); 103 } 104 if (getEntryCacheWithoutReferences() == null) { 105 log.warn(String.format( 106 "The cache for entry without references '%s' has not been found, please check the cache name or make sure you have deployed it", 107 entryCacheWithoutReferencesName)); 108 } 109 110 return source.getEntryFromSource(entryId, fetchReferences); 111 } 112 113 Cache cache = fetchReferences ? getEntryCache() : getEntryCacheWithoutReferences(); 114 Serializable entry = cache.get(entryId); 115 if (CACHE_MISS.equals(entry)) { 116 negativeHitsCounter.inc(); 117 return null; 118 } 119 DocumentModel dm = (DocumentModel) entry; 120 if (dm == null) { 121 // fetch the entry from the backend and cache it for later reuse 122 dm = source.getEntryFromSource(entryId, fetchReferences); 123 if (dm != null) { 124 cache.put(entryId, dm); 125 if (fetchReferences) { 126 sizeCounter.inc(); 127 } 128 } else if (negativeCaching) { 129 cache.put(entryId, CACHE_MISS); 130 } 131 missesCounter.inc(); 132 } else { 133 hitsCounter.inc(); 134 } 135 try { 136 if (dm == null) { 137 return null; 138 } 139 DocumentModel clone = dm.clone(); 140 // DocumentModelImpl#clone does not copy context data, hence 141 // propagate the read-only flag manually 142 if (BaseSession.isReadOnlyEntry(dm)) { 143 BaseSession.setReadOnlyEntry(clone); 144 } 145 return clone; 146 } catch (CloneNotSupportedException e) { 147 // will never happen as long a DocumentModelImpl is used 148 return dm; 149 } 150 } 151 152 public void invalidate(List<String> entryIds) { 153 if (isCacheEnabled()) { 154 synchronized (this) { 155 for (String entryId : entryIds) { 156 getEntryCache().invalidate(entryId); 157 getEntryCacheWithoutReferences().invalidate(entryId); 158 sizeCounter.dec(); 159 invalidationsCounter.inc(); 160 } 161 } 162 } 163 } 164 165 public void invalidate(String... entryIds) { 166 invalidate(Arrays.asList(entryIds)); 167 } 168 169 public void invalidateAll() { 170 if (isCacheEnabled()) { 171 synchronized (this) { 172 long count = sizeCounter.getCount(); 173 sizeCounter.dec(count); 174 invalidationsCounter.inc(count); 175 getEntryCache().invalidateAll(); 176 getEntryCacheWithoutReferences().invalidateAll(); 177 } 178 } 179 } 180 181 public void setEntryCacheName(String entryCacheName) { 182 this.entryCacheName = entryCacheName; 183 } 184 185 public void setEntryCacheWithoutReferencesName(String entryCacheWithoutReferencesName) { 186 this.entryCacheWithoutReferencesName = entryCacheWithoutReferencesName; 187 } 188 189 public void setNegativeCaching(Boolean negativeCaching) { 190 this.negativeCaching = Boolean.TRUE.equals(negativeCaching); 191 } 192 193 public Cache getEntryCache() { 194 if (entryCache == null) { 195 entryCache = Framework.getService(CacheService.class).getCache(entryCacheName); 196 } 197 return entryCache; 198 } 199 200 public Cache getEntryCacheWithoutReferences() { 201 202 if (entryCacheWithoutReferences == null) { 203 entryCacheWithoutReferences = Framework.getService(CacheService.class).getCache( 204 entryCacheWithoutReferencesName); 205 } 206 return entryCacheWithoutReferences; 207 } 208 209}