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.IOException; 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 protected final String name; 044 045 protected Cache entryCache; 046 047 protected String entryCacheName = null; 048 049 protected Cache entryCacheWithoutReferences; 050 051 protected String entryCacheWithoutReferencesName = null; 052 053 protected final MetricRegistry metrics = SharedMetricRegistries.getOrCreate(MetricsService.class.getName()); 054 055 protected final Counter hitsCounter; 056 057 protected final Counter invalidationsCounter; 058 059 protected final Counter maxCounter; 060 061 protected final Counter sizeCounter; 062 063 private final static Log log = LogFactory.getLog(DirectoryCache.class); 064 065 protected DirectoryCache(String name) { 066 this.name = name; 067 hitsCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache", "hits")); 068 invalidationsCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache", 069 "invalidations")); 070 sizeCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache", "size")); 071 maxCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache", "max")); 072 } 073 074 protected boolean isCacheEnabled() { 075 return (entryCacheName != null && entryCacheWithoutReferencesName != null); 076 } 077 078 public DocumentModel getEntry(String entryId, EntrySource source) throws DirectoryException { 079 return getEntry(entryId, source, true); 080 } 081 082 public DocumentModel getEntry(String entryId, EntrySource source, boolean fetchReferences) 083 throws DirectoryException { 084 if (!isCacheEnabled()) { 085 return source.getEntryFromSource(entryId, fetchReferences); 086 } else if (isCacheEnabled() && (getEntryCache() == null || getEntryCacheWithoutReferences() == null)) { 087 088 log.warn("Your directory configuration for cache is wrong, directory cache will not be used."); 089 if (getEntryCache() == null) { 090 log.warn(String.format( 091 "The cache for entry '%s' has not been found, please check the cache name or make sure you have deployed it", 092 entryCacheName)); 093 } 094 if (getEntryCacheWithoutReferences() == null) { 095 log.warn(String.format( 096 "The cache for entry without references '%s' has not been found, please check the cache name or make sure you have deployed it", 097 entryCacheWithoutReferencesName)); 098 } 099 100 return source.getEntryFromSource(entryId, fetchReferences); 101 } 102 103 DocumentModel dm = null; 104 if (fetchReferences) { 105 dm = (DocumentModel) getEntryCache().get(entryId); 106 if (dm == null) { 107 // fetch the entry from the backend and cache it for later 108 // reuse 109 dm = source.getEntryFromSource(entryId, fetchReferences); 110 if (dm != null) { 111 getEntryCache().put(entryId, dm); 112 sizeCounter.inc(); 113 } 114 } else { 115 hitsCounter.inc(); 116 } 117 } else { 118 dm = (DocumentModel) getEntryCacheWithoutReferences().get(entryId); 119 if (dm == null) { 120 // fetch the entry from the backend and cache it for later 121 // reuse 122 dm = source.getEntryFromSource(entryId, fetchReferences); 123 if (dm != null) { 124 getEntryCacheWithoutReferences().put(entryId, dm); 125 } 126 } else { 127 hitsCounter.inc(); 128 } 129 } 130 try { 131 if (dm == null) { 132 return null; 133 } 134 DocumentModel clone = dm.clone(); 135 // DocumentModelImpl#clone does not copy context data, hence 136 // propagate the read-only flag manually 137 if (BaseSession.isReadOnlyEntry(dm)) { 138 BaseSession.setReadOnlyEntry(clone); 139 } 140 return clone; 141 } catch (CloneNotSupportedException e) { 142 // will never happen as long a DocumentModelImpl is used 143 return dm; 144 } 145 } 146 147 public void invalidate(List<String> entryIds) { 148 if (isCacheEnabled()) { 149 synchronized (this) { 150 for (String entryId : entryIds) { 151 getEntryCache().invalidate(entryId); 152 getEntryCacheWithoutReferences().invalidate(entryId); 153 sizeCounter.dec(); 154 invalidationsCounter.inc(); 155 } 156 } 157 } 158 } 159 160 public void invalidate(String... entryIds) { 161 invalidate(Arrays.asList(entryIds)); 162 } 163 164 public void invalidateAll() { 165 if (isCacheEnabled()) { 166 synchronized (this) { 167 long count = sizeCounter.getCount(); 168 sizeCounter.dec(count); 169 invalidationsCounter.inc(count); 170 getEntryCache().invalidateAll(); 171 getEntryCacheWithoutReferences().invalidateAll(); 172 } 173 } 174 } 175 176 public void setEntryCacheName(String entryCacheName) { 177 this.entryCacheName = entryCacheName; 178 } 179 180 public void setEntryCacheWithoutReferencesName(String entryCacheWithoutReferencesName) { 181 this.entryCacheWithoutReferencesName = entryCacheWithoutReferencesName; 182 } 183 184 public Cache getEntryCache() { 185 if (entryCache == null) { 186 entryCache = Framework.getService(CacheService.class).getCache(entryCacheName); 187 } 188 return entryCache; 189 } 190 191 public Cache getEntryCacheWithoutReferences() { 192 193 if (entryCacheWithoutReferences == null) { 194 entryCacheWithoutReferences = Framework.getService(CacheService.class).getCache( 195 entryCacheWithoutReferencesName); 196 } 197 return entryCacheWithoutReferences; 198 } 199 200}