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