/*
* 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.virtual;
import org.apache.commons.collections.map.ReferenceMap;
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.nodetype.EffectiveNodeType;
import org.apache.jackrabbit.core.nodetype.NodeTypeConflictException;
import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
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.ItemStateListener;
import org.apache.jackrabbit.core.state.ChildNodeEntry;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.QPropertyDefinition;
import org.apache.jackrabbit.spi.QNodeDefinition;
import org.apache.jackrabbit.util.WeakIdentityCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.RepositoryException;
import java.util.Collection;
import java.util.Map;
/**
* This Class implements a virtual item state provider, in order to expose the
* versions to the version storage.
*/
public abstract class AbstractVISProvider implements VirtualItemStateProvider, ItemStateListener {
/**
* the default logger
*/
private static Logger log = LoggerFactory.getLogger(AbstractVISProvider.class);
/**
* the root node
*/
private VirtualNodeState root = null;
/**
* the root node id
*/
protected final NodeId rootNodeId;
/**
* the node type registry
*/
protected final NodeTypeRegistry ntReg;
/**
* the cache node states. key=ItemId, value=ItemState
*/
@SuppressWarnings("unchecked")
private final Map<NodeId, NodeState> nodes =
// Using soft references instead of weak ones seems to have
// some unexpected performance consequences, so for now it's
// better to stick with weak references.
new ReferenceMap(ReferenceMap.HARD, ReferenceMap.WEAK);
/**
* Listeners (weak references)
*/
@SuppressWarnings("unchecked")
private final transient Collection<ItemStateListener> listeners =
new WeakIdentityCollection(5);
/**
* Creates an abstract virtual item state provider
*
* @param ntReg
* @param rootNodeId
*/
public AbstractVISProvider(NodeTypeRegistry ntReg, NodeId rootNodeId) {
this.ntReg = ntReg;
this.rootNodeId = rootNodeId;
}
/**
* Creates the root node state.
*
* @return The virtual root node state.
* @throws RepositoryException
*/
protected abstract VirtualNodeState createRootNodeState() throws RepositoryException;
//-----------------------------------------------------< ItemStateManager >
/**
* {@inheritDoc}
*/
public boolean hasItemState(ItemId id) {
if (id instanceof NodeId) {
if (nodes.containsKey(id)) {
return true;
} else if (id.equals(rootNodeId)) {
return true;
} else {
return internalHasNodeState((NodeId) id);
}
} else {
return internalHasPropertyState((PropertyId) id);
}
}
/**
* {@inheritDoc}
*/
public ItemState getItemState(ItemId id)
throws NoSuchItemStateException, ItemStateException {
if (id instanceof NodeId) {
ItemState s;
if (nodes.containsKey(id)) {
s = nodes.get(id);
} else if (id.equals(rootNodeId)) {
s = getRootState();
} else {
s = cache(internalGetNodeState((NodeId) id));
}
return s;
} else {
return internalGetPropertyState((PropertyId) id);
}
}
/**
* {@inheritDoc}
*/
public NodeReferences getNodeReferences(NodeId id)
throws NoSuchItemStateException, ItemStateException {
throw new NoSuchItemStateException(id.toString());
}
/**
* {@inheritDoc}
*/
public boolean hasNodeReferences(NodeId id) {
return false;
}
//-------------------------------------------< VirtualItemStateProvider >---
/**
* {@inheritDoc}
*/
public boolean isVirtualRoot(ItemId id) {
return id.equals(rootNodeId);
}
/**
* {@inheritDoc}
*/
public NodeId getVirtualRootId() {
return rootNodeId;
}
/**
* {@inheritDoc}
*/
public NodeId[] getVirtualRootIds() {
return new NodeId[]{rootNodeId};
}
/**
* Returns the root state
*
* @return the root state
* @throws ItemStateException If the root node state does not exist and its
* creation fails.
*/
public synchronized NodeState getRootState() throws ItemStateException {
try {
if (root == null) {
root = createRootNodeState();
}
return root;
} catch (RepositoryException e) {
throw new ItemStateException("Error creating root node state", e);
}
}
/**
* Discards all virtual item states and prepares for the root state
* to be recreated when next accessed.
*
* @see <a href="https://issues.apache.org/jira/browse/JCR-2617">JCR-2617</a>
*/
protected synchronized void discardAll() {
if (root != null) {
discardTree(root);
nodes.clear();
root = null;
}
}
/**
* Recursively discards all the properties and nodes in the subtree
* rooted at the given node state.
*
* @param state root of the subtree to be discarded
*/
private void discardTree(NodeState state) {
for (Name name : state.getPropertyNames()) {
try {
getItemState(new PropertyId(state.getNodeId(), name)).discard();
} catch (ItemStateException e) {
log.warn("Unable to discard virtual property " + name, e);
}
}
for (ChildNodeEntry entry : state.getChildNodeEntries()) {
try {
discardTree((NodeState) getItemState(entry.getId()));
} catch (ItemStateException e) {
log.warn("Unable to discard virtual node " + entry.getId(), e);
}
}
state.discard();
}
/**
* Checks if this provide has the node state of the given node id
*
* @param id
* @return <code>true</code> if it has the node state
*/
protected abstract boolean internalHasNodeState(NodeId id);
/**
* Retrieves the node state with the given node id
*
* @param id
* @return
* @throws NoSuchItemStateException
* @throws ItemStateException
*/
protected abstract VirtualNodeState internalGetNodeState(NodeId id)
throws NoSuchItemStateException, ItemStateException;
/**
* Checks if this provider has the property state of the given id.
*
* @param id
* @return <code>true</code> if it has the property state
*/
protected boolean internalHasPropertyState(PropertyId id) {
try {
// get parent state
NodeState parent = (NodeState) getItemState(id.getParentId());
// handle some default prop states
if (parent instanceof VirtualNodeState) {
return parent.hasPropertyName(id.getName());
}
} catch (ItemStateException e) {
// ignore
}
return false;
}
/**
* Retrieves the property state for the given id
*
* @param id
* @return
* @throws NoSuchItemStateException
* @throws ItemStateException
*/
protected VirtualPropertyState internalGetPropertyState(PropertyId id)
throws NoSuchItemStateException, ItemStateException {
// get parent state
NodeState parent = (NodeState) getItemState(id.getParentId());
// handle some default prop states
if (parent instanceof VirtualNodeState) {
return ((VirtualNodeState) parent).getProperty(id.getName());
}
throw new NoSuchItemStateException(id.toString());
}
/**
* {@inheritDoc}
*/
public VirtualPropertyState createPropertyState(VirtualNodeState parent,
Name name, int type,
boolean multiValued)
throws RepositoryException {
PropertyId id = new PropertyId(parent.getNodeId(), name);
VirtualPropertyState prop = new VirtualPropertyState(id);
prop.setType(type);
prop.setMultiValued(multiValued);
return prop;
}
/**
* {@inheritDoc}
*/
public VirtualNodeState createNodeState(VirtualNodeState parent, Name name,
NodeId id, Name nodeTypeName)
throws RepositoryException {
assert id != null;
// create a new node state
VirtualNodeState state = new VirtualNodeState(this, parent.getNodeId(), id, nodeTypeName, Name.EMPTY_ARRAY);
cache(state);
return state;
}
/**
* {@inheritDoc}
*/
public void addListener(ItemStateListener listener) {
synchronized (listeners) {
assert (!listeners.contains(listener));
listeners.add(listener);
}
}
/**
* {@inheritDoc}
*/
public void removeListener(ItemStateListener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
/**
* returns the node type manager
*
* @return the node type manager
*/
protected NodeTypeRegistry getNodeTypeRegistry() {
return ntReg;
}
/**
* adds the node state to the cache
*
* @param state
* @return The same state.
*/
protected NodeState cache(NodeState state) {
if (state != null) {
nodes.put(state.getNodeId(), state);
log.debug("item added to cache. size=" + nodes.size());
}
return state;
}
/**
* removes the node state from the cache
*
* @param id
*/
protected void evict(NodeId id) {
nodes.remove(id);
}
/**
* invalidates the item
*
* @param id
* @param recursive
*/
public void invalidateItem(ItemId id, boolean recursive) {
VirtualNodeState state = id.equals(rootNodeId) ? root : (VirtualNodeState) nodes.get(id);
if (state != null) {
if (recursive) {
VirtualPropertyState[] props = state.getProperties();
for (int i = 0; i < props.length; i++) {
props[i].notifyStateUpdated();
}
for (ChildNodeEntry pe : state.getChildNodeEntries()) {
invalidateItem(pe.getId(), true);
}
}
state.notifyStateUpdated();
}
}
/**
* retrieves the property definition for the given constraints
*
* @param parent The parent node state.
* @param propertyName The name of the property.
* @param type
* @param multiValued
* @return
* @throws RepositoryException
*/
protected QPropertyDefinition getApplicablePropertyDef(NodeState parent, Name propertyName,
int type, boolean multiValued)
throws RepositoryException {
return getEffectiveNodeType(parent).getApplicablePropertyDef(propertyName, type, multiValued);
}
/**
* Retrieves the node definition for the given constraints.
*
* @param parent The parent state.
* @param nodeName
* @param nodeTypeName
* @return
* @throws RepositoryException
*/
protected QNodeDefinition getApplicableChildNodeDef(NodeState parent, Name nodeName, Name nodeTypeName)
throws RepositoryException {
return getEffectiveNodeType(parent).getApplicableChildNodeDef(
nodeName, nodeTypeName, getNodeTypeRegistry());
}
/**
* Returns the effective (i.e. merged and resolved) node type representation
* of this node's primary and mixin node types.
*
* @return the effective node type
* @throws RepositoryException
*/
protected EffectiveNodeType getEffectiveNodeType(NodeState parent) throws RepositoryException {
try {
NodeTypeRegistry ntReg = getNodeTypeRegistry();
return ntReg.getEffectiveNodeType(
parent.getNodeTypeName(), parent.getMixinTypeNames());
} catch (NodeTypeConflictException ntce) {
String msg = "internal error: failed to build effective node type for node " + parent.getNodeId();
throw new RepositoryException(msg, ntce);
}
}
/**
* {@inheritDoc}
*/
public void stateCreated(ItemState created) {
ItemStateListener[] la;
synchronized (listeners) {
la = listeners.toArray(new ItemStateListener[listeners.size()]);
}
for (int i = 0; i < la.length; i++) {
if (la[i] != null) {
la[i].stateCreated(created);
}
}
}
/**
* {@inheritDoc}
*/
public void stateModified(ItemState modified) {
ItemStateListener[] la;
synchronized (listeners) {
la = listeners.toArray(new ItemStateListener[listeners.size()]);
}
for (int i = 0; i < la.length; i++) {
if (la[i] != null) {
la[i].stateModified(modified);
}
}
}
/**
* {@inheritDoc}
*/
public void stateDestroyed(ItemState destroyed) {
if (destroyed.isNode() && destroyed.getId().equals(rootNodeId)) {
try {
root = createRootNodeState();
} catch (RepositoryException e) {
// ignore
}
}
evict((NodeId) destroyed.getId());
ItemStateListener[] la;
synchronized (listeners) {
la = listeners.toArray(new ItemStateListener[listeners.size()]);
}
for (int i = 0; i < la.length; i++) {
if (la[i] != null) {
la[i].stateDestroyed(destroyed);
}
}
}
/**
* {@inheritDoc}
*/
public void stateDiscarded(ItemState discarded) {
if (discarded.isNode() && discarded.getId().equals(rootNodeId)) {
try {
root = createRootNodeState();
} catch (RepositoryException e) {
// ignore
}
}
evict((NodeId) discarded.getId());
ItemStateListener[] la;
synchronized (listeners) {
la = listeners.toArray(new ItemStateListener[listeners.size()]);
}
for (int i = 0; i < la.length; i++) {
if (la[i] != null) {
la[i].stateDiscarded(discarded);
}
}
}
}