/*
* (C) Copyright 2007-2014 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Maxime Hilaire
*/
package org.nuxeo.ecm.directory;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.cache.Cache;
import org.nuxeo.ecm.core.cache.CacheService;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.metrics.MetricsService;
import com.codahale.metrics.Counter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.SharedMetricRegistries;
/**
* Very simple cache system to cache directory entry lookups (not search queries) on top of nuxeo cache
* <p>
* Beware that this cache is not transaction aware (which is not a problem for LDAP directories anyway).
*/
public class DirectoryCache {
private static final Serializable CACHE_MISS = Boolean.FALSE;
protected final String name;
protected Cache entryCache;
protected String entryCacheName = null;
protected Cache entryCacheWithoutReferences;
protected String entryCacheWithoutReferencesName = null;
protected boolean negativeCaching;
protected final MetricRegistry metrics = SharedMetricRegistries.getOrCreate(MetricsService.class.getName());
protected final Counter hitsCounter;
protected final Counter negativeHitsCounter;
protected final Counter missesCounter;
protected final Counter invalidationsCounter;
protected final Counter sizeCounter;
private final static Log log = LogFactory.getLog(DirectoryCache.class);
protected DirectoryCache(String name) {
this.name = name;
hitsCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache", "hits"));
negativeHitsCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache", "neghits"));
missesCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache", "misses"));
invalidationsCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache",
"invalidations"));
sizeCounter = metrics.counter(MetricRegistry.name("nuxeo", "directories", name, "cache", "size"));
}
protected boolean isCacheEnabled() {
return (entryCacheName != null && entryCacheWithoutReferencesName != null);
}
public DocumentModel getEntry(String entryId, EntrySource source) throws DirectoryException {
return getEntry(entryId, source, true);
}
public DocumentModel getEntry(String entryId, EntrySource source, boolean fetchReferences)
throws DirectoryException {
if (!isCacheEnabled()) {
return source.getEntryFromSource(entryId, fetchReferences);
} else if (isCacheEnabled() && (getEntryCache() == null || getEntryCacheWithoutReferences() == null)) {
log.warn("Your directory configuration for cache is wrong, directory cache will not be used.");
if (getEntryCache() == null) {
log.warn(String.format(
"The cache for entry '%s' has not been found, please check the cache name or make sure you have deployed it",
entryCacheName));
}
if (getEntryCacheWithoutReferences() == null) {
log.warn(String.format(
"The cache for entry without references '%s' has not been found, please check the cache name or make sure you have deployed it",
entryCacheWithoutReferencesName));
}
return source.getEntryFromSource(entryId, fetchReferences);
}
Cache cache = fetchReferences ? getEntryCache() : getEntryCacheWithoutReferences();
Serializable entry = cache.get(entryId);
if (CACHE_MISS.equals(entry)) {
negativeHitsCounter.inc();
return null;
}
DocumentModel dm = (DocumentModel) entry;
if (dm == null) {
// fetch the entry from the backend and cache it for later reuse
dm = source.getEntryFromSource(entryId, fetchReferences);
if (dm != null) {
cache.put(entryId, dm);
if (fetchReferences) {
sizeCounter.inc();
}
} else if (negativeCaching) {
cache.put(entryId, CACHE_MISS);
}
missesCounter.inc();
} else {
hitsCounter.inc();
}
try {
if (dm == null) {
return null;
}
DocumentModel clone = dm.clone();
// DocumentModelImpl#clone does not copy context data, hence
// propagate the read-only flag manually
if (BaseSession.isReadOnlyEntry(dm)) {
BaseSession.setReadOnlyEntry(clone);
}
return clone;
} catch (CloneNotSupportedException e) {
// will never happen as long a DocumentModelImpl is used
return dm;
}
}
public void invalidate(List<String> entryIds) {
if (isCacheEnabled()) {
synchronized (this) {
for (String entryId : entryIds) {
getEntryCache().invalidate(entryId);
getEntryCacheWithoutReferences().invalidate(entryId);
sizeCounter.dec();
invalidationsCounter.inc();
}
}
}
}
public void invalidate(String... entryIds) {
invalidate(Arrays.asList(entryIds));
}
public void invalidateAll() {
if (isCacheEnabled()) {
synchronized (this) {
long count = sizeCounter.getCount();
sizeCounter.dec(count);
invalidationsCounter.inc(count);
getEntryCache().invalidateAll();
getEntryCacheWithoutReferences().invalidateAll();
}
}
}
public void setEntryCacheName(String entryCacheName) {
this.entryCacheName = entryCacheName;
}
public void setEntryCacheWithoutReferencesName(String entryCacheWithoutReferencesName) {
this.entryCacheWithoutReferencesName = entryCacheWithoutReferencesName;
}
public void setNegativeCaching(Boolean negativeCaching) {
this.negativeCaching = Boolean.TRUE.equals(negativeCaching);
}
public Cache getEntryCache() {
if (entryCache == null) {
entryCache = Framework.getService(CacheService.class).getCache(entryCacheName);
}
return entryCache;
}
public Cache getEntryCacheWithoutReferences() {
if (entryCacheWithoutReferences == null) {
entryCacheWithoutReferences = Framework.getService(CacheService.class).getCache(
entryCacheWithoutReferencesName);
}
return entryCacheWithoutReferences;
}
}