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 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 } 082 083 protected boolean isCacheEnabled() { 084 return (entryCacheName != null && entryCacheWithoutReferencesName != null); 085 } 086 087 public DocumentModel getEntry(String entryId, EntrySource source) throws DirectoryException { 088 return getEntry(entryId, source, true); 089 } 090 091 public DocumentModel getEntry(String entryId, EntrySource source, boolean fetchReferences) 092 throws DirectoryException { 093 if (!isCacheEnabled()) { 094 return source.getEntryFromSource(entryId, fetchReferences); 095 } else if (isCacheEnabled() && (getEntryCache() == null || getEntryCacheWithoutReferences() == null)) { 096 097 log.warn("Your directory configuration for cache is wrong, directory cache will not be used."); 098 if (getEntryCache() == null) { 099 log.warn(String.format( 100 "The cache for entry '%s' has not been found, please check the cache name or make sure you have deployed it", 101 entryCacheName)); 102 } 103 if (getEntryCacheWithoutReferences() == null) { 104 log.warn(String.format( 105 "The cache for entry without references '%s' has not been found, please check the cache name or make sure you have deployed it", 106 entryCacheWithoutReferencesName)); 107 } 108 109 return source.getEntryFromSource(entryId, fetchReferences); 110 } 111 112 Cache cache = fetchReferences ? getEntryCache() : getEntryCacheWithoutReferences(); 113 Serializable entry = cache.get(entryId); 114 if (CACHE_MISS.equals(entry)) { 115 negativeHitsCounter.inc(); 116 return null; 117 } 118 DocumentModel dm = (DocumentModel) entry; 119 if (dm == null) { 120 // fetch the entry from the backend and cache it for later reuse 121 dm = source.getEntryFromSource(entryId, fetchReferences); 122 if (dm != null) { 123 cache.put(entryId, dm); 124 if (fetchReferences) { 125 sizeCounter.inc(); 126 } 127 } else if (negativeCaching) { 128 cache.put(entryId, CACHE_MISS); 129 } 130 missesCounter.inc(); 131 } else { 132 hitsCounter.inc(); 133 } 134 try { 135 if (dm == null) { 136 return null; 137 } 138 DocumentModel clone = dm.clone(); 139 // DocumentModelImpl#clone does not copy context data, hence 140 // propagate the read-only flag manually 141 if (BaseSession.isReadOnlyEntry(dm)) { 142 BaseSession.setReadOnlyEntry(clone); 143 } 144 return clone; 145 } catch (CloneNotSupportedException e) { 146 // will never happen as long a DocumentModelImpl is used 147 return dm; 148 } 149 } 150 151 public void invalidate(List<String> entryIds) { 152 if (isCacheEnabled()) { 153 synchronized (this) { 154 for (String entryId : entryIds) { 155 getEntryCache().invalidate(entryId); 156 getEntryCacheWithoutReferences().invalidate(entryId); 157 sizeCounter.dec(); 158 invalidationsCounter.inc(); 159 } 160 } 161 } 162 } 163 164 public void invalidate(String... entryIds) { 165 invalidate(Arrays.asList(entryIds)); 166 } 167 168 public void invalidateAll() { 169 if (isCacheEnabled()) { 170 synchronized (this) { 171 long count = sizeCounter.getCount(); 172 sizeCounter.dec(count); 173 invalidationsCounter.inc(count); 174 getEntryCache().invalidateAll(); 175 getEntryCacheWithoutReferences().invalidateAll(); 176 } 177 } 178 } 179 180 public void setEntryCacheName(String entryCacheName) { 181 this.entryCacheName = entryCacheName; 182 } 183 184 public void setEntryCacheWithoutReferencesName(String entryCacheWithoutReferencesName) { 185 this.entryCacheWithoutReferencesName = entryCacheWithoutReferencesName; 186 } 187 188 public void setNegativeCaching(Boolean negativeCaching) { 189 this.negativeCaching = Boolean.TRUE.equals(negativeCaching); 190 } 191 192 public Cache getEntryCache() { 193 if (entryCache == null) { 194 entryCache = Framework.getService(CacheService.class).getCache(entryCacheName); 195 } 196 return entryCache; 197 } 198 199 public Cache getEntryCacheWithoutReferences() { 200 201 if (entryCacheWithoutReferences == null) { 202 entryCacheWithoutReferences = Framework.getService(CacheService.class).getCache( 203 entryCacheWithoutReferencesName); 204 } 205 return entryCacheWithoutReferences; 206 } 207 208}