/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.jackrabbit.core.persistence.bundle;
import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_MIXINTYPES;
import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PRIMARYTYPE;
import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_UUID;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import org.apache.jackrabbit.api.stats.RepositoryStatistics;
import org.apache.jackrabbit.core.cache.Cache;
import org.apache.jackrabbit.core.cache.CacheAccessListener;
import org.apache.jackrabbit.core.cache.ConcurrentCache;
import org.apache.jackrabbit.core.cluster.UpdateEventChannel;
import org.apache.jackrabbit.core.fs.FileSystem;
import org.apache.jackrabbit.core.fs.FileSystemResource;
import org.apache.jackrabbit.core.id.ItemId;
import org.apache.jackrabbit.core.id.NodeId;
import org.apache.jackrabbit.core.id.PropertyId;
import org.apache.jackrabbit.core.persistence.CachingPersistenceManager;
import org.apache.jackrabbit.core.persistence.IterablePersistenceManager;
import org.apache.jackrabbit.core.persistence.PMContext;
import org.apache.jackrabbit.core.persistence.PersistenceManager;
import org.apache.jackrabbit.core.persistence.check.ConsistencyCheckListener;
import org.apache.jackrabbit.core.persistence.check.ConsistencyChecker;
import org.apache.jackrabbit.core.persistence.check.ConsistencyReport;
import org.apache.jackrabbit.core.persistence.util.BLOBStore;
import org.apache.jackrabbit.core.persistence.util.FileBasedIndex;
import org.apache.jackrabbit.core.persistence.util.NodeInfo;
import org.apache.jackrabbit.core.persistence.util.NodePropBundle;
import org.apache.jackrabbit.core.persistence.util.NodePropBundle.PropertyEntry;
import org.apache.jackrabbit.core.state.ChangeLog;
import org.apache.jackrabbit.core.state.ItemState;
import org.apache.jackrabbit.core.state.ItemStateException;
import org.apache.jackrabbit.core.state.NoSuchItemStateException;
import org.apache.jackrabbit.core.state.NodeReferences;
import org.apache.jackrabbit.core.state.NodeState;
import org.apache.jackrabbit.core.state.PropertyState;
import org.apache.jackrabbit.core.stats.RepositoryStatisticsImpl;
import org.apache.jackrabbit.core.util.StringIndex;
import org.apache.jackrabbit.core.value.InternalValue;
import org.apache.jackrabbit.spi.Name;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The <code>AbstractBundlePersistenceManager</code> acts as base for all
* persistence managers that store the state in a {@link NodePropBundle}.
* <p/>
* The state and all property states of one node are stored together in one
* record. Property values of a certain size can be stored outside of the bundle.
* This currently only works for binary properties. NodeReferences are not
* included in the bundle since they are addressed by the target id.
* <p/>
* Some strings like namespaces and local names are additionally managed by
* separate indexes. only the index number is serialized to the records which
* reduces the amount of memory used.
* <p/>
* Special treatment is performed for the properties "jcr:uuid", "jcr:primaryType"
* and "jcr:mixinTypes". As they are also stored in the node state they are not
* included in the bundle but generated when required.
* <p/>
* In order to increase performance, there are two caches being maintained. One is the
* bundle cache that caches already loaded bundles. The other is the
* {@link LRUNodeIdCache} that caches non-existent bundles. This is useful
* because a lot of {@link #exists(NodeId)} calls are issued that would result
* in a useless persistence lookup if the desired bundle does not exist.
* <p/>
* Configuration:<br>
* <ul>
* <li><param name="{@link #setBundleCacheSize(String) bundleCacheSize}" value="8"/>
* </ul>
*/
public abstract class AbstractBundlePersistenceManager implements
PersistenceManager, CachingPersistenceManager, IterablePersistenceManager, CacheAccessListener, ConsistencyChecker {
/** the audit logger */
private static Logger auditLogger = LoggerFactory.getLogger("org.apache.jackrabbit.core.audit");
/** the default logger */
private static Logger log = LoggerFactory.getLogger(AbstractBundlePersistenceManager.class);
/** the prefix of a node file */
protected static final String NODEFILENAME = "n";
/** the prefix of a node references file */
protected static final String NODEREFSFILENAME = "r";
/** the name of the names-index resource */
protected static final String RES_NAME_INDEX = "/names.properties";
/** the name of the namespace-index resource */
protected static final String RES_NS_INDEX = "/namespaces.properties";
/** Sentinel instance used to mark a non-existent bundle in the cache */
private static final NodePropBundle MISSING =
new NodePropBundle(NodeId.randomId());
/**
* The size estimate for the MISSING NodePropBundle. The sum of:
* - ConcurrentCache.E: 32 bytes
* - LinkedHashMap.Entry: 64 bytes
* - NodeId: 32 bytes
*/
private static final long MISSING_SIZE_ESTIMATE = 128;
/** the index for namespaces */
private StringIndex nsIndex;
/** the index for local names */
private StringIndex nameIndex;
/** the cache of loaded bundles */
private ConcurrentCache<NodeId, NodePropBundle> bundles;
/** The default minimum stats logging interval (in ms). */
private static final int DEFAULT_LOG_STATS_INTERVAL = 60 * 1000;
/** The minimum interval time between stats are logged */
private long minLogStatsInterval = Long.getLong(
"org.apache.jackrabbit.cacheLogStatsInterval",
DEFAULT_LOG_STATS_INTERVAL);
/** The last time the cache stats were logged. */
private volatile long nextLogStats =
System.currentTimeMillis() + DEFAULT_LOG_STATS_INTERVAL;
/** the persistence manager context */
protected PMContext context;
/** default size of the bundle cache */
private long bundleCacheSize = 8 * 1024 * 1024;
/** Counter of read operations. */
private AtomicLong readCounter;
/** Counter of write operations. */
private AtomicLong writeCounter;
/** Duration of write operations. */
private AtomicLong writeDuration;
/** Counter of bundle cache accesses. */
private AtomicLong cacheAccessCounter;
/** Counter of bundle read operations. */
private AtomicLong cacheMissCounter;
/** Duration of bundle read operations. */
private AtomicLong cacheMissDuration;
/** Counter of bundle cache size. */
private AtomicLong cacheSizeCounter;
/** The update event channel to use by the consistency checker when fixing inconsistencies */
private UpdateEventChannel eventChannel;
/**
* Returns the size of the bundle cache in megabytes.
* @return the size of the bundle cache in megabytes.
*/
public String getBundleCacheSize() {
return String.valueOf(bundleCacheSize / (1024 * 1024));
}
/**
* Sets the size of the bundle cache in megabytes.
* the default is 8.
*
* @param bundleCacheSize the bundle cache size in megabytes.
*/
public void setBundleCacheSize(String bundleCacheSize) {
this.bundleCacheSize = Long.parseLong(bundleCacheSize) * 1024 * 1024;
}
/**
* Creates the folder path for the given node id that is suitable for
* storing states in a filesystem.
*
* @param buf buffer to append to or <code>null</code>
* @param id the id of the node
* @return the buffer with the appended data.
*/
protected StringBuffer buildNodeFolderPath(StringBuffer buf, NodeId id) {
if (buf == null) {
buf = new StringBuffer();
}
char[] chars = id.toString().toCharArray();
int cnt = 0;
for (int i = 0; i < chars.length; i++) {
if (chars[i] == '-') {
continue;
}
//if (cnt > 0 && cnt % 4 == 0) {
if (cnt == 2 || cnt == 4) {
buf.append(FileSystem.SEPARATOR_CHAR);
}
buf.append(chars[i]);
cnt++;
}
return buf;
}
/**
* Creates the folder path for the given property id that is suitable for
* storing states in a filesystem.
*
* @param buf buffer to append to or <code>null</code>
* @param id the id of the property
* @return the buffer with the appended data.
*/
protected StringBuffer buildPropFilePath(StringBuffer buf, PropertyId id) {
if (buf == null) {
buf = new StringBuffer();
}
buildNodeFolderPath(buf, id.getParentId());
buf.append(FileSystem.SEPARATOR);
buf.append(getNsIndex().stringToIndex(id.getName().getNamespaceURI()));
buf.append('.');
buf.append(getNameIndex().stringToIndex(id.getName().getLocalName()));
return buf;
}
/**
* Creates the file path for the given property id and value index that is
* suitable for storing property values in a filesystem.
*
* @param buf buffer to append to or <code>null</code>
* @param id the id of the property
* @param i the index of the property value
* @return the buffer with the appended data.
*/
protected StringBuffer buildBlobFilePath(StringBuffer buf, PropertyId id,
int i) {
if (buf == null) {
buf = new StringBuffer();
}
buildPropFilePath(buf, id);
buf.append('.');
buf.append(i);
return buf;
}
/**
* Creates the file path for the given node id that is
* suitable for storing node states in a filesystem.
*
* @param buf buffer to append to or <code>null</code>
* @param id the id of the node
* @return the buffer with the appended data.
*/
protected StringBuffer buildNodeFilePath(StringBuffer buf, NodeId id) {
if (buf == null) {
buf = new StringBuffer();
}
buildNodeFolderPath(buf, id);
buf.append(FileSystem.SEPARATOR);
buf.append(NODEFILENAME);
return buf;
}
/**
* Creates the file path for the given references id that is
* suitable for storing reference states in a filesystem.
*
* @param buf buffer to append to or <code>null</code>
* @param id the id of the node
* @return the buffer with the appended data.
*/
protected StringBuffer buildNodeReferencesFilePath(
StringBuffer buf, NodeId id) {
if (buf == null) {
buf = new StringBuffer();
}
buildNodeFolderPath(buf, id);
buf.append(FileSystem.SEPARATOR);
buf.append(NODEREFSFILENAME);
return buf;
}
/**
* Returns the namespace index
* @return the namespace index
* @throws IllegalStateException if an error occurs.
*/
public StringIndex getNsIndex() {
try {
if (nsIndex == null) {
// load name and ns index
FileSystemResource nsFile = new FileSystemResource(context.getFileSystem(), RES_NS_INDEX);
if (nsFile.exists()) {
nsIndex = new FileBasedIndex(nsFile);
} else {
nsIndex = (StringIndex) context.getNamespaceRegistry();
}
}
return nsIndex;
} catch (Exception e) {
IllegalStateException e2 = new IllegalStateException("Unable to create nsIndex.");
e2.initCause(e);
throw e2;
}
}
/**
* Returns the local name index
* @return the local name index
* @throws IllegalStateException if an error occurs.
*/
public StringIndex getNameIndex() {
try {
if (nameIndex == null) {
nameIndex = new FileBasedIndex(new FileSystemResource(
context.getFileSystem(), RES_NAME_INDEX));
}
return nameIndex;
} catch (Exception e) {
IllegalStateException e2 = new IllegalStateException("Unable to create nameIndex.");
e2.initCause(e);
throw e2;
}
}
//-----------------------------------------< CacheablePersistenceManager >--
/**
* {@inheritDoc}
*/
public synchronized void onExternalUpdate(ChangeLog changes) {
for (ItemState state : changes.modifiedStates()) {
bundles.remove(getBundleId(state));
}
for (ItemState state : changes.deletedStates()) {
bundles.remove(getBundleId(state));
}
for (ItemState state : changes.addedStates()) {
// There may have been a cache miss entry
bundles.remove(getBundleId(state));
}
}
private NodeId getBundleId(ItemState state) {
if (state.isNode()) {
return (NodeId) state.getId();
} else {
return state.getParentId();
}
}
//------------------------------------------< IterablePersistenceManager >--
@Override
public Map<NodeId, NodeInfo> getAllNodeInfos(NodeId after, int maxCount)
throws ItemStateException, RepositoryException {
Map<NodeId, NodeInfo> infos = new LinkedHashMap<NodeId, NodeInfo>();
for (NodeId nodeId : getAllNodeIds(after, maxCount)) {
infos.put(nodeId, new NodeInfo(loadBundle(nodeId)));
}
return infos;
}
//----------------------------------------------------------------< spi >---
/**
* Loads a bundle from the underlying system.
*
* @param id the node id of the bundle
* @return the loaded bundle or <code>null</code> if the bundle does not
* exist.
* @throws ItemStateException if an error while loading occurs.
*/
protected abstract NodePropBundle loadBundle(NodeId id)
throws ItemStateException;
/**
* Stores a bundle to the underlying system.
*
* @param bundle the bundle to store
* @throws ItemStateException if an error while storing occurs.
*/
protected abstract void storeBundle(NodePropBundle bundle)
throws ItemStateException;
/**
* Deletes the bundle from the underlying system.
*
* @param bundle the bundle to destroy
*
* @throws ItemStateException if an error while destroying occurs.
*/
protected abstract void destroyBundle(NodePropBundle bundle)
throws ItemStateException;
/**
* Deletes the node references from the underlying system.
*
* @param refs the node references to destroy.
* @throws ItemStateException if an error while destroying occurs.
*/
protected abstract void destroy(NodeReferences refs)
throws ItemStateException;
/**
* Stores a node references to the underlying system.
*
* @param refs the node references to store.
* @throws ItemStateException if an error while storing occurs.
*/
protected abstract void store(NodeReferences refs)
throws ItemStateException;
/**
* Returns the BLOB store used by this persistence manager.
*
* @return BLOB store
*/
protected abstract BLOBStore getBlobStore();
//-------------------------------------------------< PersistenceManager >---
/**
* {@inheritDoc}
*
* Initializes the internal structures of this abstract persistence manager.
*/
public void init(PMContext context) throws Exception {
this.context = context;
// init bundle cache
bundles = new ConcurrentCache<NodeId, NodePropBundle>(context.getHomeDir().getName() + "BundleCache");
bundles.setMaxMemorySize(bundleCacheSize);
bundles.setAccessListener(this);
// statistics
RepositoryStatisticsImpl stats = context.getRepositoryStatistics();
readCounter = stats.getCounter(
RepositoryStatistics.Type.BUNDLE_READ_COUNTER);
writeCounter = stats.getCounter(
RepositoryStatistics.Type.BUNDLE_WRITE_COUNTER);
writeDuration = stats.getCounter(
RepositoryStatistics.Type.BUNDLE_WRITE_DURATION);
cacheAccessCounter = stats.getCounter(
RepositoryStatistics.Type.BUNDLE_CACHE_ACCESS_COUNTER);
cacheSizeCounter = stats.getCounter(
RepositoryStatistics.Type.BUNDLE_CACHE_SIZE_COUNTER);
cacheMissCounter = stats.getCounter(
RepositoryStatistics.Type.BUNDLE_CACHE_MISS_COUNTER);
cacheMissDuration = stats.getCounter(
RepositoryStatistics.Type.BUNDLE_CACHE_MISS_DURATION);
}
/**
* {@inheritDoc}
*
* Closes the persistence manager, release acquired resources.
*/
public void close() throws Exception {
// clear caches
bundles.clear();
}
/**
* {@inheritDoc}
*
* Loads the state via the appropriate NodePropBundle.
*/
public NodeState load(NodeId id) throws NoSuchItemStateException, ItemStateException {
NodePropBundle bundle = getBundle(id);
if (bundle == null) {
throw new NoSuchItemStateException(id.toString());
}
return bundle.createNodeState(this);
}
/**
* {@inheritDoc}
*
* Loads the state via the appropriate NodePropBundle.
*/
public PropertyState load(PropertyId id) throws NoSuchItemStateException, ItemStateException {
NodePropBundle bundle = getBundle(id.getParentId());
if (bundle != null) {
PropertyState state = createNew(id);
PropertyEntry p = bundle.getPropertyEntry(id.getName());
if (p != null) {
state.setMultiValued(p.isMultiValued());
state.setType(p.getType());
state.setValues(p.getValues());
state.setModCount(p.getModCount());
} else if (id.getName().equals(JCR_UUID)) {
state.setType(PropertyType.STRING);
state.setMultiValued(false);
state.setValues(new InternalValue[] {
InternalValue.create(id.getParentId().toString()) });
} else if (id.getName().equals(JCR_PRIMARYTYPE)) {
state.setType(PropertyType.NAME);
state.setMultiValued(false);
state.setValues(new InternalValue[] {
InternalValue.create(bundle.getNodeTypeName()) });
} else if (id.getName().equals(JCR_MIXINTYPES)) {
state.setType(PropertyType.NAME);
state.setMultiValued(true);
Set<Name> mixins = bundle.getMixinTypeNames();
state.setValues(InternalValue.create(
mixins.toArray(new Name[mixins.size()])));
} else {
throw new NoSuchItemStateException(id.toString());
}
return state;
} else {
throw new NoSuchItemStateException(id.toString());
}
}
/**
* {@inheritDoc}
*
* Loads the state via the appropriate NodePropBundle.
*/
public boolean exists(PropertyId id) throws ItemStateException {
NodePropBundle bundle = getBundle(id.getParentId());
if (bundle != null) {
Name name = id.getName();
return bundle.hasProperty(name)
|| JCR_PRIMARYTYPE.equals(name)
|| (JCR_UUID.equals(name) && bundle.isReferenceable())
|| (JCR_MIXINTYPES.equals(name)
&& !bundle.getMixinTypeNames().isEmpty());
} else {
return false;
}
}
/**
* {@inheritDoc}
*
* Checks the existence via the appropriate NodePropBundle.
*/
public boolean exists(NodeId id) throws ItemStateException {
// anticipating a load followed by a exists
return getBundle(id) != null;
}
/**
* {@inheritDoc}
*/
public NodeState createNew(NodeId id) {
return new NodeState(id, null, null, NodeState.STATUS_NEW, false);
}
/**
* {@inheritDoc}
*/
public PropertyState createNew(PropertyId id) {
return new PropertyState(id, PropertyState.STATUS_NEW, false);
}
/**
* Right now, this iterates over all items in the changelog and
* calls the individual methods that handle single item states
* or node references objects. Properly implemented, this method
* should ensure that changes are either written completely to
* the underlying persistence layer, or not at all.
*
* {@inheritDoc}
*/
public synchronized void store(ChangeLog changeLog)
throws ItemStateException {
boolean success = false;
try {
storeInternal(changeLog);
success = true;
} finally {
if (!success) {
bundles.clear();
}
}
}
/**
* Stores the given changelog and updates the bundle cache.
*
* @param changeLog the changelog to store
* @throws ItemStateException on failure
*/
private void storeInternal(ChangeLog changeLog)
throws ItemStateException {
// delete bundles
HashSet<ItemId> deleted = new HashSet<ItemId>();
for (ItemState state : changeLog.deletedStates()) {
if (state.isNode()) {
NodePropBundle bundle = getBundle((NodeId) state.getId());
if (bundle == null) {
throw new NoSuchItemStateException(state.getId().toString());
}
deleteBundle(bundle);
deleted.add(state.getId());
}
}
// gather added node states
HashMap<ItemId, NodePropBundle> modified = new HashMap<ItemId, NodePropBundle>();
for (ItemState state : changeLog.addedStates()) {
if (state.isNode()) {
NodePropBundle bundle = new NodePropBundle((NodeState) state);
modified.put(state.getId(), bundle);
}
}
// gather modified node states
for (ItemState state : changeLog.modifiedStates()) {
if (state.isNode()) {
NodeId nodeId = (NodeId) state.getId();
NodePropBundle bundle = modified.get(nodeId);
if (bundle == null) {
bundle = getBundle(nodeId);
if (bundle == null) {
throw new NoSuchItemStateException(nodeId.toString());
}
modified.put(nodeId, bundle);
}
bundle.update((NodeState) state);
} else {
PropertyId id = (PropertyId) state.getId();
// skip redundant primaryType, mixinTypes and uuid properties
if (id.getName().equals(JCR_PRIMARYTYPE)
|| id.getName().equals(JCR_MIXINTYPES)
|| id.getName().equals(JCR_UUID)) {
continue;
}
NodeId nodeId = id.getParentId();
NodePropBundle bundle = modified.get(nodeId);
if (bundle == null) {
bundle = getBundle(nodeId);
if (bundle == null) {
throw new NoSuchItemStateException(nodeId.toString());
}
modified.put(nodeId, bundle);
}
bundle.addProperty((PropertyState) state, getBlobStore());
}
}
// add removed properties
for (ItemState state : changeLog.deletedStates()) {
if (state.isNode()) {
// check consistency
NodeId parentId = state.getParentId();
if (!modified.containsKey(parentId) && !deleted.contains(parentId)) {
log.warn("Deleted node state's parent is not modified or deleted: " + parentId + "/" + state.getId());
}
} else {
PropertyId id = (PropertyId) state.getId();
NodeId nodeId = id.getParentId();
if (!deleted.contains(nodeId)) {
NodePropBundle bundle = modified.get(nodeId);
if (bundle == null) {
// should actually not happen
log.warn("deleted property state's parent not modified!");
bundle = getBundle(nodeId);
if (bundle == null) {
throw new NoSuchItemStateException(nodeId.toString());
}
modified.put(nodeId, bundle);
}
bundle.removeProperty(id.getName(), getBlobStore());
}
}
}
// add added properties
for (ItemState state : changeLog.addedStates()) {
if (!state.isNode()) {
PropertyId id = (PropertyId) state.getId();
// skip primaryType pr mixinTypes properties
if (id.getName().equals(JCR_PRIMARYTYPE)
|| id.getName().equals(JCR_MIXINTYPES)
|| id.getName().equals(JCR_UUID)) {
continue;
}
NodeId nodeId = id.getParentId();
NodePropBundle bundle = modified.get(nodeId);
if (bundle == null) {
// should actually not happen
log.warn("added property state's parent not modified!");
bundle = getBundle(nodeId);
if (bundle == null) {
throw new NoSuchItemStateException(nodeId.toString());
}
modified.put(nodeId, bundle);
}
bundle.addProperty((PropertyState) state, getBlobStore());
}
}
// now store all modified bundles
long updateSize = 0;
for (NodePropBundle bundle : modified.values()) {
putBundle(bundle);
updateSize += bundle.getSize();
}
changeLog.setUpdateSize(updateSize);
// store the refs
for (NodeReferences refs : changeLog.modifiedRefs()) {
if (refs.hasReferences()) {
store(refs);
} else {
destroy(refs);
}
}
}
/**
* Gets the bundle for the given node id. Read/write synchronization
* happens higher up at the SISM level, so we don't need to worry about
* conflicts here.
*
* @param id the id of the bundle to retrieve.
* @return the bundle or <code>null</code> if the bundle does not exist
*
* @throws ItemStateException if an error occurs.
*/
private NodePropBundle getBundle(NodeId id) throws ItemStateException {
NodePropBundle bundle = bundles.get(id);
readCounter.incrementAndGet();
if (bundle == MISSING) {
return null;
}
if (bundle != null) {
return bundle;
}
// cache miss
return getBundleCacheMiss(id);
}
/**
* Called when the bundle is not present in the cache, so we'll need to load
* it from the PM impl.
*
* This also updates the cache.
*
* @param id
* @return
* @throws ItemStateException
*/
private NodePropBundle getBundleCacheMiss(NodeId id)
throws ItemStateException {
long time = System.nanoTime();
log.debug("Loading bundle {}", id);
NodePropBundle bundle = loadBundle(id);
cacheMissDuration.addAndGet(System.nanoTime() - time);
cacheMissCounter.incrementAndGet();
if (bundle != null) {
bundle.markOld();
bundles.put(id, bundle, bundle.getSize());
} else {
bundles.put(id, MISSING, MISSING_SIZE_ESTIMATE);
}
return bundle;
}
/**
* Deletes the bundle
*
* @param bundle the bundle to delete
* @throws ItemStateException if an error occurs
*/
private void deleteBundle(NodePropBundle bundle) throws ItemStateException {
destroyBundle(bundle);
bundle.removeAllProperties(getBlobStore());
bundles.put(bundle.getId(), MISSING, MISSING_SIZE_ESTIMATE);
}
/**
* Stores the bundle and puts it to the cache.
*
* @param bundle the bundle to store
* @throws ItemStateException if an error occurs
*/
private void putBundle(NodePropBundle bundle) throws ItemStateException {
long time = System.nanoTime();
log.debug("Storing bundle {}", bundle.getId());
storeBundle(bundle);
if (auditLogger.isDebugEnabled()) {
auditLogger.debug("{} ({})", bundle.getId(), bundle.getSize());
}
writeDuration.addAndGet(System.nanoTime() - time);
writeCounter.incrementAndGet();
bundle.markOld();
// only put to cache if already exists. this is to ensure proper
// overwrite and not creating big contention during bulk loads
if (bundles.containsKey(bundle.getId())) {
bundles.put(bundle.getId(), bundle, bundle.getSize());
}
}
/**
* {@inheritDoc}
*/
public void checkConsistency(String[] uuids, boolean recursive, boolean fix) {
try {
ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(this, null, null, eventChannel);
checker.check(uuids, recursive);
checker.doubleCheckErrors();
if (fix) {
checker.repair();
}
} catch (RepositoryException ex) {
log.error("While running consistency check.", ex);
}
}
public void setEventChannel(UpdateEventChannel eventChannel) {
this.eventChannel = eventChannel;
}
/**
* {@inheritDoc}
*/
public ConsistencyReport check(String[] uuids, boolean recursive,
boolean fix, String lostNFoundId, ConsistencyCheckListener listener)
throws RepositoryException {
ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(this, listener, lostNFoundId, eventChannel);
checker.check(uuids, recursive);
checker.doubleCheckErrors();
if (fix) {
checker.repair();
}
return checker.getReport();
}
/**
* Evicts the bundle with <code>id</code> from the bundle cache.
*
* @param id the id of the bundle.
*/
protected void evictBundle(NodeId id) {
bundles.remove(id);
}
public void cacheAccessed(long accessCount) {
logCacheStats();
cacheAccessCounter.addAndGet(accessCount);
cacheSizeCounter.set(bundles.getMemoryUsed());
}
private void logCacheStats() {
if (log.isInfoEnabled()) {
long now = System.currentTimeMillis();
if (now < nextLogStats) {
return;
}
log.info(bundles.getCacheInfoAsString());
nextLogStats = now + minLogStatsInterval;
}
}
public void disposeCache(Cache cache) {
// NOOP
}
}