/*
* 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.version;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.version.VersionException;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.id.ItemId;
import org.apache.jackrabbit.core.id.NodeId;
import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
import org.apache.jackrabbit.core.observation.EventStateCollection;
import org.apache.jackrabbit.core.observation.EventStateCollectionFactory;
import org.apache.jackrabbit.core.state.ChangeLog;
import org.apache.jackrabbit.core.state.ItemState;
import org.apache.jackrabbit.core.state.ItemStateCacheFactory;
import org.apache.jackrabbit.core.state.ItemStateException;
import org.apache.jackrabbit.core.state.ItemStateListener;
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.XAItemStateManager;
import org.apache.jackrabbit.core.virtual.VirtualItemStateProvider;
import org.apache.jackrabbit.core.virtual.VirtualNodeState;
import org.apache.jackrabbit.core.virtual.VirtualPropertyState;
import org.apache.jackrabbit.data.core.InternalXAResource;
import org.apache.jackrabbit.data.core.TransactionContext;
import org.apache.jackrabbit.data.core.TransactionException;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
/**
* Implementation of a {@link InternalVersionManager} that works in an XA environment.
* Works as a filter between a version manager client and the global version
* manager.
*/
public class InternalXAVersionManager extends InternalVersionManagerBase
implements EventStateCollectionFactory, VirtualItemStateProvider, InternalXAResource {
/**
* Attribute name for associated change log.
*/
private static final String CHANGE_LOG_ATTRIBUTE_NAME = "XAVersionManager.ChangeLog";
/**
* Attribute name for items.
*/
private static final String ITEMS_ATTRIBUTE_NAME = "VersionItems";
/**
* Repository version manager.
*/
private final InternalVersionManagerImpl vMgr;
/**
* The session that uses this version manager.
*/
private SessionImpl session;
/**
* Items that have been modified and are part of the XA environment.
*/
private Map<NodeId, InternalVersionItem> xaItems;
/**
* flag that indicates if the version manager was locked during prepare
*/
private boolean vmgrLocked = false;
/**
* The global write lock on the version manager.
*/
private VersioningLock.WriteLock vmgrLock;
/**
* Persistent root node of the version histories.
*/
private final NodeStateEx historyRoot;
/**
* Persistent root node of the activities.
*/
private final NodeStateEx activitiesRoot;
/**
* Creates a new instance of this class.
*
* @param vMgr the underlying version manager
* @param ntReg node type registry
* @param session the session
* @param cacheFactory cache factory
* @throws RepositoryException if a an error occurs
*/
public InternalXAVersionManager(InternalVersionManagerImpl vMgr, NodeTypeRegistry ntReg,
SessionImpl session, ItemStateCacheFactory cacheFactory)
throws RepositoryException {
super(ntReg, vMgr.historiesId, vMgr.activitiesId, vMgr.getNodeIdFactory());
this.vMgr = vMgr;
this.session = session;
this.stateMgr = XAItemStateManager.createInstance(vMgr.getSharedStateMgr(),
this, CHANGE_LOG_ATTRIBUTE_NAME, cacheFactory);
NodeState state;
try {
state = (NodeState) stateMgr.getItemState(historiesId);
} catch (ItemStateException e) {
throw new RepositoryException("Unable to retrieve history root", e);
}
this.historyRoot = new NodeStateEx(stateMgr, ntReg, state, NameConstants.JCR_VERSIONSTORAGE);
try {
state = (NodeState) stateMgr.getItemState(activitiesId);
} catch (ItemStateException e) {
throw new RepositoryException("Unable to retrieve activities root", e);
}
this.activitiesRoot = new NodeStateEx(stateMgr, ntReg, state, NameConstants.JCR_ACTIVITIES);
}
//------------------------------------------< EventStateCollectionFactory >
/**
* {@inheritDoc}
*/
public EventStateCollection createEventStateCollection()
throws RepositoryException {
return vMgr.getEscFactory().createEventStateCollection(session);
}
//-------------------------------------------------------< InternalVersionManager >
/**
* {@inheritDoc}
*/
public VirtualItemStateProvider getVirtualItemStateProvider() {
return this;
}
/**
* {@inheritDoc}
*/
@Override
protected VersionHistoryInfo createVersionHistory(Session session,
NodeState node,
NodeId copiedFrom)
throws RepositoryException {
if (isInXA()) {
NodeStateEx state = internalCreateVersionHistory(node, copiedFrom);
InternalVersionHistory history =
new InternalVersionHistoryImpl(vMgr, state);
xaItems.put(state.getNodeId(), history);
Name root = NameConstants.JCR_ROOTVERSION;
return new VersionHistoryInfo(
state.getNodeId(),
state.getState().getChildNodeEntry(root, 1).getId());
}
return vMgr.createVersionHistory(session, node, copiedFrom);
}
/**
* {@inheritDoc}
*/
public NodeId createActivity(Session session, String title)
throws RepositoryException {
if (isInXA()) {
NodeStateEx state = internalCreateActivity(title);
InternalActivityImpl activity =
new InternalActivityImpl(vMgr, state);
xaItems.put(state.getNodeId(), activity);
return state.getNodeId();
} else {
return vMgr.createActivity(session, title);
}
}
/**
* {@inheritDoc}
*/
public void removeActivity(Session session, NodeId nodeId)
throws RepositoryException {
if (isInXA()) {
InternalActivityImpl act = (InternalActivityImpl) getItem(nodeId);
internalRemoveActivity(act);
}
vMgr.removeActivity(session, nodeId);
}
/**
* {@inheritDoc}
* <p>
* Before modifying activity, make a local copy of it.
*/
@Override
protected void internalRemoveActivity(InternalActivityImpl activity)
throws VersionException, RepositoryException {
if (activity.getVersionManager() != this) {
activity = makeLocalCopy(activity);
xaItems.put(activity.getId(), activity);
}
super.internalRemoveActivity(activity);
}
/**
* {@inheritDoc}
*/
public NodeId canCheckout(NodeStateEx state, NodeId activityId) throws RepositoryException {
return vMgr.canCheckout(state, activityId);
}
/**
* {@inheritDoc}
*/
public InternalVersion checkin(
Session session, NodeStateEx node, Calendar created)
throws RepositoryException {
if (isInXA()) {
return checkin(node, created);
} else {
return vMgr.checkin(session, node, created);
}
}
/**
* {@inheritDoc}
*/
public void removeVersion(Session session, InternalVersionHistory history,
Name versionName)
throws RepositoryException {
if (isInXA()) {
internalRemoveVersion((InternalVersionHistoryImpl) history, versionName);
} else {
vMgr.removeVersion(session, history, versionName);
}
}
/**
* {@inheritDoc}
*/
public void removeVersionHistory(Session session, InternalVersionHistory history)
throws RepositoryException {
if (isInXA()) {
internalRemoveVersionHistory((InternalVersionHistoryImpl) history);
} else {
vMgr.removeVersionHistory(session, history);
}
}
/**
* {@inheritDoc}
*/
public InternalVersion setVersionLabel(Session session,
InternalVersionHistory history,
Name version,
Name label, boolean move)
throws RepositoryException {
if (isInXA()) {
return setVersionLabel((InternalVersionHistoryImpl) history,
version, label, move);
} else {
return vMgr.setVersionLabel(session, history, version, label, move);
}
}
/**
* {@inheritDoc}
*/
public void close() throws Exception {
stateMgr.dispose();
}
//---------------------------------------------< VirtualItemStateProvider >
/**
* {@inheritDoc}
*/
public boolean isVirtualRoot(ItemId id) {
return false;
}
/**
* {@inheritDoc}
*/
public NodeId getVirtualRootId() {
// never used
return null;
}
public NodeId[] getVirtualRootIds() {
// never used
return null;
}
/**
* {@inheritDoc}
*/
public VirtualPropertyState createPropertyState(VirtualNodeState parent,
Name name, int type,
boolean multiValued)
throws RepositoryException {
throw new IllegalStateException("Read-only");
}
/**
* {@inheritDoc}
*/
public VirtualNodeState createNodeState(VirtualNodeState parent, Name name,
NodeId id, Name nodeTypeName)
throws RepositoryException {
throw new IllegalStateException("Read-only");
}
/**
* {@inheritDoc}
*/
public boolean setNodeReferences(ChangeLog references) {
ChangeLog changeLog = ((XAItemStateManager) stateMgr).getChangeLog();
if (changeLog != null) {
for (NodeReferences refs : references.modifiedRefs()) {
changeLog.modified(refs);
}
return true;
} else {
return false;
}
}
/**
* {@inheritDoc}
* <p>
* Return item states for changes only. Global version manager will return
* other items.
*/
public ItemState getItemState(ItemId id)
throws NoSuchItemStateException, ItemStateException {
ChangeLog changeLog = ((XAItemStateManager) stateMgr).getChangeLog();
if (changeLog != null) {
return changeLog.get(id);
}
throw new NoSuchItemStateException("State not in change log: " + id);
}
/**
* {@inheritDoc}
*/
public boolean hasItemState(ItemId id) {
ChangeLog changeLog = ((XAItemStateManager) stateMgr).getChangeLog();
if (changeLog != null) {
return changeLog.has(id);
}
return false;
}
/**
* {@inheritDoc}
*/
public NodeReferences getNodeReferences(NodeId id)
throws NoSuchItemStateException, ItemStateException {
ChangeLog changeLog = ((XAItemStateManager) stateMgr).getChangeLog();
if (changeLog != null) {
return changeLog.getReferencesTo(id);
}
return null;
}
/**
* {@inheritDoc}
*/
public boolean hasNodeReferences(NodeId id) {
ChangeLog changeLog = ((XAItemStateManager) stateMgr).getChangeLog();
if (changeLog != null) {
return changeLog.getReferencesTo(id) != null;
}
return false;
}
/**
* {@inheritDoc}
* <p>
* Not needed.
*/
public void addListener(ItemStateListener listener) {
}
/**
* {@inheritDoc}
* <p>
* Not needed.
*/
public void removeListener(ItemStateListener listener) {
}
//-----------------------------------------------< InternalVersionManagerBase >
/**
* {@inheritDoc}
*/
@Override
protected NodeStateEx getHistoryRoot() {
return historyRoot;
}
/**
* {@inheritDoc}
*/
@Override
protected NodeStateEx getActivitiesRoot() {
return activitiesRoot;
}
/**
* {@inheritDoc}
*/
@Override
protected InternalVersionItem getItem(NodeId id) throws RepositoryException {
InternalVersionItem item = null;
if (xaItems != null) {
item = xaItems.get(id);
}
if (item == null) {
item = vMgr.getItem(id);
}
return item;
}
/**
* {@inheritDoc}
*/
@Override
protected boolean hasItem(NodeId id) {
if (xaItems != null && xaItems.containsKey(id)) {
return true;
}
return vMgr.hasItem(id);
}
/**
* {@inheritDoc}
*/
@Override
protected boolean hasItemReferences(NodeId id)
throws RepositoryException {
return session.getNodeById(id).getReferences().hasNext();
}
/**
* {@inheritDoc}
*/
@Override
protected NodeStateEx getNodeStateEx(NodeId parentNodeId)
throws RepositoryException {
try {
NodeState state = (NodeState) stateMgr.getItemState(parentNodeId);
return new NodeStateEx(stateMgr, ntReg, state, null);
} catch (ItemStateException e) {
throw new RepositoryException(e);
}
}
/**
* {@inheritDoc}
* <p>
* Before modifying version history given, make a local copy of it.
*/
@Override
protected InternalVersion internalCheckin(
InternalVersionHistoryImpl history,
NodeStateEx node, boolean simple, Calendar created)
throws RepositoryException {
if (history.getVersionManager() != this) {
history = makeLocalCopy(history);
xaItems.put(history.getId(), history);
}
InternalVersion version =
super.internalCheckin(history, node, simple, created);
NodeId frozenNodeId = version.getFrozenNodeId();
InternalVersionItem frozenNode = createInternalVersionItem(frozenNodeId);
if (frozenNode != null) {
xaItems.put(frozenNodeId, frozenNode);
}
return version;
}
/**
* {@inheritDoc}
* <p>
* Before modifying version history given, make a local copy of it.
*/
@Override
protected void internalRemoveVersion(InternalVersionHistoryImpl history, Name name)
throws VersionException, RepositoryException {
if (history.getVersionManager() != this) {
history = makeLocalCopy(history);
xaItems.put(history.getId(), history);
// also put 'successor' and 'predecessor' version items to xaItem sets
InternalVersion v = history.getVersion(name);
for (InternalVersion v1 : v.getSuccessors()) {
xaItems.put(v1.getId(), v1);
}
for (InternalVersion v1 : v.getPredecessors()) {
xaItems.put(v1.getId(), v1);
}
}
super.internalRemoveVersion(history, name);
}
/**
* {@inheritDoc}
* <p>
* Before modifying version history given, make a local copy of it.
*/
@Override
protected InternalVersion setVersionLabel(InternalVersionHistoryImpl history,
Name version, Name label,
boolean move)
throws RepositoryException {
if (history.getVersionManager() != this) {
history = makeLocalCopy(history);
xaItems.put(history.getId(), history);
}
return super.setVersionLabel(history, version, label, move);
}
/**
* {@inheritDoc}
* <p>
* Put the version object into our cache.
*/
@Override
protected void versionCreated(InternalVersion version) {
xaItems.put(version.getId(), version);
}
/**
* {@inheritDoc}
* <p>
* Remove the version object from our cache.
*/
@Override
protected void versionDestroyed(InternalVersion version) {
xaItems.remove(version.getId());
}
//-------------------------------------------------------------------< XA >
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public void associate(TransactionContext tx) {
((XAItemStateManager) stateMgr).associate(tx);
Map<NodeId, InternalVersionItem> xaItems = null;
if (tx != null) {
xaItems = (Map<NodeId, InternalVersionItem>) tx.getAttribute(ITEMS_ATTRIBUTE_NAME);
if (xaItems == null) {
xaItems = new HashMap<NodeId, InternalVersionItem>();
tx.setAttribute(ITEMS_ATTRIBUTE_NAME, xaItems);
}
}
this.xaItems = xaItems;
}
/**
* {@inheritDoc}
* <p>
* Delegate the call to our XA item state manager.
*/
public void beforeOperation(TransactionContext tx) {
((XAItemStateManager) stateMgr).beforeOperation(tx);
}
/**
* {@inheritDoc}
* <p>
* Delegate the call to our XA item state manager.
*/
public void prepare(TransactionContext tx) throws TransactionException {
if (vmgrLocked) {
((XAItemStateManager) stateMgr).prepare(tx);
}
}
/**
* {@inheritDoc}
* <p>
* Delegate the call to our XA item state manager. If successful, inform
* global repository manager to update its caches.
*/
@SuppressWarnings("unchecked")
public void commit(TransactionContext tx) throws TransactionException {
if (vmgrLocked) {
((XAItemStateManager) stateMgr).commit(tx);
Map<NodeId, InternalVersionItem> xaItems =
(Map<NodeId, InternalVersionItem>) tx.getAttribute(ITEMS_ATTRIBUTE_NAME);
vMgr.itemsUpdated(xaItems.values());
}
}
/**
* {@inheritDoc}
* <p>
* Delegate the call to our XA item state manager.
*/
public void rollback(TransactionContext tx) {
if (vmgrLocked) {
((XAItemStateManager) stateMgr).rollback(tx);
}
}
/**
* {@inheritDoc}
* <p>
* Delegate the call to our XA item state manager.
*/
public void afterOperation(TransactionContext tx) {
((XAItemStateManager) stateMgr).afterOperation(tx);
}
/**
* Returns an {@link InternalXAResource} that acquires a write lock on the
* version manager in {@link InternalXAResource#prepare(TransactionContext)}.
*
* @return an internal XA resource.
*/
public InternalXAResource getXAResourceBegin() {
return new InternalXAResource() {
public void associate(TransactionContext tx) {
}
public void beforeOperation(TransactionContext tx) {
}
public void prepare(TransactionContext tx) {
vmgrLock = vMgr.acquireWriteLock();
vmgrLocked = true;
}
public void commit(TransactionContext tx) {
// JCR-2712: Ensure that the transaction is prepared
if (!vmgrLocked) {
prepare(tx);
}
}
public void rollback(TransactionContext tx) {
// JCR-2712: Ensure that the transaction is prepared
if (!vmgrLocked) {
prepare(tx);
}
}
public void afterOperation(TransactionContext tx) {
}
};
}
/**
* Returns an {@link InternalXAResource} that releases the write lock on the
* version manager in {@link InternalXAResource#commit(TransactionContext)}
* or {@link InternalXAResource#rollback(TransactionContext)}.
*
* @return an internal XA resource.
*/
public InternalXAResource getXAResourceEnd() {
return new InternalXAResource() {
public void associate(TransactionContext tx) {
}
public void beforeOperation(TransactionContext tx) {
}
public void prepare(TransactionContext tx) {
}
public void commit(TransactionContext tx) {
internalReleaseWriteLock();
}
public void rollback(TransactionContext tx) {
internalReleaseWriteLock();
}
public void afterOperation(TransactionContext tx) {
}
private void internalReleaseWriteLock() {
if (vmgrLocked) {
vmgrLock.release();
vmgrLocked = false;
}
}
};
}
//-------------------------------------------------------< implementation >
/**
* Return a flag indicating whether this version manager is currently
* associated with an XA transaction.
* @return <code>true</code> if the version manager is in a transaction
*/
private boolean isInXA() {
return xaItems != null;
}
/**
* Make a local copy of an internal version item. This will recreate the
* (global) version item with state information from our own state
* manager.
* @param history source
* @return the new copy
* @throws RepositoryException if an error occurs
*/
private InternalVersionHistoryImpl makeLocalCopy(InternalVersionHistoryImpl history)
throws RepositoryException {
VersioningLock.ReadLock lock = acquireReadLock();
try {
NodeState state = (NodeState) stateMgr.getItemState(history.getId());
NodeStateEx stateEx = new NodeStateEx(stateMgr, ntReg, state, null);
return new InternalVersionHistoryImpl(this, stateEx);
} catch (ItemStateException e) {
throw new RepositoryException("Unable to make local copy", e);
} finally {
lock.release();
}
}
/**
* Make a local copy of an internal version item. This will recreate the
* (global) version item with state information from our own state
* manager.
* @param act source
* @return the new copy
* @throws RepositoryException if an error occurs
*/
private InternalActivityImpl makeLocalCopy(InternalActivityImpl act)
throws RepositoryException {
VersioningLock.ReadLock lock = acquireReadLock();
try {
NodeState state = (NodeState) stateMgr.getItemState(act.getId());
NodeStateEx stateEx = new NodeStateEx(stateMgr, ntReg, state, null);
return new InternalActivityImpl(this, stateEx);
} catch (ItemStateException e) {
throw new RepositoryException("Unable to make local copy", e);
} finally {
lock.release();
}
}
/**
* Return a flag indicating whether an internal version item belongs to
* a different XA environment.
* @param item the item to check
* @return <code>true</code> if in a different env
*/
boolean differentXAEnv(InternalVersionItemImpl item) {
if (item.getVersionManager() == this) {
if (xaItems == null || !xaItems.containsKey(item.getId())) {
return true;
}
}
return false;
}
}