/*
* 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;
import javax.jcr.AccessDeniedException;
import javax.jcr.InvalidItemStateException;
import javax.jcr.Item;
import javax.jcr.ItemNotFoundException;
import javax.jcr.ItemVisitor;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import org.apache.jackrabbit.core.id.ItemId;
import org.apache.jackrabbit.core.session.SessionContext;
import org.apache.jackrabbit.core.session.SessionOperation;
import org.apache.jackrabbit.core.state.ItemState;
import org.apache.jackrabbit.core.state.SessionItemStateManager;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.value.ValueHelper;
/**
* <code>ItemImpl</code> implements the <code>Item</code> interface.
*/
public abstract class ItemImpl implements Item {
protected static final int STATUS_NORMAL = 0;
protected static final int STATUS_MODIFIED = 1;
protected static final int STATUS_DESTROYED = 2;
protected static final int STATUS_INVALIDATED = 3;
protected final ItemId id;
/**
* The component context of the session to which this item is associated.
*/
protected final SessionContext sessionContext;
/**
* Item data associated with this item.
*/
protected final ItemData data;
/**
* <code>ItemManager</code> that created this <code>Item</code>
*/
protected final ItemManager itemMgr;
/**
* <code>SessionItemStateManager</code> associated with this <code>Item</code>
*/
protected final SessionItemStateManager stateMgr;
/**
* Package private constructor.
*
* @param itemMgr the <code>ItemManager</code> that created this <code>Item</code>
* @param sessionContext the component context of the associated session
* @param data ItemData of this <code>Item</code>
*/
ItemImpl(ItemManager itemMgr, SessionContext sessionContext, ItemData data) {
this.sessionContext = sessionContext;
this.stateMgr = sessionContext.getItemStateManager();
this.id = data.getId();
this.itemMgr = itemMgr;
this.data = data;
}
protected <T> T perform(final SessionOperation<T> operation)
throws RepositoryException {
itemSanityCheck();
return sessionContext.getSessionState().perform(operation);
}
/**
* Performs a sanity check on this item and the associated session.
*
* @throws RepositoryException if this item has been rendered invalid for some reason
*/
protected void sanityCheck() throws RepositoryException {
// check session status
sessionContext.getSessionState().checkAlive();
// check status of this item for read operation
itemSanityCheck();
}
/**
* Checks the status of this item.
*
* @throws RepositoryException if this item no longer exists
*/
protected void itemSanityCheck() throws RepositoryException {
// check status of this item for read operation
final int status = data.getStatus();
if (status == STATUS_DESTROYED || status == STATUS_INVALIDATED) {
throw new InvalidItemStateException(
"Item does not exist anymore: " + id);
}
}
protected boolean isTransient() {
return getItemState().isTransient();
}
protected abstract ItemState getOrCreateTransientItemState() throws RepositoryException;
protected abstract void makePersistent() throws RepositoryException;
/**
* Marks this instance as 'removed' and notifies its listeners.
* The resulting state is either 'temporarily invalidated' or
* 'permanently invalidated', depending on the initial state.
*
* @throws RepositoryException if an error occurs
*/
protected void setRemoved() throws RepositoryException {
final int status = data.getStatus();
if (status == STATUS_INVALIDATED || status == STATUS_DESTROYED) {
// this instance is already 'invalid', get outta here
return;
}
ItemState transientState = getOrCreateTransientItemState();
if (transientState.getStatus() == ItemState.STATUS_NEW) {
// this is a 'new' item, simply dispose the transient state
// (it is no longer used); this will indirectly (through
// stateDiscarded listener method) invalidate this instance permanently
stateMgr.disposeTransientItemState(transientState);
} else {
// this is an 'existing' item (i.e. it is backed by persistent
// state), mark it as 'removed'
transientState.setStatus(ItemState.STATUS_EXISTING_REMOVED);
// transfer the transient state to the attic
stateMgr.moveTransientItemStateToAttic(transientState);
// set state of this instance to 'invalid'
data.setStatus(STATUS_INVALIDATED);
// notify the manager that this instance has been
// temporarily invalidated
itemMgr.itemInvalidated(id, data);
}
}
/**
* Returns the item-state associated with this <code>Item</code>.
*
* @return state associated with this <code>Item</code>
*/
ItemState getItemState() {
return data.getState();
}
/**
* Return the id of this <code>Item</code>.
*
* @return the id of this <code>Item</code>
*/
public ItemId getId() {
return id;
}
/**
* Returns the primary path to this <code>Item</code>.
*
* @return the primary path to this <code>Item</code>
*/
public Path getPrimaryPath() throws RepositoryException {
return sessionContext.getHierarchyManager().getPath(id);
}
/**
* Failsafe mapping of internal <code>id</code> to JCR path for use in
* diagnostic output, error messages etc.
*
* @return JCR path or some fallback value
*/
public String safeGetJCRPath() {
return itemMgr.safeGetJCRPath(id);
}
/**
* Same as <code>{@link Item#getName()}</code> except that
* this method returns a <code>Name</code> instead of a
* <code>String</code>.
*
* @return the name of this item as <code>Name</code>
* @throws RepositoryException if an error occurs.
*/
public abstract Name getQName() throws RepositoryException;
/**
* Utility method that converts the given string into a qualified JCR name.
*
* @param name name string
* @return qualified name
* @throws RepositoryException if the given name is invalid
*/
protected Name getQName(String name) throws RepositoryException {
return sessionContext.getQName(name);
}
/**
* Utility method that returns the value factory of this session.
*
* @return value factory
* @throws RepositoryException if the value factory is not available
*/
protected ValueFactory getValueFactory() throws RepositoryException {
return getSession().getValueFactory();
}
/**
* Utility method that converts the given strings into JCR values of the
* given type
*
* @param values value strings
* @param type value type
* @return JCR values
* @throws RepositoryException if the values can not be converted
*/
protected Value[] getValues(String[] values, int type)
throws RepositoryException {
if (values != null) {
return ValueHelper.convert(values, type, getValueFactory());
} else {
return null;
}
}
/**
* Utility method that returns the type of the first of the given values,
* or {@link PropertyType#UNDEFINED} when given no values.
*
* @param values given values, or <code>null</code>
* @return value type, or {@link PropertyType#UNDEFINED}
*/
protected int getType(Value[] values) {
if (values != null) {
for (Value value : values) {
if (value != null) {
return value.getType();
}
}
}
return PropertyType.UNDEFINED;
}
//-----------------------------------------------------------------< Item >
/**
* {@inheritDoc}
*/
public abstract void accept(ItemVisitor visitor)
throws RepositoryException;
/**
* {@inheritDoc}
*/
public abstract boolean isNode();
/**
* {@inheritDoc}
*/
public abstract String getName() throws RepositoryException;
/**
* {@inheritDoc}
*/
public abstract Node getParent()
throws ItemNotFoundException, AccessDeniedException, RepositoryException;
/**
* {@inheritDoc}
*/
public boolean isNew() {
final ItemState state = getItemState();
return state.isTransient() && state.getOverlayedState() == null;
}
/**
* checks if this item is new. running outside of transactions, this
* is the same as {@link #isNew()} but within a transaction an item can
* be saved but not yet persisted.
*/
protected boolean isTransactionalNew() {
final ItemState state = getItemState();
return state.getStatus() == ItemState.STATUS_NEW;
}
/**
* {@inheritDoc}
*/
public boolean isModified() {
final ItemState state = getItemState();
return state.isTransient() && state.getOverlayedState() != null;
}
/**
* {@inheritDoc}
*/
public void remove() throws RepositoryException {
perform(new ItemRemoveOperation(this, true));
}
/**
* {@inheritDoc}
*/
public void save() throws RepositoryException {
perform(new ItemSaveOperation(getItemState()));
}
/**
* {@inheritDoc}
*/
public void refresh(boolean keepChanges) throws RepositoryException {
perform(new ItemRefreshOperation(getItemState(), keepChanges));
}
/**
* {@inheritDoc}
*/
public Item getAncestor(final int degree) throws RepositoryException {
return perform(new SessionOperation<Item>() {
public Item perform(SessionContext context)
throws RepositoryException {
if (degree == 0) {
return context.getItemManager().getRootNode();
}
try {
// Path.getAncestor requires relative degree, i.e. we need
// to convert absolute to relative ancestor degree
Path path = getPrimaryPath();
int relDegree = path.getAncestorCount() - degree;
if (relDegree < 0) {
throw new ItemNotFoundException();
} else if (relDegree == 0) {
return ItemImpl.this; // shortcut
}
Path ancestorPath = path.getAncestor(relDegree);
return context.getItemManager().getNode(ancestorPath);
} catch (PathNotFoundException e) {
throw new ItemNotFoundException("Ancestor not found", e);
}
}
public String toString() {
return "item.getAncestor(" + degree + ")";
}
});
}
/**
* {@inheritDoc}
*/
public String getPath() throws RepositoryException {
return perform(new SessionOperation<String>() {
public String perform(SessionContext context)
throws RepositoryException {
return context.getJCRPath(getPrimaryPath());
}
public String toString() {
return "item.getPath()";
}
});
}
/**
* {@inheritDoc}
*/
public int getDepth() throws RepositoryException {
return perform(new SessionOperation<Integer>() {
public Integer perform(SessionContext context)
throws RepositoryException {
ItemState state = getItemState();
if (state.getParentId() == null) {
return 0; // shortcut
} else {
return context.getHierarchyManager().getDepth(id);
}
}
public String toString() {
return "item.getDepth()";
}
});
}
/**
* Returns the session associated with this item.
* <p>
* Since Jackrabbit 1.4 it is safe to use this method regardless
* of item state.
*
* @see <a href="http://issues.apache.org/jira/browse/JCR-911">Issue JCR-911</a>
* @return current session
*/
public Session getSession() {
return sessionContext.getSessionImpl();
}
/**
* {@inheritDoc}
*/
public boolean isSame(Item otherItem) throws RepositoryException {
// check state of this instance
sanityCheck();
if (this == otherItem) {
return true;
}
if (otherItem instanceof ItemImpl) {
ItemImpl other = (ItemImpl) otherItem;
return id.equals(other.id)
&& getSession().getWorkspace().getName().equals(
other.getSession().getWorkspace().getName());
}
return false;
}
//--------------------------------------------------------------< Object >
/**
* Returns the({@link #safeGetJCRPath() safe}) path of this item for use
* in diagnostic output.
*
* @return "/path/to/item"
*/
public String toString() {
return safeGetJCRPath();
}
}