org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyEntryImpl Maven / Gradle / Ivy
/*
* 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.jcr2spi.hierarchy;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import javax.jcr.InvalidItemStateException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.RepositoryException;
import org.apache.jackrabbit.jcr2spi.state.ItemState;
import org.apache.jackrabbit.jcr2spi.state.ItemStateFactory;
import org.apache.jackrabbit.jcr2spi.state.Status;
import org.apache.jackrabbit.jcr2spi.state.TransientItemStateFactory;
import org.apache.jackrabbit.jcr2spi.state.ItemState.MergeResult;
import org.apache.jackrabbit.spi.IdFactory;
import org.apache.jackrabbit.spi.ItemInfoCache;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.PathFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* HierarchyEntryImpl
implements base functionality for child node
* and property references.
*/
abstract class HierarchyEntryImpl implements HierarchyEntry {
private static Logger log = LoggerFactory.getLogger(HierarchyEntryImpl.class);
/**
* The required generation of this entry. This is used by the
* {@link ItemInfoCache} to determine whether an item info in the cache is
* up to date or not. That is whether the generation of the item info in the
* cache is the same or more recent as the required generation of this entry.
*/
private long generation;
/**
* Cached soft reference to the target ItemState.
*/
private Reference target;
/**
* The name of the target item state.
*/
protected Name name;
/**
* Hard reference to the parent NodeEntry
.
*/
protected NodeEntryImpl parent;
/**
* The item state factory to create the item state.
*/
protected final EntryFactory factory;
/**
* Creates a new HierarchyEntryImpl
with the given parent
* NodeState
.
*
* @param parent the NodeEntry
that owns this child node
* reference.
* @param name the name of the child item.
* @param factory
*/
HierarchyEntryImpl(NodeEntryImpl parent, Name name, EntryFactory factory) {
this.parent = parent;
this.name = name;
this.factory = factory;
}
/**
* Shortcut for {@link EntryFactory#getItemStateFactory()}
* @return
*/
protected TransientItemStateFactory getItemStateFactory() {
return factory.getItemStateFactory();
}
/**
* Shortcut for {@link EntryFactory#getPathFactory()}
* @return
*/
protected PathFactory getPathFactory() {
return factory.getPathFactory();
}
/**
* Shortcut for {@link EntryFactory#getIdFactory()}
* @return
*/
protected IdFactory getIdFactory() {
return factory.getIdFactory();
}
/**
* Resolves this HierarchyEntryImpl
and returns the target
* ItemState
of this reference. This method may return a
* cached ItemState
if this method was called before already
* otherwise this method will forward the call to {@link #doResolve()}
* and cache its return value. If an existing state has been invalidated
* before, an attempt is made to reload it in order to make sure, that
* a call to {@link ItemState#isValid()} does not equivocally return false.
*
* @return the ItemState
where this reference points to.
* @throws ItemNotFoundException if the referenced ItemState
* does not exist.
* @throws RepositoryException if an error occurs.
*/
ItemState resolve() throws ItemNotFoundException, RepositoryException {
// check if already resolved
ItemState state = internalGetItemState();
// not yet resolved. retrieve and keep soft reference to state
if (state == null) {
try {
state = doResolve();
// set the item state unless 'setItemState' has already been
// called by the ItemStateFactory (recall internalGetItemState)
if (internalGetItemState() == null) {
setItemState(state);
}
} catch (ItemNotFoundException e) {
remove();
throw e;
}
} else if (state.getStatus() == Status.INVALIDATED) {
// completely reload this entry, but don't reload recursively
reload(false);
}
return state;
}
/**
* Resolves this HierarchyEntryImpl
and returns the target
* ItemState
of this reference.
*
* @return the ItemState
where this reference points to.
* @throws ItemNotFoundException if the referenced ItemState
* does not exist.
* @throws RepositoryException if another error occurs.
*/
abstract ItemState doResolve() throws ItemNotFoundException, RepositoryException;
/**
* Build the Path of this entry
*
* @param workspacePath
* @return
* @throws RepositoryException
*/
abstract Path buildPath(boolean workspacePath) throws RepositoryException;
/**
* @return the item state or null
if the entry isn't resolved.
*/
ItemState internalGetItemState() {
ItemState state = null;
if (target != null) {
state = target.get();
}
return state;
}
protected EntryFactory.InvalidationStrategy getInvalidationStrategy() {
return factory.getInvalidationStrategy();
}
/**
* Invalidates the underlying {@link ItemState}. If recursive
is
* true also invalidates the underlying item states of all child entries.
* @param recursive
*/
protected void invalidateInternal(boolean recursive) {
ItemState state = internalGetItemState();
if (state == null) {
log.debug("Skip invalidation for unresolved HierarchyEntry " + name);
} else {
state.invalidate();
}
}
//-----------------------------------------------------< HierarchyEntry >---
/**
* @see HierarchyEntry#getName()
*/
public Name getName() {
return name;
}
/**
* @see HierarchyEntry#getPath()
*/
public Path getPath() throws RepositoryException {
return buildPath(false);
}
/**
* @see HierarchyEntry#getWorkspacePath()
*/
public Path getWorkspacePath() throws RepositoryException {
return buildPath(true);
}
/**
* @see HierarchyEntry#getParent()
*/
public NodeEntry getParent() {
return parent;
}
/**
* @see HierarchyEntry#getStatus()
*/
public int getStatus() {
ItemState state = internalGetItemState();
if (state == null) {
return Status._UNDEFINED_;
} else {
return state.getStatus();
}
}
/**
* @see HierarchyEntry#isAvailable()
*/
public boolean isAvailable() {
return internalGetItemState() != null;
}
/**
* {@inheritDoc}
* @see HierarchyEntry#getItemState()
*/
public ItemState getItemState() throws ItemNotFoundException, RepositoryException {
ItemState state = resolve();
return state;
}
/**
* {@inheritDoc}
* @see HierarchyEntry#setItemState(ItemState)
*/
public synchronized void setItemState(ItemState state) {
ItemState currentState = internalGetItemState();
if (state == null || state == currentState || denotesNode() != state.isNode()) {
throw new IllegalArgumentException();
}
if (currentState == null) {
// not connected yet to an item state. either a new entry or
// an unresolved hierarchy entry.
target = new SoftReference(state);
} else {
// was already resolved before -> merge the existing state
// with the passed state.
int currentStatus = currentState.getStatus();
boolean keepChanges = Status.isTransient(currentStatus) || Status.isStale(currentStatus);
MergeResult mergeResult = currentState.merge(state, keepChanges);
if (currentStatus == Status.INVALIDATED) {
currentState.setStatus(Status.EXISTING);
} else if (mergeResult.modified()) {
currentState.setStatus(Status.MODIFIED);
} // else: not modified. just leave status as it is.
mergeResult.dispose();
}
}
/**
* {@inheritDoc}
* @see HierarchyEntry#invalidate(boolean)
*/
public void invalidate(boolean recursive) {
getInvalidationStrategy().invalidate(this, recursive);
}
public void calculateStatus() {
getInvalidationStrategy().applyPending(this);
}
/**
* {@inheritDoc}
* @see HierarchyEntry#revert()
*/
public void revert() throws RepositoryException {
ItemState state = internalGetItemState();
if (state == null) {
// nothing to do
return;
}
int oldStatus = state.getStatus();
switch (oldStatus) {
case Status.EXISTING_MODIFIED:
case Status.STALE_MODIFIED:
// revert state modifications
state.revert();
state.setStatus(Status.EXISTING);
break;
case Status.EXISTING_REMOVED:
// revert state modifications
state.revert();
state.setStatus(Status.EXISTING);
break;
case Status.NEW:
// reverting a NEW state is equivalent to its removal.
// however: no need remove the complete hierarchy as revert is
// always related to Item#refresh(false) which affects the
// complete tree (and all add-operations within it) anyway.
state.setStatus(Status.REMOVED);
parent.internalRemoveChildEntry(this);
break;
case Status.STALE_DESTROYED:
// state does not exist any more -> reverting of pending
// transient changes (that lead to the stale status) can be
// omitted and the entry is complete removed instead.
remove();
break;
default:
// Cannot revert EXISTING, REMOVED, INVALIDATED, MODIFIED states.
// State was implicitly reverted or external modifications
// reverted the modification.
log.debug("State with status " + oldStatus + " cannot be reverted.");
}
}
/**
* {@inheritDoc}
* @see HierarchyEntry#reload(boolean)
*/
public void reload(boolean recursive) {
int status = getStatus();
if (status == Status._UNDEFINED_) {
// unresolved: entry will be loaded and validated upon resolution.
return;
}
if (Status.isTransient(status) || Status.isStale(status) || Status.isTerminal(status)) {
// transient || stale: avoid reloading
// new || terminal: cannot be reloaded from persistent layer anyway.
log.debug("Skip reload for item with status " + Status.getName(status) + ".");
return;
}
/**
* Retrieved a fresh ItemState from the persistent layer. Which will
* then be merged into the current state.
*/
try {
ItemStateFactory isf = getItemStateFactory();
if (denotesNode()) {
NodeEntry ne = (NodeEntry) this;
isf.createNodeState(ne.getWorkspaceId(), ne);
} else {
PropertyEntry pe = (PropertyEntry) this;
isf.createPropertyState(pe.getWorkspaceId(), pe);
}
} catch (ItemNotFoundException e) {
// remove hierarchyEntry including all children
log.debug("Item '" + getName() + "' cannot be found on the persistent layer -> remove.");
remove();
} catch (RepositoryException e) {
// TODO: rather throw?
log.error("Exception while reloading item: " + e);
}
}
/**
* {@inheritDoc}
* @see HierarchyEntry#transientRemove()
*/
public void transientRemove() throws InvalidItemStateException, RepositoryException {
ItemState state = internalGetItemState();
if (state == null) {
// nothing to do -> correct status must be set upon resolution.
return;
}
// if during recursive removal an invalidated entry is found, reload
// it in order to determine the current status.
if (state.getStatus() == Status.INVALIDATED) {
reload(false);
}
switch (state.getStatus()) {
case Status.NEW:
state.setStatus(Status.REMOVED);
parent.internalRemoveChildEntry(this);
break;
case Status.EXISTING:
case Status.EXISTING_MODIFIED:
state.setStatus(Status.EXISTING_REMOVED);
// NOTE: parent does not need to be informed. an transiently
// removed propertyEntry is automatically moved to the 'attic'
// if a conflict with a new entry occurs.
break;
case Status.REMOVED:
case Status.STALE_DESTROYED:
throw new InvalidItemStateException("Item has already been removed by someone else. Status = " + Status.getName(state.getStatus()));
default:
throw new RepositoryException("Cannot transiently remove an ItemState with status " + Status.getName(state.getStatus()));
}
}
/**
* @see HierarchyEntry#remove()
*/
public void remove() {
internalRemove(false);
}
public long getGeneration() {
calculateStatus();
return generation;
}
//--------------------------------------------------------------------------
/**
* @param staleParent
*/
void internalRemove(boolean staleParent) {
ItemState state = internalGetItemState();
int status = getStatus();
if (state != null) {
if (status == Status.EXISTING_MODIFIED) {
state.setStatus(Status.STALE_DESTROYED);
} else if (status == Status.NEW && staleParent) {
// keep status NEW
} else {
state.setStatus(Status.REMOVED);
if (!staleParent) {
parent.internalRemoveChildEntry(this);
}
}
} else {
// unresolved
if (!staleParent && parent != null) {
parent.internalRemoveChildEntry(this);
}
}
}
// ----------------------------------------------< InvalidationStrategy >---
/**
* An implementation of InvalidationStrategy
which lazily invalidates
* the underlying {@link ItemState}s.
*/
static class LazyInvalidation implements EntryFactory.InvalidationStrategy {
/**
* Marker for entries with a pending recursive invalidation.
*/
private static long INVALIDATION_PENDING = -1;
/**
* Number of the current generation
*/
private long currentGeneration;
/**
* Increment for obtaining the next generation from the current generation.
*/
private int nextGeneration;
/**
* A recursive invalidation is being processed if true
.
* This flag is for preventing re-entrance.
*/
private boolean invalidating;
/**
* Records a pending recursive {@link ItemState#invalidate() invalidation} for
* entry
if recursive
is true
. Otherwise
* invalidates the entry right away.
* {@inheritDoc}
*/
public void invalidate(HierarchyEntry entry, boolean recursive) {
HierarchyEntryImpl he = (HierarchyEntryImpl) entry;
if (recursive) {
he.generation = INVALIDATION_PENDING;
if (!invalidating) {
nextGeneration = 1;
}
} else {
if (!invalidating) {
nextGeneration = 1;
}
he.invalidateInternal(false);
}
}
/**
* Checks whether entry
itself has a invalidation pending.
* If so, the entry
is invalidated. Otherwise check
* whether an invalidation occurred after the entry has last been
* invalidated. If so, search the path to the root for an originator of
* the pending invalidation.
* If such an originator is found, invalidate each entry on the path.
* Otherwise this method does nothing.
* {@inheritDoc}
*/
public void applyPending(HierarchyEntry entry) {
if (!invalidating) {
invalidating = true;
currentGeneration += nextGeneration;
nextGeneration = 0;
try {
HierarchyEntryImpl he = (HierarchyEntryImpl) entry;
if (he.generation == INVALIDATION_PENDING) {
he.invalidateInternal(true);
he.generation = currentGeneration;
} else if (he.generation < currentGeneration) {
resolvePendingInvalidation(he);
}
} finally {
invalidating = false;
}
}
}
/**
* Search the path to the root for an originator of a pending invalidation of
* this entry
. If such an originator is found, invalidate each
* entry on the path. Otherwise do nothing.
*
* @param entry
*/
private void resolvePendingInvalidation(HierarchyEntryImpl entry) {
if (entry != null) {
// First recursively travel up to the first parent node
// which has invalidation pending or to the root node if
// no such node exists.
if (entry.generation != INVALIDATION_PENDING) {
resolvePendingInvalidation(entry.parent);
}
// Then travel the path backwards invalidating as required
if (entry.generation == INVALIDATION_PENDING) {
entry.invalidateInternal(true);
}
entry.generation = currentGeneration;
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy