Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.modeshape.jcr.cache.document.LazyCachedNode Maven / Gradle / Ivy
/*
* ModeShape (http://www.modeshape.org)
*
* 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.
*/
package org.modeshape.jcr.cache.document;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import org.infinispan.schematic.document.Document;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.common.annotation.ThreadSafe;
import org.modeshape.jcr.JcrLexicon;
import org.modeshape.jcr.ModeShapeLexicon;
import org.modeshape.jcr.cache.CachedNode;
import org.modeshape.jcr.cache.ChildReference;
import org.modeshape.jcr.cache.ChildReferences;
import org.modeshape.jcr.cache.NodeCache;
import org.modeshape.jcr.cache.NodeKey;
import org.modeshape.jcr.cache.NodeNotFoundException;
import org.modeshape.jcr.cache.NodeNotFoundInParentException;
import org.modeshape.jcr.cache.PathCache;
import org.modeshape.jcr.cache.ReferrerCounts;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.NameFactory;
import org.modeshape.jcr.value.NamespaceRegistry;
import org.modeshape.jcr.value.Path;
import org.modeshape.jcr.value.Path.Segment;
import org.modeshape.jcr.value.Property;
/**
* This is a (mostly) immutable {@link CachedNode} implementation that lazily loads its content. Technically each instance
* modifies its internal state, but most of the state is based upon a single Document that is read-in only once and never changed
* again. And thus externally each instance appears to be immutable and invariant, except anything that has to do with this node's
* {@link #getName(NodeCache)} (e.g., {@link #getSegment(NodeCache) segment}, and the {@link #getPath(NodeCache) path} {
* {@link #getPath(PathCache) methods}). That's because the name of this node is actually stored on the parent in the
* parent's {@link #getChildReferences(NodeCache) child references}, and this node's name, SNS index, and thus the path can all
* change even though none of the information stored in this node's document will actually change.
*
* This class is marked {@link Serializable} so instances can be placed within an Infinispan cache, though this class is never
* intended to actually be persisted. Instead, it is kept within ModeShape's {@link WorkspaceCache} that uses a purely in-memory
* Infinispan cache containing a configurable number of the most-recently-used {@link CachedNode} instances. As soon as
* {@link CachedNode} instances are evicted, they are GCed and no longer used. (Note that they can be easily reconstructed from
* the entries stored in the repository's main cache.
*
*
* The {@link WorkspaceCache} that keeps these {@link LazyCachedNode} instances is intended to be a cache of the persisted nodes,
* and thus are accessed by all sessions for that workspace. When a persisted node is changed, the corresponding
* {@link CachedNode} is purged from the {@link WorkspaceCache}. Therefore, these LazyCachedNode's must be able to be accessed
* concurrently from multiple threads; this is the reason why all of the lazily-populated fields are either atomic references or
* volatile. (Since all of these lazily-populated members are idempotent, the implementations do this properly without
* synchronization or blocking. The exception is the {@link #parentRefToSelf} field, which can change but is done so in an atomic
* fashion (see {@link #parentReferenceToSelf(WorkspaceCache)} for details).
*
*/
@ThreadSafe
public class LazyCachedNode implements CachedNode, Serializable {
private static final long serialVersionUID = 1L;
// There are two 'final' fields that are always set during construction. The 'document' is the snapshot of node's state
// (except for the node's name or SNS index, which are stored in the parent's document).
private final NodeKey key;
private final Document document;
// The remaining attributes are all lazily loaded/constructed from the 'document' via the DocumentTranslator methods.\
// The WorkspaceCache in which these LazyCachedNodes are kept are accessible
private transient final AtomicReference> properties = new AtomicReference>();
private transient final AtomicReference parentRefToSelf = new AtomicReference();
private transient volatile NodeKey parent;
private transient volatile Set additionalParents;
private transient volatile boolean propertiesFullyLoaded = false;
private transient volatile ChildReferences childReferences;
private transient volatile Boolean hasACL = null;
private transient final AtomicReference>> permissions = new AtomicReference<>();
public LazyCachedNode( NodeKey key,
Document document ) {
assert document != null;
assert key != null;
this.key = key;
this.document = document;
}
protected final WorkspaceCache workspaceCache( NodeCache cache ) {
return ((DocumentCache)cache.unwrap()).workspaceCache();
}
/**
* Get the {@link Document} that represents this node.
*
* @param cache the cache to which this node belongs, required in case this node needs to use the cache; may not be null
* @return the document; never null
* @throws NodeNotFoundException if this node no longer exists
*/
protected Document document( WorkspaceCache cache ) {
return document;
}
@Override
public NodeKey getParentKey( NodeCache cache ) {
if (parent == null) {
// This is idempotent, so it's okay if another thread sneaks in here and recalculates the object before we do ...
WorkspaceCache wsCache = workspaceCache(cache);
parent = wsCache.translator().getParentKey(document(wsCache), wsCache.getWorkspaceKey(), key.getWorkspaceKey());
}
return parent;
}
@Override
public NodeKey getParentKeyInAnyWorkspace( NodeCache cache ) {
WorkspaceCache wsCache = workspaceCache(cache);
return wsCache.translator().getParentKey(document(wsCache), key.getWorkspaceKey(), key.getWorkspaceKey());
}
@Override
public Set getAdditionalParentKeys( NodeCache cache ) {
if (additionalParents == null) {
WorkspaceCache wsCache = workspaceCache(cache);
Set additionalParents = wsCache.translator().getAdditionalParentKeys(document(wsCache));
this.additionalParents = additionalParents.isEmpty() ? additionalParents : Collections.unmodifiableSet(additionalParents);
}
return additionalParents;
}
@Override
public boolean isAtOrBelow( NodeCache cache,
Path path ) {
Path aPath = getPath(cache);
if (path.isAtOrAbove(aPath)) return true;
Set additionalParents = getAdditionalParentKeys(cache);
if (!additionalParents.isEmpty()) {
Path parentOfPath = path.getParent();
for (NodeKey parentKey : additionalParents) {
CachedNode parent = cache.getNode(parentKey);
if (parent.getPath(cache).isAtOrBelow(parentOfPath)) {
ChildReference ref = parent.getChildReferences(cache).getChild(key);
if (ref != null && ref.getSegment().equals(path.getLastSegment())) return true;
}
}
}
return false;
}
protected CachedNode parent( WorkspaceCache cache ) {
NodeKey parentKey = getParentKey(cache);
if (parentKey == null) {
return null;
}
CachedNode parent = cache.getNode(parentKey);
if (parent == null) {
throw new NodeNotFoundException(parentKey);
}
return parent;
}
/**
* Get the parent node's child reference to this node. This method atomically determines if the {@link #parentRefToSelf}
* object is still up-to-date with the parent node's cached representation. If it is not still valid, then it will be
* recomputed.
*
* This method is carefully written to always return an atomically-consistent result without requiring any locking or
* synchronization. Generally speaking, at any given moment the cached information is idempotent, so we rely upon that and use
* an AtomicReference to ensure that the reference is updated atomically. The method implementation also reads the
* AtomicReference only once before deciding what to do; this means that multiple threads can concurrently call this method
* and it will always return the correct information.
*
*
* @param cache the cache
* @return the child reference; never null (even for the root node)
* @throws NodeNotFoundInParentException if this node is no longer referenced by its parent as a child of the parent node
* (which can happen if this node is used while in the midst of being (re)moved.
*/
protected ChildReference parentReferenceToSelf( WorkspaceCache cache ) {
CachedNode currentParent = null;
// If we currently have cached our parent's reference to us (and it is still complete) ...
ParentReferenceToSelf prts = parentRefToSelf.get();
if (prts != null && prts.isComplete()) {
if (prts.isRoot()) {
// We are the root node (always), so we can immediately return ...
return prts.childReferenceInParent();
}
// Get the current parent to compare with what we've cached ...
currentParent = parent(cache);
if (currentParent == null && prts.isRoot()) {
// We are the root and this never changes. Therefore, our 'parentRefToSelf' is always valid ...
return prts.childReferenceInParent();
}
if (prts.isValid(currentParent)) {
// Our cached form still looks okay ...
return prts.childReferenceInParent();
}
// Our cached form is no longer valid so we have to build a new one ...
}
// We have to (re)find our parent's reference to us ...
if (currentParent == null) currentParent = parent(cache);
if (currentParent == null) {
// This is the root node ...
parentRefToSelf.compareAndSet(null, new RootParentReferenceToSelf(cache));
return parentRefToSelf.get().childReferenceInParent(); // always get the most recent
}
// The rest of this logic is only for non-root nodes ...
assert currentParent != null;
// Get our parent's child references to find which one points to us ...
ChildReferences currentReferences = currentParent.getChildReferences(cache);
ChildReference parentRefToMe = null;
if (currentReferences.supportsGetChildReferenceByKey()) {
// Just using the node key is faster if it is supported by the implementation ...
parentRefToMe = currentReferences.getChild(key);
} else {
// Directly look up the ChildReference by going to the cache (and possibly connector) ...
NodeKey parentKey = getParentKey(cache);
parentRefToMe = cache.getChildReference(parentKey, key);
}
if (parentRefToMe != null) {
// We found a new ChildReference instance from the current parent, so cache it ...
parentRefToSelf.set(new NonRootParentReferenceToSelf(currentParent, parentRefToMe));
return parentRefToSelf.get().childReferenceInParent(); // always get the most recent
}
assert parentRefToMe == null;
// This node references a parent, but that parent no longer has a child reference to this node. Perhaps this node is
// in the midst of being moved or removed. Either way, we don't have much choice but to throw an exception about
// us not being found...
throw new NodeNotFoundInParentException(key, getParentKey(cache));
}
protected Map properties() {
if (properties.get() == null) {
// Try to create the properties map, but some other thread might have snuck in and done it for us ...
properties.compareAndSet(null, new ConcurrentHashMap());
}
return properties.get();
}
@Override
public NodeKey getKey() {
return key;
}
@Override
public Name getName( NodeCache cache ) {
return parentReferenceToSelf(workspaceCache(cache)).getName();
}
@Override
public Segment getSegment( NodeCache cache ) {
return parentReferenceToSelf(workspaceCache(cache)).getSegment();
}
/**
* Get the name for this node, without any same-name-sibiling (SNS) index.
*
* @param cache the workspace cache to which this node belongs, required in case this node needs to use the cache; may not be
* null
* @return the name; never null, but the root node will have a zero-length name
* @throws NodeNotFoundInParentException if this node no longer exists
* @see #getSegment(NodeCache)
* @see #getPath(NodeCache)
*/
protected Segment getSegment( WorkspaceCache cache ) {
return parentReferenceToSelf(cache).getSegment();
}
@Override
public Path getPath( NodeCache cache ) {
WorkspaceCache wsCache = workspaceCache(cache);
CachedNode parent = parent(wsCache);
if (parent != null) {
Path parentPath = parent.getPath(wsCache);
return wsCache.pathFactory().create(parentPath, getSegment(wsCache));
}
// check that the node hasn't been removed in the meantime
if (wsCache.getNode(key) == null) {
throw new NodeNotFoundException(key);
}
// This is the root node ...
return wsCache.rootPath();
}
@Override
public Path getPath( PathCache pathCache ) throws NodeNotFoundException {
NodeCache cache = pathCache.getCache();
WorkspaceCache wsCache = workspaceCache(cache);
CachedNode parent = parent(wsCache);
if (parent != null) {
Path parentPath = pathCache.getPath(parent);
return wsCache.pathFactory().create(parentPath, getSegment(wsCache));
}
// check that the node hasn't been removed in the meantime
if (wsCache.getNode(key) == null) {
throw new NodeNotFoundException(key);
}
// This is the root node ...
return wsCache.rootPath();
}
@Override
public int getDepth( NodeCache cache ) throws NodeNotFoundException {
WorkspaceCache wsCache = workspaceCache(cache);
CachedNode parent = parent(wsCache);
if (parent != null) {
// This is not the root, so get our parent's depth and add 1 ...
return parent.getDepth(cache) + 1;
}
// make sure that this isn't a node which has been removed in the meantime
if (wsCache.getNode(key) == null) {
throw new NodeNotFoundException(key);
}
// This is the root node ...
return 0;
}
@Override
public Name getPrimaryType( NodeCache cache ) {
Property prop = getProperty(JcrLexicon.PRIMARY_TYPE, cache);
assert prop != null;
WorkspaceCache wsCache = workspaceCache(cache);
return wsCache.nameFactory().create(prop.getFirstValue());
}
@Override
public Set getMixinTypes( NodeCache cache ) {
Property prop = getProperty(JcrLexicon.MIXIN_TYPES, cache);
if (prop == null || prop.size() == 0) return Collections.emptySet();
final NameFactory nameFactory = workspaceCache(cache).nameFactory();
if (prop.size() == 1) {
Name name = nameFactory.create(prop.getFirstValue());
return Collections.singleton(name);
}
Set names = new HashSet();
for (Object value : prop) {
Name name = nameFactory.create(value);
names.add(name);
}
return names;
}
@Override
public int getPropertyCount( NodeCache cache ) {
if (propertiesFullyLoaded) return properties().size();
WorkspaceCache wsCache = workspaceCache(cache);
return wsCache.translator().countProperties(document(wsCache));
}
@Override
public boolean hasProperties( NodeCache cache ) {
Map props = properties();
if (!props.isEmpty()) return true;
if (propertiesFullyLoaded) return false;
WorkspaceCache wsCache = workspaceCache(cache);
return wsCache.translator().hasProperties(document(wsCache));
}
@Override
public boolean hasProperty( Name name,
NodeCache cache ) {
Map props = properties();
if (props.containsKey(name)) return true;
if (propertiesFullyLoaded) return false;
WorkspaceCache wsCache = workspaceCache(cache);
return wsCache.translator().hasProperty(document(wsCache), name);
}
@Override
public Property getProperty( Name name,
NodeCache cache ) {
Map props = properties();
Property property = props.get(name);
if (property == null && !propertiesFullyLoaded) {
WorkspaceCache wsCache = workspaceCache(cache);
property = wsCache.translator().getProperty(document(wsCache), name);
if (property != null) {
props.put(name, property);
}
}
return property;
}
@Override
public Properties getPropertiesByName( NodeCache cache ) {
if (!propertiesFullyLoaded) {
WorkspaceCache wsCache = workspaceCache(cache);
wsCache.translator().getProperties(document(wsCache), properties());
this.propertiesFullyLoaded = true;
}
return new Properties() {
@Override
public Property getProperty( Name name ) {
return properties().get(name);
}
@Override
public Iterator iterator() {
return properties().values().iterator();
}
};
}
@Override
public Iterator getProperties( NodeCache cache ) {
if (!propertiesFullyLoaded) {
WorkspaceCache wsCache = workspaceCache(cache);
wsCache.translator().getProperties(document(wsCache), properties());
this.propertiesFullyLoaded = true;
}
return properties().values().iterator();
}
@Override
public Iterator getProperties( Collection> namePatterns,
NodeCache cache ) {
WorkspaceCache wsCache = workspaceCache(cache);
final NamespaceRegistry registry = wsCache.context().getNamespaceRegistry();
return new PatternIterator(getProperties(wsCache), namePatterns) {
@Override
protected String matchable( Property value ) {
return value.getName().getString(registry);
}
};
}
@Override
public ChildReferences getChildReferences( NodeCache cache ) {
if (childReferences == null) {
// This is idempotent, so it's okay if another thread sneaks in here and recalculates the object before we do ...
WorkspaceCache wsCache = workspaceCache(cache);
childReferences = wsCache.translator().getChildReferences(wsCache, document(wsCache));
}
return childReferences;
}
@Override
public Set getReferrers( NodeCache cache,
ReferenceType type ) {
// Get the referrers ...
WorkspaceCache wsCache = workspaceCache(cache);
return wsCache.translator().getReferrers(document(wsCache), type);
}
@Override
public ReferrerCounts getReferrerCounts( NodeCache cache ) {
// Get the referrers ...
WorkspaceCache wsCache = workspaceCache(cache);
Map strongCounts = wsCache.translator().getReferrerCounts(document(wsCache), ReferenceType.STRONG);
Map weakCounts = wsCache.translator().getReferrerCounts(document(wsCache), ReferenceType.WEAK);
return ReferrerCounts.create(strongCounts, weakCounts);
}
@Override
public boolean isQueryable( NodeCache cache ) {
WorkspaceCache wsCache = workspaceCache(cache);
return wsCache.translator().isQueryable(document(wsCache));
}
@Override
public boolean hasACL( NodeCache cache ) {
if (hasACL == null) {
hasACL = getChildReferences(cache).getChild(ModeShapeLexicon.ACCESS_LIST_NODE_NAME) != null;
}
return hasACL;
}
@Override
public Map> getPermissions( NodeCache cache ) {
if (!hasACL(cache)) {
return null;
}
if (permissions.get() == null) {
ChildReference aclNodeReference = getChildReferences(cache).getChild(ModeShapeLexicon.ACCESS_LIST_NODE_NAME);
assert aclNodeReference != null;
CachedNode aclNode = cache.getNode(aclNodeReference);
assert aclNode != null;
Map> permissions = new HashMap<>();
ChildReferences permissionsReference = aclNode.getChildReferences(cache);
for (ChildReference permissionReference : permissionsReference) {
CachedNode permission = cache.getNode(permissionReference);
String name = permission.getProperty(ModeShapeLexicon.PERMISSION_PRINCIPAL_NAME, cache).getFirstValue().toString();
Property privileges = permission.getProperty(ModeShapeLexicon.PERMISSION_PRIVILEGES_NAME, cache);
Set privilegeNames = new HashSet<>();
for (Object privilege : privileges.getValuesAsArray()) {
privilegeNames.add(privilege.toString());
}
permissions.put(name, privilegeNames);
}
this.permissions.compareAndSet(null, permissions);
}
return permissions.get();
}
@Override
public boolean isExternal( NodeCache cache) {
return !getKey().getSourceKey().equals(cache.getRootKey().getSourceKey());
}
@Override
public int hashCode() {
return key.hashCode();
}
@Override
public boolean equals( Object obj ) {
if (obj == this) return true;
if (obj instanceof CachedNode) {
CachedNode that = (CachedNode)obj;
return this.getKey().equals(that.getKey());
}
return false;
}
@Override
public String toString() {
return getString(null);
}
public String getString( NamespaceRegistry registry ) {
StringBuilder sb = new StringBuilder();
sb.append("Node ").append(key).append(": ");
if (document != null) sb.append(document);
else sb.append(" ");
return sb.toString();
}
/**
* A single object used to cache the parent's {@link ChildReference} that points to this node and methods that determine
* whether this cached information is still valid.
*
* @author Randall Hauch ([email protected] )
*/
protected static interface ParentReferenceToSelf {
/**
* Get the cached {@link ChildReference} instance.
*
* @return the child reference; never null
*/
ChildReference childReferenceInParent();
/**
* Determine if this object is still complete. Some implementations use weak references that can eventually become nulled
* as the target is garbage collected. When that happens, this method should return true.
*
* @return true if this object is still complete, or false if any information becomes garbage collected
*/
boolean isComplete();
/**
* Determine if this instance is still valid, given the supplied {@link CachedNode} instance that represents the
* most-recently acquired parent node representation.
*
* @param recentParent the most recent cached node for the parent
* @return true if this object is still valid, or false if the parent's information has changed since this object was
* created
*/
boolean isValid( CachedNode recentParent );
/**
* Get whether this represents the {@link #childReferenceInParent() child reference} pointing to the root node.
*
* @return true if the object that owns this is the root node, or false otherwise
*/
boolean isRoot();
}
/**
* A {@link ParentReferenceToSelf} implementation used only for the root node. The root node never changes, but it is the only
* node that does not have a {@link ChildReference} pointing to it. Since ModeShape keeps all node names within the
* {@link ChildReference} instances, this method simply holds onto a special {@link WorkspaceCache#childReferenceForRoot()
* ChildReference that contains the root's name}.
*
* This class is immutable, and it is only used for the root {@link LazyCachedNode} instance (which can indeed change as
* properties and children are added/removed/changed.
*
*
* @author Randall Hauch ([email protected] )
*/
@Immutable
protected static final class RootParentReferenceToSelf implements ParentReferenceToSelf {
private final ChildReference childReferenceInParent;
protected RootParentReferenceToSelf( WorkspaceCache cache ) {
childReferenceInParent = cache.childReferenceForRoot();
}
@Override
public boolean isRoot() {
return true;
}
@Override
public ChildReference childReferenceInParent() {
return this.childReferenceInParent;
}
@Override
public boolean isComplete() {
return true;
}
@Override
public boolean isValid( CachedNode recentParent ) {
return true;
}
@Override
public String toString() {
return "RootParentReferenceToSelf";
}
}
/**
* A {@link ParentReferenceToSelf} implementation that caches the {@link ChildReference} from the parent plus the actual
* parent (via a weak reference to the {@link CachedNode}.
*
* This class is immutable, because it is simply discarded when it is out of date; see
* {@link LazyCachedNode#parentReferenceToSelf(WorkspaceCache)}).
*
*
* @author Randall Hauch ([email protected] )
*/
@Immutable
protected static final class NonRootParentReferenceToSelf implements ParentReferenceToSelf {
private final ChildReference childReferenceInParent;
private final WeakReference parent;
protected NonRootParentReferenceToSelf( CachedNode parent,
ChildReference childReferenceInParent ) {
assert parent != null;
assert childReferenceInParent != null;
this.childReferenceInParent = childReferenceInParent;
this.parent = new WeakReference(parent);
}
@Override
public boolean isRoot() {
return false;
}
@Override
public ChildReference childReferenceInParent() {
return this.childReferenceInParent;
}
@Override
public boolean isComplete() {
return this.parent.get() != null; // null if this was garbage collected
}
@Override
public boolean isValid( CachedNode recentParent ) {
CachedNode parent = this.parent.get();
return parent == recentParent;
}
@Override
public String toString() {
return childReferenceInParent.toString() + " in " + parent.get();
}
}
}