org.apache.jackrabbit.jcr2spi.state.ItemState 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.state;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import javax.jcr.InvalidItemStateException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.RepositoryException;
import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyEntry;
import org.apache.jackrabbit.jcr2spi.hierarchy.NodeEntry;
import org.apache.jackrabbit.jcr2spi.nodetype.ItemDefinitionProvider;
import org.apache.jackrabbit.spi.ItemId;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.util.WeakIdentityCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* ItemState
represents the state of an Item
.
*/
public abstract class ItemState {
/**
* Logger instance
*/
private static Logger log = LoggerFactory.getLogger(ItemState.class);
/**
* the internal status of this item state
*/
private int status;
/**
* The hierarchy entry this state belongs to.
*/
private final HierarchyEntry hierarchyEntry;
/**
* Listeners (weak references)
*/
@SuppressWarnings("unchecked")
private final transient Collection listeners = new WeakIdentityCollection(5);
/**
* The ItemStateFactory
which is used to create new
* ItemState
instances.
*/
final ItemStateFactory isf;
final ItemDefinitionProvider definitionProvider;
/**
* Constructs an item state
*
* @param entry
* @param isf
* @param definitionProvider
*/
protected ItemState(HierarchyEntry entry, ItemStateFactory isf,
ItemDefinitionProvider definitionProvider) {
this(getInitialStatus(entry.getParent()), entry, isf, definitionProvider);
}
/**
* Constructs an item state
*
* @param entry
* @param isf
* @param definitionProvider
*/
protected ItemState(int initialStatus, HierarchyEntry entry,
ItemStateFactory isf,
ItemDefinitionProvider definitionProvider) {
if (entry == null) {
throw new IllegalArgumentException("Cannot build ItemState from 'null' HierarchyEntry");
}
switch (initialStatus) {
case Status.EXISTING:
case Status.NEW:
case Status.EXISTING_REMOVED:
status = initialStatus;
break;
default:
String msg = "illegal status: " + initialStatus;
log.debug(msg);
throw new IllegalArgumentException(msg);
}
this.hierarchyEntry = entry;
this.isf = isf;
this.definitionProvider = definitionProvider;
}
/**
*
* @param parent
* @return
*/
private static int getInitialStatus(NodeEntry parent) {
int status = Status.EXISTING;
// walk up hierarchy and check if any of the parents is transiently
// removed, in which case the status must be set to EXISTING_REMOVED.
while (parent != null) {
if (parent.getStatus() == Status.EXISTING_REMOVED) {
status = Status.EXISTING_REMOVED;
break;
}
parent = parent.getParent();
}
return status;
}
//----------------------------------------------------------< ItemState >---
/**
* The HierarchyEntry
corresponding to this ItemState
.
*
* @return The HierarchyEntry
corresponding to this ItemState
.
*/
public HierarchyEntry getHierarchyEntry() {
return hierarchyEntry;
}
/**
* Returns true
if this item state is valid and can be accessed.
* @return
* @see Status#isValid(int)
* @see Status#isStale(int)
*/
public boolean isValid() {
return Status.isValid(getStatus()) || Status.isStale(getStatus());
}
/**
* Utility method:
* Determines if this item state represents a node.
*
* @return true if this item state represents a node, otherwise false.
*/
public abstract boolean isNode();
/**
* Utility method:
* Returns the name of this state. Shortcut for calling 'getName' on the
* {@link ItemState#getHierarchyEntry() hierarchy entry}.
*
* @return name of this state
*/
public Name getName() {
return getHierarchyEntry().getName();
}
/**
* Utility method:
* Returns the identifier of this item state. Shortcut for calling 'getId'
* on the {@link ItemState#getHierarchyEntry() hierarchy entry}.
*
* @return the identifier of this item state..
*/
public abstract ItemId getId() throws RepositoryException;
/**
* Utility method:
* Returns the identifier of this item state. Shortcut for calling 'getWorkspaceId'
* on the NodeEntry or PropertyEntry respectively.
*
* @return the identifier of this item state..
*/
public abstract ItemId getWorkspaceId() throws RepositoryException;
/**
* Utility method:
* Returns the path of this item state. Shortcut for calling
* 'getPath' on the {@link ItemState#getHierarchyEntry() hierarchy entry}.
*
* @return
* @throws RepositoryException if an error occurs
*/
public Path getPath() throws RepositoryException {
return getHierarchyEntry().getPath();
}
/**
* Utility method: Shortcut for calling
* 'getParent().getNodeState()' on the {@link ItemState#getHierarchyEntry()
* hierarchy entry}.
*
* @return
* @throws ItemNotFoundException
* @throws RepositoryException
*/
public NodeState getParent() throws ItemNotFoundException, RepositoryException {
// safeguard against root node's null parent
NodeEntry parent = getHierarchyEntry().getParent();
if (parent != null) {
return getHierarchyEntry().getParent().getNodeState();
}
return null;
}
/**
* Returns the status of this item.
*
* @return the status of this item.
*/
public final int getStatus() {
// Call calculateStatus to apply a possible pending invalidation
// in the entry hierarchy.
getHierarchyEntry().calculateStatus();
return status;
}
/**
* Sets the new status of this item.
*
* @param newStatus the new status
*/
public void setStatus(int newStatus) {
int oldStatus = status;
if (oldStatus == newStatus) {
return;
}
if (oldStatus == Status.REMOVED) {
throw new IllegalStateException("State is already in terminal status " + Status.getName(oldStatus));
}
if (Status.isValidStatusChange(oldStatus, newStatus)) {
status = Status.getNewStatus(oldStatus, newStatus);
} else {
throw new IllegalArgumentException("Invalid new status " + Status.getName(newStatus) + " for state with status " + Status.getName(oldStatus));
}
// Notify listeners about status change
// copy listeners to array to avoid ConcurrentModificationException
ItemStateLifeCycleListener[] la;
synchronized (listeners) {
la = listeners.toArray(new ItemStateLifeCycleListener[listeners.size()]);
}
for (int i = 0; i < la.length; i++) {
if (la[i] != null) {
la[i].statusChanged(this, oldStatus);
}
}
if (status == Status.MODIFIED) {
/*
change back tmp MODIFIED status, that is used as marker only
inform listeners about (external) changes.
*/
status = Status.EXISTING;
}
}
/**
* Merge all data from the given state into this state. If
* 'keepChanges
' is true, transient modifications present on
* this state are not touched. Otherwise this state is completely reset
* according to the given other state.
*
* @param another
* @param keepChanges
* @return a MergeResult instance which represent the result of the merge operation
*/
public abstract MergeResult merge(ItemState another, boolean keepChanges);
/**
* Revert all transient modifications made to this ItemState.
*
* @return true if this state has been modified i.e. if there was anything
* to revert.
*/
public abstract boolean revert();
/**
* Add an ItemStateLifeCycleListener
*
* @param listener the new listener to be informed on modifications
*/
public void addListener(ItemStateLifeCycleListener listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
/**
* Remove an ItemStateLifeCycleListener
*
* @param listener an existing listener
*/
public void removeListener(ItemStateLifeCycleListener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
/**
* Unmodifiable iterator over the listeners present on this item state.
*
* @return iterator over ItemStateLifeCycleListener
s.
*/
public Iterator getListeners() {
return Collections.unmodifiableCollection(listeners).iterator();
}
/**
* Invalidates this state: set its {@link Status} to {@link Status#INVALIDATED}
* if the current status is {@link Status#EXISTING}. Does nothing otherwise.
*/
public void invalidate() {
if (status == Status.EXISTING) {
setStatus(Status.INVALIDATED);
} else {
log.debug("Skip invalidation for item {} with status {}", getName(), Status.getName(status));
}
}
/**
* Marks this item state as modified.
*/
void markModified() throws InvalidItemStateException {
switch (status) {
case Status.EXISTING:
setStatus(Status.EXISTING_MODIFIED);
break;
case Status.EXISTING_MODIFIED:
// already modified, do nothing
break;
case Status.NEW:
// still new, do nothing
break;
case Status.STALE_DESTROYED:
case Status.STALE_MODIFIED:
// should actually not get here because item should check before
// it modifies an item state.
throw new InvalidItemStateException("Cannot mark stale state modified.");
case Status.EXISTING_REMOVED:
default:
String msg = "Cannot mark item state with status '" + Status.getName(status) + "' modified.";
throw new InvalidItemStateException(msg);
}
}
// -----------------------------------------------------< MergeResult >---
/**
* A MergeResult represents the result of a {@link ItemState#merge(ItemState, boolean)}
* operation.
*/
public interface MergeResult {
/**
* @return true iff the target state of {@link ItemState#merge(ItemState, boolean)}
* was modified.
*/
public boolean modified();
/**
* Dispose this MergeResult and release all internal resources that
* are not needed any more.
*/
public void dispose();
}
/**
* A SimpleMergeResult is just a holder for a modification status.
* The {@link #modified()} method just returns the modification status passed
* to the constructor.
*/
protected class SimpleMergeResult implements MergeResult {
private final boolean modified;
/**
* @param modified modification status
*/
public SimpleMergeResult(boolean modified) {
this.modified = modified;
}
public boolean modified() {
return modified;
}
public void dispose() {
// nothing to do.
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy