| /* |
| * Copyright (c) 2007-2015 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.CDOBranch; |
| import org.eclipse.emf.cdo.common.branch.CDOBranchChangedEvent.ChangeKind; |
| import org.eclipse.emf.cdo.common.branch.CDOBranchManager; |
| 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.protocol.CDOProtocol.CommitNotificationInfo; |
| 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.CDORevisionProvider; |
| import org.eclipse.emf.cdo.common.revision.CDORevisionUtil; |
| import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta; |
| import org.eclipse.emf.cdo.common.security.CDOPermission; |
| import org.eclipse.emf.cdo.internal.common.commit.DelegatingCommitInfo; |
| import org.eclipse.emf.cdo.internal.server.bundle.OM; |
| import org.eclipse.emf.cdo.server.IPermissionManager; |
| 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.InternalRepository; |
| 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.AdapterUtil; |
| 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.net4j.util.registry.HashMapRegistry; |
| import org.eclipse.net4j.util.registry.IRegistry; |
| |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| |
| import java.text.MessageFormat; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| 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 boolean openOnClientSide; |
| |
| private long firstUpdateTime; |
| |
| private long lastUpdateTime; |
| |
| @ExcludeFromDump |
| private Object lastUpdateTimeLock = new Object(); |
| |
| private Map<Integer, InternalView> views = new HashMap<Integer, InternalView>(); |
| |
| private AtomicInteger lastTempViewID = new AtomicInteger(); |
| |
| private final IRegistry<String, Object> properties = new HashMapRegistry<String, Object>() |
| { |
| @Override |
| public void setAutoCommit(boolean autoCommit) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| |
| @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; |
| } |
| |
| public final IRegistry<String, Object> properties() |
| { |
| return properties; |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| public CDOCommonSession getContainer() |
| { |
| return this; |
| } |
| |
| public InternalSessionManager getManager() |
| { |
| return manager; |
| } |
| |
| public CDOBranchManager getBranchManager() |
| { |
| return manager.getRepository().getBranchManager(); |
| } |
| |
| public ISessionProtocol getProtocol() |
| { |
| return protocol; |
| } |
| |
| public int getSessionID() |
| { |
| return sessionID; |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| public String getUserID() |
| { |
| return userID; |
| } |
| |
| public void setUserID(String userID) |
| { |
| this.userID = 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; |
| } |
| |
| @Deprecated |
| public long getLastUpdateTime() |
| { |
| synchronized (lastUpdateTimeLock) |
| { |
| return lastUpdateTime; |
| } |
| } |
| |
| public long getFirstUpdateTime() |
| { |
| return firstUpdateTime; |
| } |
| |
| public void setFirstUpdateTime(long firstUpdateTime) |
| { |
| this.firstUpdateTime = firstUpdateTime; |
| } |
| |
| public boolean isOpenOnClientSide() |
| { |
| return openOnClientSide; |
| } |
| |
| public void setOpenOnClientSide() |
| { |
| openOnClientSide = true; |
| manager.openedOnClientSide(this); |
| } |
| |
| public InternalView[] getElements() |
| { |
| checkActive(); |
| return getViews(); |
| } |
| |
| @Override |
| public boolean isEmpty() |
| { |
| checkActive(); |
| |
| synchronized (views) |
| { |
| return views.isEmpty(); |
| } |
| } |
| |
| public InternalView[] getViews() |
| { |
| checkActive(); |
| return getViewsArray(); |
| } |
| |
| private InternalView[] getViewsArray() |
| { |
| synchronized (views) |
| { |
| return views.values().toArray(new InternalView[views.size()]); |
| } |
| } |
| |
| public InternalView getView(int viewID) |
| { |
| checkActive(); |
| |
| synchronized (views) |
| { |
| 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(); |
| |
| synchronized (views) |
| { |
| views.put(viewID, view); |
| } |
| |
| fireElementAddedEvent(view); |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| public void viewClosed(InternalView view) |
| { |
| int viewID = view.getViewID(); |
| InternalView removedView; |
| |
| synchronized (views) |
| { |
| removedView = views.remove(viewID); |
| } |
| |
| if (removedView == 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(); |
| for (EStructuralFeature feature : revision.getClassInfo().getAllPersistentFeatures()) |
| { |
| // 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 CDOPermission getPermission(CDORevision revision, CDOBranchPoint securityContext) |
| { |
| IPermissionManager permissionManager = manager.getPermissionManager(); |
| if (permissionManager != null) |
| { |
| return permissionManager.getPermission(revision, securityContext, this); |
| } |
| |
| return CDORevision.PERMISSION_PROVIDER.getPermission(revision, securityContext); |
| } |
| |
| 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); |
| } |
| } |
| |
| @Deprecated |
| public void sendBranchNotification(InternalCDOBranch branch) throws Exception |
| { |
| sendBranchNotification(branch, ChangeKind.CREATED); |
| } |
| |
| public void sendBranchNotification(InternalCDOBranch branch, ChangeKind changeKind) throws Exception |
| { |
| if (protocol != null) |
| { |
| protocol.sendBranchNotification(branch, changeKind); |
| } |
| } |
| |
| @Deprecated |
| public void sendCommitNotification(CDOCommitInfo commitInfo) throws Exception |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| public void sendCommitNotification(CDOCommitInfo commitInfo, boolean clearResourcePathCache) throws Exception |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public void sendCommitNotification(CommitNotificationInfo notificationInfo) throws Exception |
| { |
| if (protocol == null) |
| { |
| return; |
| } |
| |
| if (!isPassiveUpdateEnabled()) |
| { |
| return; |
| } |
| |
| byte securityImpact = notificationInfo.getSecurityImpact(); |
| if (securityImpact == CommitNotificationInfo.IMPACT_PERMISSIONS) |
| { |
| IPermissionManager permissionManager = manager.getPermissionManager(); |
| Set<? extends Object> impactedRules = notificationInfo.getImpactedRules(); |
| |
| if (!permissionManager.hasAnyRule(this, impactedRules)) |
| { |
| securityImpact = CommitNotificationInfo.IMPACT_NONE; |
| } |
| } |
| |
| CommitInfo sessionCommitInfo = new CommitInfo(notificationInfo); |
| |
| CommitNotificationInfo sessionNotificationInfo = new CommitNotificationInfo(); |
| sessionNotificationInfo.setSender(notificationInfo.getSender()); |
| sessionNotificationInfo.setCommitInfo(sessionCommitInfo); |
| sessionNotificationInfo.setRevisionProvider(notificationInfo.getRevisionProvider()); |
| sessionNotificationInfo.setClearResourcePathCache(notificationInfo.isClearResourcePathCache()); |
| sessionNotificationInfo.setNewPermissions(sessionCommitInfo.getNewPermissions()); |
| sessionNotificationInfo.setSecurityImpact(securityImpact); |
| |
| protocol.sendCommitNotification(sessionNotificationInfo); |
| |
| synchronized (lastUpdateTimeLock) |
| { |
| CDOCommitInfo originalCommitInfo = notificationInfo.getCommitInfo(); |
| lastUpdateTime = originalCommitInfo.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()) |
| { |
| try |
| { |
| if (view.options().isLockNotificationEnabled()) |
| { |
| CDOBranch affectedBranch = lockChangeInfo.getBranch(); |
| if (view.getBranch() == affectedBranch || affectedBranch == null) |
| { |
| protocol.sendLockNotification(lockChangeInfo); |
| break; |
| } |
| } |
| } |
| catch (Exception ex) |
| { |
| if (!view.isClosed()) |
| { |
| OM.LOG.warn("A problem occured while notifying view " + view, ex); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private boolean hasSubscription(CDOID id, InternalView[] views) |
| { |
| for (InternalView view : views) |
| { |
| try |
| { |
| if (view.hasSubscription(id)) |
| { |
| return true; |
| } |
| } |
| catch (Exception ex) |
| { |
| if (!view.isClosed()) |
| { |
| OM.LOG.warn("A problem occured while checking subscriptions of view " + view, ex); |
| } |
| } |
| } |
| |
| 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); |
| } |
| } |
| |
| @SuppressWarnings({ "unchecked", "rawtypes" }) |
| public Object getAdapter(Class adapter) |
| { |
| return AdapterUtil.adapt(this, adapter, false); |
| } |
| |
| @Override |
| public String toString() |
| { |
| String name = "unknown"; |
| if (manager != null) |
| { |
| InternalRepository repository = manager.getRepository(); |
| if (repository != null) |
| { |
| name = repository.getName(); |
| } |
| } |
| |
| if (userID != null && userID.length() != 0) |
| { |
| name = userID + "@" + name; |
| } |
| |
| return MessageFormat.format("Session{0} [{1}]", sessionID, name); //$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(); |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private final class CommitInfo extends DelegatingCommitInfo |
| { |
| private final CDOCommitInfo delegate; |
| |
| private final CDORevisionProvider revisionProvider; |
| |
| private final InternalView[] views; |
| |
| private final IPermissionManager permissionManager; |
| |
| private final Map<CDOID, CDOPermission> newPermissions; |
| |
| private final boolean additions; |
| |
| private final boolean changes; |
| |
| public CommitInfo(CommitNotificationInfo notificationInfo) |
| { |
| delegate = notificationInfo.getCommitInfo(); |
| revisionProvider = notificationInfo.getRevisionProvider(); |
| |
| views = getViews(); |
| permissionManager = manager.getPermissionManager(); |
| if (permissionManager != null) |
| { |
| newPermissions = CDOIDUtil.createMap(); |
| } |
| else |
| { |
| newPermissions = null; |
| } |
| |
| PassiveUpdateMode passiveUpdateMode = getPassiveUpdateMode(); |
| additions = passiveUpdateMode == PassiveUpdateMode.ADDITIONS; |
| changes = additions || passiveUpdateMode == PassiveUpdateMode.CHANGES; |
| } |
| |
| @Override |
| protected CDOCommitInfo getDelegate() |
| { |
| return delegate; |
| } |
| |
| protected void addNewPermission(CDOID id, CDOPermission permission) |
| { |
| newPermissions.put(id, permission); |
| } |
| |
| public Map<CDOID, CDOPermission> getNewPermissions() |
| { |
| return newPermissions; |
| } |
| |
| @Override |
| public List<CDOIDAndVersion> getNewObjects() |
| { |
| final List<CDOIDAndVersion> newObjects = super.getNewObjects(); |
| return new IndexedList<CDOIDAndVersion>() |
| { |
| @Override |
| public CDOIDAndVersion get(int index) |
| { |
| CDORevision revision = (CDORevision)newObjects.get(index); |
| if (additions) |
| { |
| if (permissionManager == null) |
| { |
| // Return full revision |
| return revision; |
| } |
| |
| CDOPermission permission = permissionManager.getPermission(revision, delegate, Session.this); |
| CDOID id = revision.getID(); |
| addNewPermission(id, permission); |
| |
| if (permission != CDOPermission.NONE) |
| { |
| // Return full revision |
| return revision; |
| } |
| } |
| |
| // Prevent sending full revision by copying the id and version |
| return CDOIDUtil.createIDAndVersion(revision); |
| } |
| |
| @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) |
| { |
| CDORevisionDelta revisionDelta = (CDORevisionDelta)changedObjects.get(index); |
| CDOID id = revisionDelta.getID(); |
| |
| if (changes || hasSubscription(id, views)) |
| { |
| if (permissionManager == null) |
| { |
| // Return full delta |
| return revisionDelta; |
| } |
| |
| if (revisionProvider == null) |
| { |
| // Return full delta |
| return revisionDelta; |
| } |
| |
| CDORevision newRevision = revisionProvider.getRevision(id); |
| CDOPermission permission = permissionManager.getPermission(newRevision, delegate, Session.this); |
| addNewPermission(id, permission); |
| |
| if (permission != CDOPermission.NONE) |
| { |
| // Return full delta |
| return revisionDelta; |
| } |
| } |
| |
| // Prevent sending full delta by copying the id and version |
| return CDORevisionUtil.copyRevisionKey(revisionDelta); |
| } |
| |
| @Override |
| public int size() |
| { |
| return changedObjects.size(); |
| } |
| }; |
| } |
| } |
| } |