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}