blob: bb0205d2ba0195416bc70c4a7aa8fd11b1eb1027 [file] [log] [blame]
/**
* Copyright (c) 2004 - 2011 Eike Stepper (Berlin, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eike Stepper - initial API and implementation
* Simon McDuff - bug 201266
* Simon McDuff - bug 230832
* Simon McDuff - bug 233490
* Simon McDuff - bug 213402
*/
package org.eclipse.emf.cdo.internal.server;
import org.eclipse.emf.cdo.common.CDOCommonRepository;
import org.eclipse.emf.cdo.common.CDOCommonSession;
import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
import org.eclipse.emf.cdo.common.commit.CDOCommitInfo;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.id.CDOIDUtil;
import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
import org.eclipse.emf.cdo.common.model.CDOModelUtil;
import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
import org.eclipse.emf.cdo.common.revision.CDOIDAndVersion;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.common.revision.CDORevisionKey;
import org.eclipse.emf.cdo.common.revision.CDORevisionUtil;
import org.eclipse.emf.cdo.internal.common.commit.DelegatingCommitInfo;
import org.eclipse.emf.cdo.server.IView;
import org.eclipse.emf.cdo.session.remote.CDORemoteSessionMessage;
import org.eclipse.emf.cdo.spi.common.branch.InternalCDOBranch;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionManager;
import org.eclipse.emf.cdo.spi.server.ISessionProtocol;
import org.eclipse.emf.cdo.spi.server.InternalSession;
import org.eclipse.emf.cdo.spi.server.InternalSessionManager;
import org.eclipse.emf.cdo.spi.server.InternalTransaction;
import org.eclipse.emf.cdo.spi.server.InternalView;
import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump;
import org.eclipse.net4j.util.collection.IndexedList;
import org.eclipse.net4j.util.container.Container;
import org.eclipse.net4j.util.event.EventUtil;
import org.eclipse.net4j.util.event.IListener;
import org.eclipse.net4j.util.lifecycle.ILifecycle;
import org.eclipse.net4j.util.lifecycle.LifecycleEventAdapter;
import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
import org.eclipse.net4j.util.om.log.OMLogger;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import java.text.MessageFormat;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Eike Stepper
*/
public class Session extends Container<IView> implements InternalSession
{
private InternalSessionManager manager;
private ISessionProtocol protocol;
private int sessionID;
private String userID;
private boolean passiveUpdateEnabled = true;
private PassiveUpdateMode passiveUpdateMode = PassiveUpdateMode.INVALIDATIONS;
private LockNotificationMode lockNotificationMode = LockNotificationMode.IF_REQUIRED_BY_VIEWS;
private long lastUpdateTime;
@ExcludeFromDump
private Object lastUpdateTimeLock = new Object();
private ConcurrentMap<Integer, InternalView> views = new ConcurrentHashMap<Integer, InternalView>();
private AtomicInteger lastTempViewID = new AtomicInteger();
@ExcludeFromDump
private IListener protocolListener = new LifecycleEventAdapter()
{
@Override
protected void onDeactivated(ILifecycle lifecycle)
{
deactivate();
}
};
private boolean subscribed;
/**
* @since 2.0
*/
public Session(InternalSessionManager manager, ISessionProtocol protocol, int sessionID, String userID)
{
this.manager = manager;
this.protocol = protocol;
this.sessionID = sessionID;
this.userID = userID;
EventUtil.addListener(protocol, protocolListener);
activate();
}
/**
* @since 2.0
*/
public Options options()
{
return this;
}
/**
* @since 2.0
*/
public CDOCommonSession getContainer()
{
return this;
}
public InternalSessionManager getManager()
{
return manager;
}
public ISessionProtocol getProtocol()
{
return protocol;
}
public int getSessionID()
{
return sessionID;
}
/**
* @since 2.0
*/
public String getUserID()
{
return userID;
}
/**
* @since 2.0
*/
public boolean isSubscribed()
{
return subscribed;
}
/**
* @since 2.0
*/
public void setSubscribed(boolean subscribed)
{
checkActive();
if (this.subscribed != subscribed)
{
this.subscribed = subscribed;
byte opcode = subscribed ? CDOProtocolConstants.REMOTE_SESSION_SUBSCRIBED
: CDOProtocolConstants.REMOTE_SESSION_UNSUBSCRIBED;
manager.sendRemoteSessionNotification(this, opcode);
}
}
/**
* @since 2.0
*/
public boolean isPassiveUpdateEnabled()
{
return passiveUpdateEnabled;
}
/**
* @since 2.0
*/
public void setPassiveUpdateEnabled(boolean passiveUpdateEnabled)
{
checkActive();
this.passiveUpdateEnabled = passiveUpdateEnabled;
}
public PassiveUpdateMode getPassiveUpdateMode()
{
return passiveUpdateMode;
}
public void setPassiveUpdateMode(PassiveUpdateMode passiveUpdateMode)
{
checkActive();
checkArg(passiveUpdateMode, "passiveUpdateMode");
this.passiveUpdateMode = passiveUpdateMode;
}
public LockNotificationMode getLockNotificationMode()
{
return lockNotificationMode;
}
public void setLockNotificationMode(LockNotificationMode lockNotificationMode)
{
checkActive();
checkArg(lockNotificationMode, "lockNotificationMode");
this.lockNotificationMode = lockNotificationMode;
}
public long getLastUpdateTime()
{
synchronized (lastUpdateTimeLock)
{
return lastUpdateTime;
}
}
public InternalView[] getElements()
{
checkActive();
return getViews();
}
@Override
public boolean isEmpty()
{
checkActive();
return views.isEmpty();
}
public InternalView[] getViews()
{
checkActive();
return getViewsArray();
}
private InternalView[] getViewsArray()
{
return views.values().toArray(new InternalView[views.size()]);
}
public InternalView getView(int viewID)
{
checkActive();
return views.get(viewID);
}
/**
* @since 2.0
*/
public InternalView openView(int viewID, CDOBranchPoint branchPoint)
{
checkActive();
if (viewID == TEMP_VIEW_ID)
{
viewID = -lastTempViewID.incrementAndGet();
}
InternalView view = new View(this, viewID, branchPoint);
view.activate();
addView(view);
return view;
}
/**
* @since 2.0
*/
public InternalTransaction openTransaction(int viewID, CDOBranchPoint branchPoint)
{
checkActive();
if (viewID == TEMP_VIEW_ID)
{
viewID = -lastTempViewID.incrementAndGet();
}
InternalTransaction transaction = new Transaction(this, viewID, branchPoint);
transaction.activate();
addView(transaction);
return transaction;
}
private void addView(InternalView view)
{
checkActive();
int viewID = view.getViewID();
views.put(viewID, view);
fireElementAddedEvent(view);
}
/**
* @since 2.0
*/
public void viewClosed(InternalView view)
{
int viewID = view.getViewID();
if (views.remove(viewID) == view)
{
view.doClose();
fireElementRemovedEvent(view);
}
}
/**
* TODO I can't see how recursion is controlled/limited
*
* @since 2.0
*/
public void collectContainedRevisions(InternalCDORevision revision, CDOBranchPoint branchPoint, int referenceChunk,
Set<CDOID> revisions, List<CDORevision> additionalRevisions)
{
InternalCDORevisionManager revisionManager = getManager().getRepository().getRevisionManager();
EClass eClass = revision.getEClass();
EStructuralFeature[] features = CDOModelUtil.getAllPersistentFeatures(eClass);
for (int i = 0; i < features.length; i++)
{
EStructuralFeature feature = features[i];
// TODO Clarify feature maps
if (feature instanceof EReference && !feature.isMany() && ((EReference)feature).isContainment())
{
Object value = revision.getValue(feature);
if (value instanceof CDOID)
{
CDOID id = (CDOID)value;
if (!CDOIDUtil.isNull(id) && !revisions.contains(id))
{
InternalCDORevision containedRevision = revisionManager.getRevision(id, branchPoint, referenceChunk,
CDORevision.DEPTH_NONE, true);
revisions.add(id);
additionalRevisions.add(containedRevision);
// Recurse
collectContainedRevisions(containedRevision, branchPoint, referenceChunk, revisions, additionalRevisions);
}
}
}
}
}
public CDOID provideCDOID(Object idObject)
{
return (CDOID)idObject;
}
public void sendRepositoryTypeNotification(CDOCommonRepository.Type oldType, CDOCommonRepository.Type newType)
throws Exception
{
if (protocol != null)
{
protocol.sendRepositoryTypeNotification(oldType, newType);
}
}
@Deprecated
public void sendRepositoryStateNotification(CDOCommonRepository.State oldState, CDOCommonRepository.State newState)
throws Exception
{
sendRepositoryStateNotification(oldState, newState, null);
}
public void sendRepositoryStateNotification(CDOCommonRepository.State oldState, CDOCommonRepository.State newState,
CDOID rootResourceID) throws Exception
{
if (protocol != null)
{
protocol.sendRepositoryStateNotification(oldState, newState, rootResourceID);
}
}
public void sendBranchNotification(InternalCDOBranch branch) throws Exception
{
if (protocol != null)
{
protocol.sendBranchNotification(branch);
}
}
public void sendCommitNotification(final CDOCommitInfo commitInfo) throws Exception
{
if (protocol == null)
{
return;
}
if (!isPassiveUpdateEnabled())
{
return;
}
final InternalView[] views = getViews();
protocol.sendCommitNotification(new DelegatingCommitInfo()
{
private final PassiveUpdateMode passiveUpdateMode = getPassiveUpdateMode();
private final boolean additions = passiveUpdateMode == PassiveUpdateMode.ADDITIONS;
private final boolean changes = passiveUpdateMode == PassiveUpdateMode.CHANGES;
@Override
protected CDOCommitInfo getDelegate()
{
return commitInfo;
}
@Override
public List<CDOIDAndVersion> getNewObjects()
{
final List<CDOIDAndVersion> newObjects = super.getNewObjects();
return new IndexedList<CDOIDAndVersion>()
{
@Override
public CDOIDAndVersion get(int index)
{
// The following will always be a CDORevision!
CDOIDAndVersion newObject = newObjects.get(index);
if (additions)
{
// Return full revisions if not in INVALIDATION mode
return newObject;
}
// Prevent sending whole revisions by copying the id and version
return CDOIDUtil.createIDAndVersion(newObject);
}
@Override
public int size()
{
return newObjects.size();
}
};
}
@Override
public List<CDORevisionKey> getChangedObjects()
{
final List<CDORevisionKey> changedObjects = super.getChangedObjects();
return new IndexedList<CDORevisionKey>()
{
@Override
public CDORevisionKey get(int index)
{
// The following will always be a CDORevisionDelta!
CDORevisionKey changedObject = changedObjects.get(index);
if (changes || additions || hasSubscription(changedObject.getID(), views))
{
return changedObject;
}
// Prevent sending whole revisions by copying the id and version
return CDORevisionUtil.copyRevisionKey(changedObject);
}
@Override
public int size()
{
return changedObjects.size();
}
};
}
});
synchronized (lastUpdateTimeLock)
{
lastUpdateTime = commitInfo.getTimeStamp();
}
}
public void sendLockNotification(CDOLockChangeInfo lockChangeInfo) throws Exception
{
if (protocol != null)
{
if (options().getLockNotificationMode() == LockNotificationMode.ALWAYS)
{
protocol.sendLockNotification(lockChangeInfo);
return;
}
if (options().getLockNotificationMode() == LockNotificationMode.IF_REQUIRED_BY_VIEWS)
{
// If this session has one (or more) views configured for this branch,
// only then do we send the lockChangeInfo.
for (InternalView view : getViews())
{
if (view.options().isLockNotificationEnabled() && view.getBranch().equals(lockChangeInfo.getBranch()))
{
protocol.sendLockNotification(lockChangeInfo);
break;
}
}
}
}
}
private boolean hasSubscription(CDOID id, InternalView[] views)
{
for (InternalView view : views)
{
if (view.hasSubscription(id))
{
return true;
}
}
return false;
}
public void sendRemoteSessionNotification(InternalSession sender, byte opcode) throws Exception
{
if (protocol != null)
{
protocol.sendRemoteSessionNotification(sender, opcode);
}
}
public void sendRemoteMessageNotification(InternalSession sender, CDORemoteSessionMessage message) throws Exception
{
if (protocol != null)
{
protocol.sendRemoteMessageNotification(sender, message);
}
}
@Override
public String toString()
{
return MessageFormat.format("Session[{0}]", sessionID); //$NON-NLS-1$
}
/**
* @since 2.0
*/
public void close()
{
LifecycleUtil.deactivate(this, OMLogger.Level.DEBUG);
}
/**
* @since 2.0
*/
public boolean isClosed()
{
return !isActive();
}
@Override
protected void doDeactivate() throws Exception
{
EventUtil.removeListener(protocol, protocolListener);
protocolListener = null;
LifecycleUtil.deactivate(protocol, OMLogger.Level.DEBUG);
protocol = null;
for (IView view : getViewsArray())
{
view.close();
}
views = null;
manager.sessionClosed(this);
manager = null;
super.doDeactivate();
}
}