/* * 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.state; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <code>ItemState</code> represents the state of an <code>Item</code>. */ public abstract class ItemState { /** * Logger instance */ private static Logger log = LoggerFactory.getLogger(ItemState.class); //------------------< flags defining the current status of this instance > /** * the status is undefined */ public static final int STATUS_UNDEFINED = 0; /** * 'existing', i.e. persistent state */ public static final int STATUS_EXISTING = 1; /** * 'existing', i.e. persistent state that has been transiently modified (copy-on-write) */ public static final int STATUS_EXISTING_MODIFIED = 2; /** * 'existing', i.e. persistent state that has been transiently removed (copy-on-write) */ public static final int STATUS_EXISTING_REMOVED = 3; /** * 'new' state */ public static final int STATUS_NEW = 4; /** * 'existing', i.e. persistent state that has been destroyed by somebody else */ public static final int STATUS_STALE_DESTROYED = 6; /** * the internal status of this item state */ protected int status = STATUS_UNDEFINED; /** * a modification counter used to prevent concurrent modifications */ private short modCount; /** * Flag indicating whether this state is transient */ private final boolean isTransient; /** * Parent container. */ private ItemStateListener container; /** * the backing persistent item state (may be null) */ protected ItemState overlayedState; /** * Constructs a new unconnected item state * * @param initialStatus the initial status of the item state object * @param isTransient flag indicating whether this state is transient or not */ protected ItemState(int initialStatus, boolean isTransient) { switch (initialStatus) { case STATUS_EXISTING: case STATUS_NEW: status = initialStatus; break; default: String msg = "illegal status: " + initialStatus; log.debug(msg); throw new IllegalArgumentException(msg); } modCount = 0; overlayedState = null; this.isTransient = isTransient; } /** * Constructs a new item state that is initially connected to an overlayed * state. * * @param overlayedState the backing item state being overlayed * @param initialStatus the initial status of the new <code>ItemState</code> instance * @param isTransient flag indicating whether this state is transient or not */ protected ItemState(ItemState overlayedState, int initialStatus, boolean isTransient) { switch (initialStatus) { case STATUS_EXISTING: case STATUS_EXISTING_MODIFIED: case STATUS_EXISTING_REMOVED: status = initialStatus; break; case STATUS_UNDEFINED: // see http://issues.apache.org/jira/browse/JCR-897 log.debug("creating ItemState instance with initialStatus=" + STATUS_UNDEFINED + ", id=" + overlayedState.getId()); status = initialStatus; break; default: String msg = "illegal status: " + initialStatus; log.debug(msg); throw new IllegalArgumentException(msg); } this.isTransient = isTransient; this.overlayedState = overlayedState; } /** * Copy state information from another state into this state * @param state source state information * @param syncModCount if the modCount should be synchronized. */ public abstract void copy(ItemState state, boolean syncModCount); /** * Pull state information from overlayed state. */ synchronized void pull() { ItemState state = overlayedState; if (state != null) { // sync modification count copy(state, true); } } /** * Push state information into overlayed state. */ void push() { ItemState state = overlayedState; if (state != null) { state.copy(this, false); } } /** * Called by <code>TransientItemStateManager</code> and * <code>LocalItemStateManager</code> when this item state has been disposed. */ void onDisposed() { disconnect(); overlayedState = null; status = STATUS_UNDEFINED; } /** * Connect this state to some underlying overlayed state. */ public void connect(ItemState overlayedState) throws ItemStateException { if (this.overlayedState != null && this.overlayedState != overlayedState) { throw new ItemStateException( "Item state already connected to another" + " underlying state: " + this); } this.overlayedState = overlayedState; } /** * Reconnect this state to the overlayed state that it has been * disconnected from earlier. */ protected void reconnect() throws ItemStateException { if (this.overlayedState == null) { throw new ItemStateException( "Item state cannot be reconnected because there's no" + " underlying state to reconnect to: " + this); } } /** * Disconnect this state from the underlying overlayed state. */ protected void disconnect() { if (overlayedState != null) { overlayedState = null; } } /** * Return a flag indicating whether this state is connected to some other state. * @return <code>true</code> if this state is connected, <code>false</code> otherwise. */ protected boolean isConnected() { return overlayedState != null; } /** * Notify the parent container about changes to this state. */ protected void notifyStateDiscarded() { if (container != null) { container.stateDiscarded(this); } } /** * Notify the parent container about changes to this state. */ protected void notifyStateCreated() { if (container != null) { container.stateCreated(this); } } /** * Notify the parent container about changes to this state. */ public void notifyStateUpdated() { if (container != null) { container.stateModified(this); } } /** * Notify the parent container about changes to this state. */ protected void notifyStateDestroyed() { if (container != null) { container.stateDestroyed(this); } } //-------------------------------------------------------< public methods > /** * Determines if this item state represents a node. * * @return true if this item state represents a node, otherwise false. */ public abstract boolean isNode(); /** * Returns the identifier of this item. * * @return the id of this item. */ public abstract ItemId getId(); /** * Returns <code>true</code> if this item state represents new or modified * state (i.e. the result of copy-on-write) or <code>false</code> if it * represents existing, unmodified state. * * @return <code>true</code> if this item state is modified or new, * otherwise <code>false</code> */ public boolean isTransient() { return isTransient; } /** * Determines whether this item state has become stale. * @return true if this item state has become stale, false otherwise. */ public boolean isStale() { return overlayedState != null && modCount != overlayedState.getModCount(); } /** * Returns the NodeId of the parent <code>NodeState</code> or <code>null</code> * if either this item state represents the root node or this item state is * 'free floating', i.e. not attached to the repository's hierarchy. * * @return the parent <code>NodeState</code>'s Id */ public abstract NodeId getParentId(); /** * Returns the status of this item. * * @return the status of this item. */ public int getStatus() { return status; } /** * Sets the new status of this item. * * @param newStatus the new status */ public void setStatus(int newStatus) { switch (newStatus) { case STATUS_NEW: case STATUS_EXISTING: case STATUS_EXISTING_REMOVED: case STATUS_EXISTING_MODIFIED: case STATUS_STALE_DESTROYED: case STATUS_UNDEFINED: status = newStatus; return; default: String msg = "illegal status: " + newStatus; log.debug(msg); throw new IllegalArgumentException(msg); } } /** * Returns the modification count. * * @return the modification count. */ public short getModCount() { return modCount; } /** * Sets the modification count. * * @param modCount the modification count of this item */ public void setModCount(short modCount) { this.modCount = modCount; } /** * Updates the modification count. */ synchronized void touch() { modCount++; } /** * Discards this instance, i.e. renders it 'invalid'. */ public void discard() { if (status != STATUS_UNDEFINED) { // notify listeners notifyStateDiscarded(); // reset status status = STATUS_UNDEFINED; } } /** * Determines if this item state is overlying persistent state. * * @return <code>true</code> if this item state is overlying persistent * state, otherwise <code>false</code>. */ public boolean hasOverlayedState() { return overlayedState != null; } /** * Returns the persistent state backing <i>this</i> transient state or * <code>null</code> if there is no persistent state (i.e.. <i>this</i> * state is purely transient). * * @return the persistent item state or <code>null</code> if there is * no persistent state. */ public ItemState getOverlayedState() { return overlayedState; } /** * Set the parent container that will receive notifications about changes to this state. * @param container container to be informed on modifications */ public void setContainer(ItemStateListener container) { if (this.container != null) { throw new IllegalStateException("State already connected to a container: " + this.container); } this.container = container; } /** * Return the parent container that will receive notifications about changes to this state. Returns * <code>null</code> if none has been yet assigned. * @return container or <code>null</code> */ public ItemStateListener getContainer() { return container; } /** * Returns the approximate memory consumption of this state. * * @return the approximate memory consumption of this state. */ public abstract long calculateMemoryFootprint(); }