| /* |
| * Copyright (c) 2009-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 226778 |
| * Simon McDuff - bug 230832 |
| * Simon McDuff - bug 233490 |
| * Simon McDuff - bug 213402 |
| * Victor Roldan Betancort - maintenance |
| * Christian W. Damus (CEA LIST) - bug 418454 |
| */ |
| package org.eclipse.emf.internal.cdo.session; |
| |
| import org.eclipse.emf.cdo.CDOState; |
| import org.eclipse.emf.cdo.common.CDOCommonRepository; |
| import org.eclipse.emf.cdo.common.branch.CDOBranch; |
| import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; |
| import org.eclipse.emf.cdo.common.branch.CDOBranchVersion; |
| import org.eclipse.emf.cdo.common.commit.CDOChangeKind; |
| import org.eclipse.emf.cdo.common.commit.CDOChangeSet; |
| import org.eclipse.emf.cdo.common.commit.CDOChangeSetData; |
| import org.eclipse.emf.cdo.common.commit.CDOCommitInfo; |
| import org.eclipse.emf.cdo.common.commit.CDOCommitInfoManager; |
| import org.eclipse.emf.cdo.common.id.CDOID; |
| import org.eclipse.emf.cdo.common.id.CDOIDGenerator; |
| import org.eclipse.emf.cdo.common.id.CDOIDUtil; |
| import org.eclipse.emf.cdo.common.lob.CDOLobInfo; |
| import org.eclipse.emf.cdo.common.lob.CDOLobStore; |
| import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo; |
| import org.eclipse.emf.cdo.common.model.CDOPackageUnit; |
| import org.eclipse.emf.cdo.common.protocol.CDOProtocol.CommitNotificationInfo; |
| import org.eclipse.emf.cdo.common.revision.CDOElementProxy; |
| import org.eclipse.emf.cdo.common.revision.CDOIDAndVersion; |
| import org.eclipse.emf.cdo.common.revision.CDOList; |
| import org.eclipse.emf.cdo.common.revision.CDORevisable; |
| 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.common.revision.delta.CDOAddFeatureDelta; |
| import org.eclipse.emf.cdo.common.revision.delta.CDOClearFeatureDelta; |
| import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta; |
| import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDeltaVisitor; |
| import org.eclipse.emf.cdo.common.revision.delta.CDOListFeatureDelta; |
| import org.eclipse.emf.cdo.common.revision.delta.CDOMoveFeatureDelta; |
| import org.eclipse.emf.cdo.common.revision.delta.CDORemoveFeatureDelta; |
| import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta; |
| import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta; |
| import org.eclipse.emf.cdo.common.security.CDOPermission; |
| import org.eclipse.emf.cdo.common.util.CDOCommonUtil; |
| import org.eclipse.emf.cdo.common.util.CDOPackageNotFoundException; |
| import org.eclipse.emf.cdo.common.util.RepositoryStateChangedEvent; |
| import org.eclipse.emf.cdo.common.util.RepositoryTypeChangedEvent; |
| import org.eclipse.emf.cdo.eresource.EresourcePackage; |
| import org.eclipse.emf.cdo.etypes.EtypesPackage; |
| import org.eclipse.emf.cdo.internal.common.revision.delta.CDOMoveFeatureDeltaImpl; |
| import org.eclipse.emf.cdo.internal.common.revision.delta.CDOSetFeatureDeltaImpl; |
| import org.eclipse.emf.cdo.internal.common.revision.delta.CDOSingleValueFeatureDeltaImpl; |
| import org.eclipse.emf.cdo.session.CDOCollectionLoadingPolicy; |
| import org.eclipse.emf.cdo.session.CDORepositoryInfo; |
| import org.eclipse.emf.cdo.session.CDOSession; |
| import org.eclipse.emf.cdo.session.CDOSessionInvalidationEvent; |
| import org.eclipse.emf.cdo.session.CDOSessionLocksChangedEvent; |
| import org.eclipse.emf.cdo.session.remote.CDORemoteSessionManager; |
| import org.eclipse.emf.cdo.spi.common.CDOLobStoreImpl; |
| import org.eclipse.emf.cdo.spi.common.branch.CDOBranchUtil; |
| import org.eclipse.emf.cdo.spi.common.branch.InternalCDOBranch; |
| import org.eclipse.emf.cdo.spi.common.branch.InternalCDOBranchManager; |
| import org.eclipse.emf.cdo.spi.common.commit.CDORevisionAvailabilityInfo; |
| import org.eclipse.emf.cdo.spi.common.commit.InternalCDOCommitInfoManager; |
| import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageRegistry; |
| import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageUnit; |
| import org.eclipse.emf.cdo.spi.common.revision.CDOFeatureDeltaVisitorImpl; |
| import org.eclipse.emf.cdo.spi.common.revision.DetachedCDORevision; |
| import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision; |
| import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionCache; |
| import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta; |
| import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionManager; |
| import org.eclipse.emf.cdo.spi.common.revision.PointerCDORevision; |
| import org.eclipse.emf.cdo.transaction.CDOTransaction; |
| import org.eclipse.emf.cdo.util.CDOUtil; |
| import org.eclipse.emf.cdo.view.CDOFetchRuleManager; |
| import org.eclipse.emf.cdo.view.CDOView; |
| |
| import org.eclipse.emf.internal.cdo.analyzer.NOOPFetchRuleManager; |
| import org.eclipse.emf.internal.cdo.bundle.OM; |
| import org.eclipse.emf.internal.cdo.messages.Messages; |
| import org.eclipse.emf.internal.cdo.object.CDOFactoryImpl; |
| import org.eclipse.emf.internal.cdo.session.remote.CDORemoteSessionManagerImpl; |
| import org.eclipse.emf.internal.cdo.util.DefaultLocksChangedEvent; |
| |
| import org.eclipse.net4j.util.AdapterUtil; |
| import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump; |
| import org.eclipse.net4j.util.WrappedException; |
| import org.eclipse.net4j.util.concurrent.ConcurrencyUtil; |
| import org.eclipse.net4j.util.concurrent.ExecutorWorkSerializer; |
| import org.eclipse.net4j.util.concurrent.IExecutorServiceProvider; |
| import org.eclipse.net4j.util.concurrent.IRWLockManager; |
| import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType; |
| import org.eclipse.net4j.util.concurrent.IRWOLockManager; |
| import org.eclipse.net4j.util.concurrent.RWOLockManager; |
| import org.eclipse.net4j.util.event.Event; |
| import org.eclipse.net4j.util.event.EventUtil; |
| import org.eclipse.net4j.util.event.IEvent; |
| import org.eclipse.net4j.util.event.IListener; |
| import org.eclipse.net4j.util.event.Notifier; |
| import org.eclipse.net4j.util.io.IOUtil; |
| 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.om.trace.ContextTracer; |
| import org.eclipse.net4j.util.options.OptionsEvent; |
| import org.eclipse.net4j.util.registry.HashMapRegistry; |
| import org.eclipse.net4j.util.registry.IRegistry; |
| import org.eclipse.net4j.util.security.IPasswordCredentialsProvider; |
| |
| import org.eclipse.emf.common.util.ECollections; |
| import org.eclipse.emf.ecore.EPackage; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.EcorePackage; |
| import org.eclipse.emf.spi.cdo.CDOPermissionUpdater; |
| import org.eclipse.emf.spi.cdo.CDOSessionProtocol; |
| import org.eclipse.emf.spi.cdo.CDOSessionProtocol.RefreshSessionResult; |
| import org.eclipse.emf.spi.cdo.InternalCDOObject; |
| import org.eclipse.emf.spi.cdo.InternalCDORemoteSessionManager; |
| import org.eclipse.emf.spi.cdo.InternalCDOSession; |
| import org.eclipse.emf.spi.cdo.InternalCDOSessionInvalidationEvent; |
| import org.eclipse.emf.spi.cdo.InternalCDOTransaction; |
| import org.eclipse.emf.spi.cdo.InternalCDOView; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Reader; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.concurrent.ExecutorService; |
| |
| /** |
| * @author Eike Stepper |
| */ |
| public abstract class CDOSessionImpl extends CDOTransactionContainerImpl |
| implements InternalCDOSession, IExecutorServiceProvider |
| { |
| private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_SESSION, CDOSessionImpl.class); |
| |
| private ExceptionHandler exceptionHandler; |
| |
| private CDOIDGenerator idGenerator; |
| |
| private InternalCDOPackageRegistry packageRegistry; |
| |
| private InternalCDOBranchManager branchManager; |
| |
| private InternalCDORevisionManager revisionManager; |
| |
| private InternalCDOCommitInfoManager commitInfoManager; |
| |
| private CDOSessionProtocol sessionProtocol; |
| |
| @ExcludeFromDump |
| private IListener sessionProtocolListener = new LifecycleEventAdapter() |
| { |
| @Override |
| protected void onDeactivated(ILifecycle lifecycle) |
| { |
| sessionProtocolDeactivated(); |
| } |
| }; |
| |
| private int sessionID; |
| |
| private String userID; |
| |
| private long lastUpdateTime; |
| |
| @ExcludeFromDump |
| private LastUpdateTimeLock lastUpdateTimeLock = new LastUpdateTimeLock(); |
| |
| private CDOSession.Options options = createOptions(); |
| |
| private final IRegistry<String, Object> properties = new HashMapRegistry<String, Object>() |
| { |
| @Override |
| public void setAutoCommit(boolean autoCommit) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| |
| private Invalidator invalidator = new Invalidator(); |
| |
| private CDORepositoryInfo repositoryInfo; |
| |
| private CDOFetchRuleManager fetchRuleManager; |
| |
| private IRWOLockManager<CDOSessionImpl, Object> lockManager = new RWOLockManager<CDOSessionImpl, Object>(); |
| |
| @ExcludeFromDump |
| private Set<CDOSessionImpl> singletonCollection = Collections.singleton(this); |
| |
| private boolean mainBranchLocal; |
| |
| private IPasswordCredentialsProvider credentialsProvider; |
| |
| private InternalCDORemoteSessionManager remoteSessionManager; |
| |
| /** |
| * A map to track for every object that was committed since this session's last refresh, onto what CDOBranchPoint it |
| * was committed. (Used only for sticky transactions, see bug 290032 - Sticky views.) |
| */ |
| private Map<CDOID, CDOBranchPoint> committedSinceLastRefresh = CDOIDUtil.createMap(); |
| |
| static |
| { |
| // Ensure that these 3 packages are registered with the global package registry in stand-alone |
| EcorePackage.eINSTANCE.getClass(); |
| EresourcePackage.eINSTANCE.getClass(); |
| EtypesPackage.eINSTANCE.getClass(); |
| } |
| |
| public CDOSessionImpl() |
| { |
| } |
| |
| public CDORepositoryInfo getRepositoryInfo() |
| { |
| return repositoryInfo; |
| } |
| |
| public void setRepositoryInfo(CDORepositoryInfo repositoryInfo) |
| { |
| this.repositoryInfo = repositoryInfo; |
| } |
| |
| public int getSessionID() |
| { |
| return sessionID; |
| } |
| |
| public void setSessionID(int sessionID) |
| { |
| this.sessionID = sessionID; |
| } |
| |
| public String getUserID() |
| { |
| return userID; |
| } |
| |
| public void setUserID(String userID) |
| { |
| this.userID = userID; |
| } |
| |
| public ExceptionHandler getExceptionHandler() |
| { |
| return exceptionHandler; |
| } |
| |
| public void setExceptionHandler(ExceptionHandler exceptionHandler) |
| { |
| checkInactive(); |
| this.exceptionHandler = exceptionHandler; |
| } |
| |
| public CDOIDGenerator getIDGenerator() |
| { |
| return idGenerator; |
| } |
| |
| public void setIDGenerator(CDOIDGenerator idGenerator) |
| { |
| checkInactive(); |
| this.idGenerator = idGenerator; |
| } |
| |
| public InternalCDOPackageRegistry getPackageRegistry() |
| { |
| return packageRegistry; |
| } |
| |
| public void setPackageRegistry(InternalCDOPackageRegistry packageRegistry) |
| { |
| this.packageRegistry = packageRegistry; |
| } |
| |
| public InternalCDOBranchManager getBranchManager() |
| { |
| return branchManager; |
| } |
| |
| public void setBranchManager(InternalCDOBranchManager branchManager) |
| { |
| checkInactive(); |
| this.branchManager = branchManager; |
| } |
| |
| public InternalCDORevisionManager getRevisionManager() |
| { |
| return revisionManager; |
| } |
| |
| public void setRevisionManager(InternalCDORevisionManager revisionManager) |
| { |
| checkInactive(); |
| this.revisionManager = revisionManager; |
| } |
| |
| public InternalCDOCommitInfoManager getCommitInfoManager() |
| { |
| return commitInfoManager; |
| } |
| |
| public void setCommitInfoManager(InternalCDOCommitInfoManager commitInfoManager) |
| { |
| checkInactive(); |
| this.commitInfoManager = commitInfoManager; |
| } |
| |
| public CDOSessionProtocol getSessionProtocol() |
| { |
| return sessionProtocol; |
| } |
| |
| public void setSessionProtocol(CDOSessionProtocol sessionProtocol) |
| { |
| if (exceptionHandler == null) |
| { |
| this.sessionProtocol = sessionProtocol; |
| } |
| else |
| { |
| if (this.sessionProtocol instanceof DelegatingSessionProtocol) |
| { |
| ((DelegatingSessionProtocol)this.sessionProtocol).setDelegate(sessionProtocol); |
| } |
| else |
| { |
| this.sessionProtocol = new DelegatingSessionProtocol(sessionProtocol, exceptionHandler); |
| } |
| } |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| public CDOFetchRuleManager getFetchRuleManager() |
| { |
| return fetchRuleManager; |
| } |
| |
| public ExecutorService getExecutorService() |
| { |
| return ConcurrencyUtil.getExecutorService(sessionProtocol); |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| public void setFetchRuleManager(CDOFetchRuleManager fetchRuleManager) |
| { |
| if (fetchRuleManager == null) |
| { |
| fetchRuleManager = new NOOPFetchRuleManager() |
| { |
| public CDOCollectionLoadingPolicy getCollectionLoadingPolicy() |
| { |
| return options().getCollectionLoadingPolicy(); |
| } |
| }; |
| } |
| |
| this.fetchRuleManager = fetchRuleManager; |
| } |
| |
| @Deprecated |
| public org.eclipse.emf.cdo.common.protocol.CDOAuthenticator getAuthenticator() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| public void setAuthenticator(org.eclipse.emf.cdo.common.protocol.CDOAuthenticator authenticator) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public IPasswordCredentialsProvider getCredentialsProvider() |
| { |
| return credentialsProvider; |
| } |
| |
| public void setCredentialsProvider(IPasswordCredentialsProvider credentialsProvider) |
| { |
| this.credentialsProvider = credentialsProvider; |
| } |
| |
| public boolean isMainBranchLocal() |
| { |
| return mainBranchLocal; |
| } |
| |
| public void setMainBranchLocal(boolean mainBranchLocal) |
| { |
| this.mainBranchLocal = mainBranchLocal; |
| } |
| |
| public InternalCDORemoteSessionManager getRemoteSessionManager() |
| { |
| return remoteSessionManager; |
| } |
| |
| public void setRemoteSessionManager(InternalCDORemoteSessionManager remoteSessionManager) |
| { |
| this.remoteSessionManager = remoteSessionManager; |
| } |
| |
| public CDOLobStore getLobStore() |
| { |
| final CDOLobStore cache = options().getLobCache(); |
| return new CDOLobStore.Delegating() |
| { |
| @Override |
| public InputStream getBinary(final CDOLobInfo info) throws IOException |
| { |
| for (;;) |
| { |
| try |
| { |
| return super.getBinary(info); |
| } |
| catch (FileNotFoundException couldNotBeRead) |
| { |
| try |
| { |
| loadBinary(info); |
| } |
| catch (FileNotFoundException couldNotBeCreated) |
| { |
| // Try to read again |
| } |
| } |
| } |
| } |
| |
| @Override |
| public Reader getCharacter(CDOLobInfo info) throws IOException |
| { |
| for (;;) |
| { |
| try |
| { |
| return super.getCharacter(info); |
| } |
| catch (FileNotFoundException couldNotBeRead) |
| { |
| try |
| { |
| loadCharacter(info); |
| } |
| catch (FileNotFoundException couldNotBeCreated) |
| { |
| // Try to read again |
| } |
| } |
| } |
| } |
| |
| private void loadBinary(final CDOLobInfo info) throws IOException |
| { |
| final File file = getDelegate().getBinaryFile(info.getID()); |
| final FileOutputStream out = new FileOutputStream(file); |
| |
| loadLobAsync(info, new Runnable() |
| { |
| public void run() |
| { |
| try |
| { |
| getSessionProtocol().loadLob(info, out); |
| } |
| catch (Throwable t) |
| { |
| OM.LOG.error(t); |
| IOUtil.delete(file); |
| } |
| } |
| }); |
| } |
| |
| private void loadCharacter(final CDOLobInfo info) throws IOException |
| { |
| final File file = getDelegate().getCharacterFile(info.getID()); |
| final FileWriter out = new FileWriter(file); |
| |
| loadLobAsync(info, new Runnable() |
| { |
| public void run() |
| { |
| try |
| { |
| getSessionProtocol().loadLob(info, out); |
| } |
| catch (Throwable t) |
| { |
| OM.LOG.error(t); |
| IOUtil.delete(file); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| protected CDOLobStore getDelegate() |
| { |
| return cache; |
| } |
| }; |
| } |
| |
| protected void loadLobAsync(CDOLobInfo info, Runnable runnable) |
| { |
| new Thread(runnable, "LobLoader").start(); |
| } |
| |
| public void close() |
| { |
| LifecycleUtil.deactivate(this, OMLogger.Level.DEBUG); |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| public boolean isClosed() |
| { |
| return !isActive(); |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| public CDOSession.Options options() |
| { |
| return options; |
| } |
| |
| public IRegistry<String, Object> properties() |
| { |
| return properties; |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| protected CDOSession.Options createOptions() |
| { |
| return new OptionsImpl(); |
| } |
| |
| public Object processPackage(Object value) |
| { |
| CDOFactoryImpl.prepareDynamicEPackage(value); |
| return value; |
| } |
| |
| public EPackage[] loadPackages(CDOPackageUnit packageUnit) |
| { |
| if (packageUnit.getOriginalType().isGenerated()) |
| { |
| if (!options().isGeneratedPackageEmulationEnabled()) |
| { |
| throw new CDOPackageNotFoundException(packageUnit.getID()); |
| } |
| } |
| |
| return getSessionProtocol().loadPackages(packageUnit); |
| } |
| |
| public void acquireAtomicRequestLock(Object key) |
| { |
| try |
| { |
| lockManager.lock(LockType.WRITE, key, this, IRWLockManager.WAIT); |
| } |
| catch (InterruptedException ex) |
| { |
| throw WrappedException.wrap(ex); |
| } |
| } |
| |
| public void releaseAtomicRequestLock(Object key) |
| { |
| lockManager.unlock(LockType.WRITE, key, singletonCollection); |
| } |
| |
| @Override |
| protected void initViewSynced(InternalCDOView view) |
| { |
| view.setSession(this); |
| view.setLastUpdateTime(getLastUpdateTime()); |
| } |
| |
| @Override |
| protected CDOBranch getMainBranch() |
| { |
| return getBranchManager().getMainBranch(); |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| public long refresh() |
| { |
| checkActive(); |
| if (options().isPassiveUpdateEnabled()) |
| { |
| return CDOBranchPoint.UNSPECIFIED_DATE; |
| } |
| |
| return refresh(false); |
| } |
| |
| public long refresh(RefreshSessionResult.Provider provider) |
| { |
| Map<CDOBranch, List<InternalCDOView>> views = new HashMap<CDOBranch, List<InternalCDOView>>(); |
| Map<CDOBranch, Map<CDOID, InternalCDORevision>> viewedRevisions = new HashMap<CDOBranch, Map<CDOID, InternalCDORevision>>(); |
| collectViewedRevisions(views, viewedRevisions); |
| cleanupRevisionCache(viewedRevisions); |
| |
| RefreshSessionResult result = provider.getRefreshSessionResult(views, viewedRevisions); |
| |
| setLastUpdateTime(result.getLastUpdateTime()); |
| registerPackageUnits(result.getPackageUnits()); |
| |
| for (Entry<CDOBranch, List<InternalCDOView>> entry : views.entrySet()) |
| { |
| CDOBranch branch = entry.getKey(); |
| List<InternalCDOView> branchViews = entry.getValue(); |
| processRefreshSessionResult(result, branch, branchViews, viewedRevisions); |
| } |
| |
| return result.getLastUpdateTime(); |
| } |
| |
| protected final long refresh(final boolean enablePassiveUpdates) |
| { |
| return refresh(new RefreshSessionResult.Provider() |
| { |
| public RefreshSessionResult getRefreshSessionResult(Map<CDOBranch, List<InternalCDOView>> views, |
| Map<CDOBranch, Map<CDOID, InternalCDORevision>> viewedRevisions) |
| { |
| CDOSessionProtocol sessionProtocol = getSessionProtocol(); |
| long lastUpdateTime = getLastUpdateTime(); |
| int initialChunkSize = options().getCollectionLoadingPolicy().getInitialChunkSize(); |
| |
| return sessionProtocol.refresh(lastUpdateTime, viewedRevisions, initialChunkSize, enablePassiveUpdates); |
| } |
| }); |
| } |
| |
| public void processRefreshSessionResult(RefreshSessionResult result, CDOBranch branch, |
| List<InternalCDOView> branchViews, Map<CDOBranch, Map<CDOID, InternalCDORevision>> viewedRevisions) |
| { |
| Map<CDOID, InternalCDORevision> oldRevisions = viewedRevisions.get(branch); |
| |
| InternalCDORevisionManager revisionManager = getRevisionManager(); |
| List<CDORevisionKey> changedObjects = new ArrayList<CDORevisionKey>(); |
| List<InternalCDORevision> newRevisions = result.getChangedObjects(branch); |
| |
| for (InternalCDORevision newRevision : newRevisions) |
| { |
| revisionManager.addRevision(newRevision); |
| |
| CDOID id = newRevision.getID(); |
| InternalCDORevision oldRevision = oldRevisions.get(id); |
| InternalCDORevisionDelta delta = newRevision.compare(oldRevision); |
| changedObjects.add(delta); |
| } |
| |
| List<CDOIDAndVersion> detachedObjects = result.getDetachedObjects(branch); |
| for (CDOIDAndVersion detachedObject : detachedObjects) |
| { |
| CDOID id = detachedObject.getID(); |
| revisionManager.reviseLatest(id, branch); |
| } |
| |
| long lastUpdateTime = result.getLastUpdateTime(); |
| for (InternalCDOView view : branchViews) |
| { |
| CDOBranch viewBranch = view.getBranch(); |
| view.invalidate(viewBranch, lastUpdateTime, changedObjects, detachedObjects, oldRevisions, false, true); |
| } |
| } |
| |
| private void collectViewedRevisions(Map<CDOBranch, List<InternalCDOView>> views, |
| Map<CDOBranch, Map<CDOID, InternalCDORevision>> viewedRevisions) |
| { |
| for (InternalCDOView view : getViews()) |
| { |
| if (view.getTimeStamp() == CDOView.UNSPECIFIED_DATE) |
| { |
| CDOBranch branch = view.getBranch(); |
| Map<CDOID, InternalCDORevision> revisions = viewedRevisions.get(branch); |
| boolean needNewMap = revisions == null; |
| if (needNewMap) |
| { |
| revisions = CDOIDUtil.createMap(); |
| } |
| |
| view.collectViewedRevisions(revisions); |
| if (!revisions.isEmpty()) |
| { |
| List<InternalCDOView> list = views.get(branch); |
| if (list == null) |
| { |
| list = new ArrayList<InternalCDOView>(); |
| views.put(branch, list); |
| } |
| |
| list.add(view); |
| |
| if (needNewMap) |
| { |
| viewedRevisions.put(branch, revisions); |
| } |
| } |
| } |
| } |
| } |
| |
| private void cleanupRevisionCache(Map<CDOBranch, Map<CDOID, InternalCDORevision>> viewedRevisions) |
| { |
| Set<InternalCDORevision> set = new HashSet<InternalCDORevision>(); |
| for (Map<CDOID, InternalCDORevision> revisions : viewedRevisions.values()) |
| { |
| for (InternalCDORevision revision : revisions.values()) |
| { |
| set.add(revision); |
| } |
| } |
| |
| InternalCDORevisionCache cache = getRevisionManager().getCache(); |
| List<CDORevision> currentRevisions = cache.getCurrentRevisions(); |
| for (CDORevision revision : currentRevisions) |
| { |
| if (!set.contains(revision)) |
| { |
| cache.removeRevision(revision.getID(), revision); |
| } |
| } |
| } |
| |
| public long getLastUpdateTime() |
| { |
| synchronized (lastUpdateTimeLock) |
| { |
| return lastUpdateTime; |
| } |
| } |
| |
| public void setLastUpdateTime(long lastUpdateTime) |
| { |
| synchronized (lastUpdateTimeLock) |
| { |
| if (this.lastUpdateTime < lastUpdateTime) |
| { |
| this.lastUpdateTime = lastUpdateTime; |
| } |
| |
| lastUpdateTimeLock.notifyAll(); |
| } |
| } |
| |
| public void waitForUpdate(long updateTime) |
| { |
| waitForUpdate(updateTime, NO_TIMEOUT); |
| } |
| |
| public boolean waitForUpdate(long updateTime, long timeoutMillis) |
| { |
| long end = timeoutMillis == NO_TIMEOUT ? Long.MAX_VALUE : System.currentTimeMillis() + timeoutMillis; |
| InternalCDOView views[] = getViews(); |
| if (views.length > 0) |
| { |
| for (CDOView view : views) |
| { |
| long viewTimeoutMillis = timeoutMillis == NO_TIMEOUT ? NO_TIMEOUT : end - System.currentTimeMillis(); |
| boolean ok = view.waitForUpdate(updateTime, viewTimeoutMillis); |
| if (!ok) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| // Session without views |
| for (;;) |
| { |
| synchronized (lastUpdateTimeLock) |
| { |
| if (lastUpdateTime >= updateTime) |
| { |
| return true; |
| } |
| |
| long now = System.currentTimeMillis(); |
| if (now >= end) |
| { |
| return false; |
| } |
| |
| try |
| { |
| lastUpdateTimeLock.wait(end - now); |
| } |
| catch (InterruptedException ex) |
| { |
| throw WrappedException.wrap(ex); |
| } |
| } |
| } |
| } |
| |
| public boolean runAfterUpdate(final long updateTime, final Runnable runnable) |
| { |
| synchronized (lastUpdateTimeLock) |
| { |
| if (lastUpdateTime < updateTime) |
| { |
| addListener(new IListener() |
| { |
| public void notifyEvent(IEvent event) |
| { |
| if (event instanceof CDOSessionInvalidationEvent) |
| { |
| CDOSessionInvalidationEvent e = (CDOSessionInvalidationEvent)event; |
| if (e.getTimeStamp() >= updateTime) |
| { |
| removeListener(this); |
| runnable.run(); |
| } |
| } |
| } |
| }); |
| |
| return false; |
| } |
| } |
| |
| runnable.run(); |
| return true; |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| public Object resolveElementProxy(CDORevision revision, EStructuralFeature feature, int accessIndex, int serverIndex) |
| { |
| if (!((InternalCDORevision)revision).isUnchunked()) |
| { |
| CDOCollectionLoadingPolicy policy = options().getCollectionLoadingPolicy(); |
| return policy.resolveProxy(revision, feature, accessIndex, serverIndex); |
| } |
| |
| return revision.data().get(feature, accessIndex); |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| public void resolveAllElementProxies(CDORevision revision) |
| { |
| InternalCDORevision internalRevision = (InternalCDORevision)revision; |
| if (!internalRevision.isUnchunked()) |
| { |
| CDOCollectionLoadingPolicy policy = options().getCollectionLoadingPolicy(); |
| |
| for (EReference reference : internalRevision.getClassInfo().getAllPersistentReferences()) |
| { |
| if (reference.isMany()) |
| { |
| CDOList list = internalRevision.getList(reference); |
| for (Iterator<Object> it = list.iterator(); it.hasNext();) |
| { |
| Object element = it.next(); |
| if (element instanceof CDOElementProxy) |
| { |
| policy.resolveAllProxies(internalRevision, reference); |
| break; |
| } |
| } |
| } |
| } |
| |
| internalRevision.setUnchunked(); |
| } |
| } |
| |
| public void ensureChunks(InternalCDORevision revision, int chunkSize) |
| { |
| resolveAllElementProxies(revision); |
| } |
| |
| public void handleRepositoryTypeChanged(CDOCommonRepository.Type oldType, CDOCommonRepository.Type newType) |
| { |
| fireEvent(new RepositoryTypeChangedEvent(this, oldType, newType)); |
| } |
| |
| public void handleRepositoryStateChanged(CDOCommonRepository.State oldState, CDOCommonRepository.State newState) |
| { |
| fireEvent(new RepositoryStateChangedEvent(this, oldState, newState)); |
| } |
| |
| @Deprecated |
| public void handleBranchNotification(InternalCDOBranch branch) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| public void handleCommitNotification(CDOCommitInfo commitInfo) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| public void handleCommitNotification(CDOCommitInfo commitInfo, boolean clearResourcePathCache) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public void handleCommitNotification(CommitNotificationInfo info) |
| { |
| try |
| { |
| CDOCommitInfo commitInfo = info.getCommitInfo(); |
| registerPackageUnits(commitInfo.getNewPackageUnits()); |
| |
| boolean clearResourcePathCache = info.isClearResourcePathCache(); |
| byte securityImpact = info.getSecurityImpact(); |
| Map<CDOID, CDOPermission> newPermissions = info.getNewPermissions(); |
| |
| invalidate(commitInfo, null, clearResourcePathCache, securityImpact, newPermissions); |
| } |
| catch (RuntimeException ex) |
| { |
| if (isActive()) |
| { |
| OM.LOG.error(ex); |
| } |
| else |
| { |
| OM.LOG.info(Messages.getString("CDOSessionImpl.2")); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| public void handleLockNotification(CDOLockChangeInfo lockChangeInfo, InternalCDOView sender) |
| { |
| for (InternalCDOView view : getViews()) |
| { |
| if (view != sender) |
| { |
| view.handleLockNotification(sender, lockChangeInfo); |
| } |
| } |
| |
| fireEvent(new LocksChangedEvent(sender, lockChangeInfo)); |
| } |
| |
| private void registerPackageUnits(List<CDOPackageUnit> packageUnits) |
| { |
| InternalCDOPackageRegistry packageRegistry = getPackageRegistry(); |
| for (CDOPackageUnit newPackageUnit : packageUnits) |
| { |
| packageRegistry.putPackageUnit((InternalCDOPackageUnit)newPackageUnit); |
| } |
| } |
| |
| /** |
| * Computes/adjusts CDOMoveFeatureDelta.value, CDORemoveFeatureDelta.value and CDOSetFeatureDelta.oldValue. |
| * <p> |
| * Implicitely adjusts the underlying CDORevisionDelta. |
| */ |
| private void addOldValuesToDelta(final CDORevision oldRevision, CDORevisionDelta revisionDelta) |
| { |
| CDOFeatureDeltaVisitor visitor = new CDOFeatureDeltaVisitorImpl() |
| { |
| private List<Object> workList; |
| |
| @Override |
| public void visit(CDOAddFeatureDelta delta) |
| { |
| workList.add(delta.getIndex(), delta.getValue()); |
| } |
| |
| @Override |
| public void visit(CDOClearFeatureDelta delta) |
| { |
| workList.clear(); |
| } |
| |
| @Override |
| public void visit(CDOListFeatureDelta deltas) |
| { |
| @SuppressWarnings("unchecked") |
| List<Object> list = (List<Object>)((InternalCDORevision)oldRevision).getValue(deltas.getFeature()); |
| if (list != null) |
| { |
| workList = new ArrayList<Object>(list); |
| super.visit(deltas); |
| } |
| } |
| |
| @Override |
| public void visit(CDOMoveFeatureDelta delta) |
| { |
| Object value = workList.get(delta.getOldPosition()); |
| ((CDOMoveFeatureDeltaImpl)delta).setValue(value); // Adjust delta |
| ECollections.move(workList, delta.getNewPosition(), delta.getOldPosition()); |
| } |
| |
| @Override |
| public void visit(CDORemoveFeatureDelta delta) |
| { |
| Object oldValue = workList.remove(delta.getIndex()); |
| ((CDOSingleValueFeatureDeltaImpl)delta).setValue(oldValue); // Adjust delta |
| } |
| |
| @Override |
| public void visit(CDOSetFeatureDelta delta) |
| { |
| EStructuralFeature feature = delta.getFeature(); |
| Object value = null; |
| if (feature.isMany()) |
| { |
| value = workList.set(delta.getIndex(), delta.getValue()); |
| } |
| else |
| { |
| value = ((InternalCDORevision)oldRevision).getValue(feature); |
| } |
| |
| ((CDOSetFeatureDeltaImpl)delta).setOldValue(value); // Adjust delta |
| } |
| }; |
| |
| for (CDOFeatureDelta featureDelta : revisionDelta.getFeatureDeltas()) |
| { |
| featureDelta.accept(visitor); |
| } |
| } |
| |
| public Object startLocalCommit() |
| { |
| return invalidator.startLocalCommit(); |
| } |
| |
| public void endLocalCommit(Object token) |
| { |
| invalidator.endLocalCommit(token); |
| } |
| |
| @Deprecated |
| public void invalidate(CDOCommitInfo commitInfo, InternalCDOTransaction sender) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| public void invalidate(CDOCommitInfo commitInfo, InternalCDOTransaction sender, boolean clearResourcePathCache) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public void invalidate(CDOCommitInfo commitInfo, InternalCDOTransaction sender, boolean clearResourcePathCache, |
| byte securityImpact, Map<CDOID, CDOPermission> newPermissions) |
| { |
| invalidator.reorderInvalidations(commitInfo, sender, clearResourcePathCache, securityImpact, newPermissions); |
| } |
| |
| public ILifecycle getInvalidator() |
| { |
| return invalidator; |
| } |
| |
| @SuppressWarnings({ "unchecked", "rawtypes" }) |
| public Object getAdapter(Class adapter) |
| { |
| return AdapterUtil.adapt(this, adapter, false); |
| } |
| |
| @Override |
| public String toString() |
| { |
| String name = repositoryInfo == null ? "?" : repositoryInfo.getName(); //$NON-NLS-1$ |
| if (userID != null && userID.length() != 0) |
| { |
| name = userID + "@" + name; |
| } |
| |
| return MessageFormat.format("Session{0} [{1}]", sessionID, name); //$NON-NLS-1$ |
| } |
| |
| public CDOBranchPoint getCommittedSinceLastRefresh(CDOID id) |
| { |
| if (isSticky()) |
| { |
| return committedSinceLastRefresh.get(id); |
| } |
| |
| return null; |
| } |
| |
| public void setCommittedSinceLastRefresh(CDOID id, CDOBranchPoint branchPoint) |
| { |
| if (isSticky()) |
| { |
| committedSinceLastRefresh.put(id, branchPoint); |
| } |
| } |
| |
| public void clearCommittedSinceLastRefresh() |
| { |
| if (isSticky()) |
| { |
| committedSinceLastRefresh.clear(); |
| } |
| } |
| |
| public boolean isSticky() |
| { |
| return !options().isPassiveUpdateEnabled() && getRepositoryInfo().isSupportingAudits(); |
| } |
| |
| public CDOChangeSetData compareRevisions(CDOBranchPoint source, CDOBranchPoint target) |
| { |
| long now = getLastUpdateTime(); |
| |
| if (target.getTimeStamp() == CDOBranchPoint.UNSPECIFIED_DATE) |
| { |
| target = target.getBranch().getPoint(now); |
| } |
| |
| if (source.getTimeStamp() == CDOBranchPoint.UNSPECIFIED_DATE) |
| { |
| source = source.getBranch().getPoint(now); |
| } |
| |
| CDORevisionAvailabilityInfo targetInfo = createRevisionAvailabilityInfo2(target); |
| CDORevisionAvailabilityInfo sourceInfo = createRevisionAvailabilityInfo2(source); |
| |
| Set<CDOID> ids = sessionProtocol.loadMergeData(targetInfo, sourceInfo, null, null); |
| |
| cacheRevisions2(targetInfo); |
| cacheRevisions2(sourceInfo); |
| |
| return CDORevisionUtil.createChangeSetData(ids, sourceInfo, targetInfo); |
| } |
| |
| public MergeData getMergeData(CDOBranchPoint target, CDOBranchPoint source, CDOBranchPoint sourceBase, |
| boolean computeChangeSets) |
| { |
| CDOBranchPoint ancestor = CDOBranchUtil.getAncestor(target, source); |
| |
| CDORevisionAvailabilityInfo ancestorInfo = createRevisionAvailabilityInfo2(ancestor); |
| CDORevisionAvailabilityInfo targetInfo = createRevisionAvailabilityInfo2(target); |
| CDORevisionAvailabilityInfo sourceInfo = createRevisionAvailabilityInfo2(source); |
| CDORevisionAvailabilityInfo baseInfo = sourceBase != null ? createRevisionAvailabilityInfo2(sourceBase) : null; |
| |
| Set<CDOID> ids = sessionProtocol.loadMergeData(targetInfo, sourceInfo, ancestorInfo, baseInfo); |
| |
| cacheRevisions2(targetInfo); |
| cacheRevisions2(sourceInfo); |
| cacheRevisions2(ancestorInfo); |
| |
| if (baseInfo != null) |
| { |
| cacheRevisions2(baseInfo); |
| } |
| else |
| { |
| baseInfo = ancestorInfo; |
| } |
| |
| CDOChangeSet targetChanges = null; |
| CDOChangeSet sourceChanges = null; |
| if (computeChangeSets) |
| { |
| targetChanges = createChangeSet(ids, ancestorInfo, targetInfo); |
| sourceChanges = createChangeSet(ids, baseInfo, sourceInfo); |
| } |
| |
| return new MergeData(target, source, sourceBase, ancestor, targetInfo, sourceInfo, baseInfo, ancestorInfo, ids, |
| targetChanges, sourceChanges); |
| } |
| |
| @Deprecated |
| public CDORevisionAvailabilityInfo createRevisionAvailabilityInfo(CDOBranchPoint branchPoint) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| private CDORevisionAvailabilityInfo createRevisionAvailabilityInfo2(CDOBranchPoint branchPoint) |
| { |
| CDORevisionAvailabilityInfo info = new CDORevisionAvailabilityInfo(branchPoint); |
| |
| InternalCDORevisionManager revisionManager = getRevisionManager(); |
| InternalCDORevisionCache cache = revisionManager.getCache(); |
| |
| List<CDORevision> revisions = cache.getRevisions(branchPoint); |
| for (CDORevision revision : revisions) |
| { |
| if (revision instanceof PointerCDORevision) |
| { |
| PointerCDORevision pointer = (PointerCDORevision)revision; |
| CDOBranchVersion target = pointer.getTarget(); |
| if (target != null) |
| { |
| revision = cache.getRevisionByVersion(pointer.getID(), target); |
| } |
| } |
| else if (revision instanceof DetachedCDORevision) |
| { |
| revision = null; |
| } |
| |
| if (revision != null) |
| { |
| resolveAllElementProxies(revision); |
| info.addRevision(revision); |
| } |
| } |
| |
| return info; |
| } |
| |
| @Deprecated |
| public void cacheRevisions(CDORevisionAvailabilityInfo info) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| private void cacheRevisions2(CDORevisionAvailabilityInfo info) |
| { |
| InternalCDORevisionManager revisionManager = getRevisionManager(); |
| CDOBranch branch = info.getBranchPoint().getBranch(); |
| for (CDORevisionKey key : info.getAvailableRevisions().values()) |
| { |
| CDORevision revision = (CDORevision)key; |
| revisionManager.addRevision(revision); |
| |
| if (revision.getBranch() != branch) |
| { |
| CDOID id = revision.getID(); |
| CDORevision firstRevision = revisionManager.getCache().getRevisionByVersion(id, |
| branch.getVersion(CDOBranchVersion.FIRST_VERSION)); |
| if (firstRevision != null) |
| { |
| long revised = firstRevision.getTimeStamp() - 1L; |
| CDOBranchVersion target = CDOBranchUtil.copyBranchVersion(revision); |
| PointerCDORevision pointer = new PointerCDORevision(revision.getEClass(), id, branch, revised, target); |
| revisionManager.addRevision(pointer); |
| } |
| } |
| } |
| } |
| |
| private CDOChangeSet createChangeSet(Set<CDOID> ids, CDORevisionAvailabilityInfo startInfo, |
| CDORevisionAvailabilityInfo endInfo) |
| { |
| CDOChangeSetData data = CDORevisionUtil.createChangeSetData(ids, startInfo, endInfo); |
| return CDORevisionUtil.createChangeSet(startInfo.getBranchPoint(), endInfo.getBranchPoint(), data); |
| } |
| |
| @Override |
| protected void doActivate() throws Exception |
| { |
| super.doActivate(); |
| |
| Runnable runnable = SessionUtil.getTestDelayInSessionActivation(); |
| if (runnable != null) |
| { |
| runnable.run(); |
| } |
| |
| InternalCDORemoteSessionManager remoteSessionManager = new CDORemoteSessionManagerImpl(); |
| remoteSessionManager.setLocalSession(this); |
| setRemoteSessionManager(remoteSessionManager); |
| remoteSessionManager.activate(); |
| |
| checkState(sessionProtocol, "sessionProtocol"); //$NON-NLS-1$ |
| checkState(remoteSessionManager, "remoteSessionManager"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| protected void doAfterActivate() throws Exception |
| { |
| super.doAfterActivate(); |
| |
| ExecutorService executorService = ConcurrencyUtil.getExecutorService(sessionProtocol); |
| invalidator.setExecutor(executorService); |
| LifecycleUtil.activate(invalidator); |
| } |
| |
| @Override |
| protected void doDeactivate() throws Exception |
| { |
| LifecycleUtil.deactivate(invalidator); |
| super.doDeactivate(); |
| |
| unhookSessionProtocol(); |
| |
| CDORemoteSessionManager remoteSessionManager = getRemoteSessionManager(); |
| setRemoteSessionManager(null); |
| LifecycleUtil.deactivate(remoteSessionManager); |
| |
| CDOSessionProtocol sessionProtocol = getSessionProtocol(); |
| LifecycleUtil.deactivate(sessionProtocol); |
| setSessionProtocol(null); |
| } |
| |
| /** |
| * Makes this session start listening to its protocol |
| */ |
| protected CDOSessionProtocol hookSessionProtocol() |
| { |
| EventUtil.addListener(sessionProtocol, sessionProtocolListener); |
| return sessionProtocol; |
| } |
| |
| /** |
| * Makes this session stop listening to its protocol |
| */ |
| protected void unhookSessionProtocol() |
| { |
| EventUtil.removeListener(sessionProtocol, sessionProtocolListener); |
| } |
| |
| protected void sessionProtocolDeactivated() |
| { |
| deactivate(); |
| } |
| |
| /** |
| * A separate class for better monitor debugging. |
| * |
| * @author Eike Stepper |
| */ |
| private static final class LastUpdateTimeLock |
| { |
| } |
| |
| /** |
| * @author Eike Stepper |
| * @since 2.0 |
| */ |
| protected class OptionsImpl extends Notifier implements Options |
| { |
| private boolean generatedPackageEmulationEnabled; |
| |
| private boolean passiveUpdateEnabled = true; |
| |
| private PassiveUpdateMode passiveUpdateMode = PassiveUpdateMode.INVALIDATIONS; |
| |
| private LockNotificationMode lockNotificationMode = LockNotificationMode.IF_REQUIRED_BY_VIEWS; |
| |
| private CDOCollectionLoadingPolicy collectionLoadingPolicy; |
| |
| private CDOLobStore lobCache = CDOLobStoreImpl.INSTANCE; |
| |
| private CDOPermissionUpdater permissionUpdater = CDOPermissionUpdater.SERVER; |
| |
| public OptionsImpl() |
| { |
| setCollectionLoadingPolicy(null); // Init default |
| } |
| |
| public CDOSession getContainer() |
| { |
| return CDOSessionImpl.this; |
| } |
| |
| public boolean isGeneratedPackageEmulationEnabled() |
| { |
| return generatedPackageEmulationEnabled; |
| } |
| |
| public synchronized void setGeneratedPackageEmulationEnabled(boolean generatedPackageEmulationEnabled) |
| { |
| this.generatedPackageEmulationEnabled = generatedPackageEmulationEnabled; |
| if (this.generatedPackageEmulationEnabled != generatedPackageEmulationEnabled) |
| { |
| this.generatedPackageEmulationEnabled = generatedPackageEmulationEnabled; |
| // TODO Check inconsistent state if switching off? |
| |
| IListener[] listeners = getListeners(); |
| if (listeners != null) |
| { |
| fireEvent(new GeneratedPackageEmulationEventImpl(), listeners); |
| } |
| } |
| } |
| |
| public boolean isPassiveUpdateEnabled() |
| { |
| return passiveUpdateEnabled; |
| } |
| |
| public synchronized void setPassiveUpdateEnabled(boolean passiveUpdateEnabled) |
| { |
| if (this.passiveUpdateEnabled != passiveUpdateEnabled) |
| { |
| this.passiveUpdateEnabled = passiveUpdateEnabled; |
| CDOSessionProtocol protocol = getSessionProtocol(); |
| if (protocol != null) |
| { |
| if (passiveUpdateEnabled) |
| { |
| refresh(true); |
| } |
| else |
| { |
| protocol.disablePassiveUpdate(); |
| } |
| |
| IListener[] listeners = getListeners(); |
| if (listeners != null) |
| { |
| fireEvent(new PassiveUpdateEventImpl(!passiveUpdateEnabled, passiveUpdateEnabled, passiveUpdateMode, |
| passiveUpdateMode), listeners); |
| } |
| } |
| } |
| } |
| |
| public PassiveUpdateMode getPassiveUpdateMode() |
| { |
| return passiveUpdateMode; |
| } |
| |
| public void setPassiveUpdateMode(PassiveUpdateMode passiveUpdateMode) |
| { |
| checkArg(passiveUpdateMode, "passiveUpdateMode"); //$NON-NLS-1$ |
| if (this.passiveUpdateMode != passiveUpdateMode) |
| { |
| PassiveUpdateMode oldMode = this.passiveUpdateMode; |
| this.passiveUpdateMode = passiveUpdateMode; |
| CDOSessionProtocol protocol = getSessionProtocol(); |
| if (protocol != null) |
| { |
| protocol.setPassiveUpdateMode(passiveUpdateMode); |
| |
| IListener[] listeners = getListeners(); |
| if (listeners != null) |
| { |
| fireEvent( |
| new PassiveUpdateEventImpl(passiveUpdateEnabled, passiveUpdateEnabled, oldMode, passiveUpdateMode), |
| listeners); |
| } |
| } |
| } |
| } |
| |
| public LockNotificationMode getLockNotificationMode() |
| { |
| return lockNotificationMode; |
| } |
| |
| public void setLockNotificationMode(LockNotificationMode lockNotificationMode) |
| { |
| checkArg(lockNotificationMode, "lockNotificationMode"); //$NON-NLS-1$ |
| if (this.lockNotificationMode != lockNotificationMode) |
| { |
| LockNotificationMode oldMode = this.lockNotificationMode; |
| this.lockNotificationMode = lockNotificationMode; |
| CDOSessionProtocol protocol = getSessionProtocol(); |
| if (protocol != null) |
| { |
| protocol.setLockNotificationMode(lockNotificationMode); |
| |
| IListener[] listeners = getListeners(); |
| if (listeners != null) |
| { |
| fireEvent(new LockNotificationModeEventImpl(oldMode, lockNotificationMode), listeners); |
| } |
| } |
| } |
| this.lockNotificationMode = lockNotificationMode; |
| } |
| |
| public CDOCollectionLoadingPolicy getCollectionLoadingPolicy() |
| { |
| synchronized (this) |
| { |
| return collectionLoadingPolicy; |
| } |
| } |
| |
| public void setCollectionLoadingPolicy(CDOCollectionLoadingPolicy policy) |
| { |
| if (policy == null) |
| { |
| policy = CDOUtil.createCollectionLoadingPolicy(CDORevision.UNCHUNKED, CDORevision.UNCHUNKED); |
| } |
| |
| CDOSession oldSession = policy.getSession(); |
| if (oldSession != null) |
| { |
| throw new IllegalArgumentException("Policy is already associated with " + oldSession); |
| } |
| |
| policy.setSession(CDOSessionImpl.this); |
| |
| IListener[] listeners = getListeners(); |
| IEvent event = null; |
| |
| synchronized (this) |
| { |
| if (collectionLoadingPolicy != policy) |
| { |
| collectionLoadingPolicy = policy; |
| if (listeners != null) |
| { |
| event = new CollectionLoadingPolicyEventImpl(); |
| } |
| } |
| } |
| |
| if (event != null) |
| { |
| fireEvent(event, listeners); |
| } |
| } |
| |
| public CDOLobStore getLobCache() |
| { |
| synchronized (this) |
| { |
| return lobCache; |
| } |
| } |
| |
| public void setLobCache(CDOLobStore cache) |
| { |
| if (cache == null) |
| { |
| cache = CDOLobStoreImpl.INSTANCE; |
| } |
| |
| IListener[] listeners = getListeners(); |
| IEvent event = null; |
| |
| synchronized (this) |
| { |
| if (lobCache != cache) |
| { |
| lobCache = cache; |
| if (listeners != null) |
| { |
| event = new LobCacheEventImpl(); |
| } |
| } |
| } |
| |
| if (event != null) |
| { |
| fireEvent(event, listeners); |
| } |
| } |
| |
| public CDOPermissionUpdater getPermissionUpdater() |
| { |
| synchronized (this) |
| { |
| return permissionUpdater; |
| } |
| } |
| |
| public void setPermissionUpdater(CDOPermissionUpdater permissionUpdater) |
| { |
| IListener[] listeners = getListeners(); |
| IEvent event = null; |
| |
| synchronized (this) |
| { |
| if (this.permissionUpdater != permissionUpdater) |
| { |
| this.permissionUpdater = permissionUpdater; |
| if (listeners != null) |
| { |
| event = new PermissionUpdaterEventImpl(); |
| } |
| } |
| } |
| |
| if (event != null) |
| { |
| fireEvent(event, listeners); |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private final class GeneratedPackageEmulationEventImpl extends OptionsEvent |
| implements GeneratedPackageEmulationEvent |
| { |
| private static final long serialVersionUID = 1L; |
| |
| public GeneratedPackageEmulationEventImpl() |
| { |
| super(OptionsImpl.this); |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private final class PassiveUpdateEventImpl extends OptionsEvent implements PassiveUpdateEvent |
| { |
| private static final long serialVersionUID = 1L; |
| |
| private boolean oldEnabled; |
| |
| private boolean newEnabled; |
| |
| private PassiveUpdateMode oldMode; |
| |
| private PassiveUpdateMode newMode; |
| |
| public PassiveUpdateEventImpl(boolean oldEnabled, boolean newEnabled, PassiveUpdateMode oldMode, |
| PassiveUpdateMode newMode) |
| { |
| super(OptionsImpl.this); |
| this.oldEnabled = oldEnabled; |
| this.newEnabled = newEnabled; |
| this.oldMode = oldMode; |
| this.newMode = newMode; |
| } |
| |
| public boolean getOldEnabled() |
| { |
| return oldEnabled; |
| } |
| |
| public boolean getNewEnabled() |
| { |
| return newEnabled; |
| } |
| |
| public PassiveUpdateMode getOldMode() |
| { |
| return oldMode; |
| } |
| |
| public PassiveUpdateMode getNewMode() |
| { |
| return newMode; |
| } |
| } |
| |
| /** |
| * @author Caspar De Groot |
| */ |
| private final class LockNotificationModeEventImpl extends OptionsEvent implements LockNotificationModeEvent |
| { |
| private static final long serialVersionUID = 1L; |
| |
| private LockNotificationMode oldMode, newMode; |
| |
| public LockNotificationModeEventImpl(LockNotificationMode oldMode, LockNotificationMode newMode) |
| { |
| super(OptionsImpl.this); |
| this.oldMode = oldMode; |
| this.newMode = newMode; |
| } |
| |
| public LockNotificationMode getOldMode() |
| { |
| return oldMode; |
| } |
| |
| public LockNotificationMode getNewMode() |
| { |
| return newMode; |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private final class CollectionLoadingPolicyEventImpl extends OptionsEvent implements CollectionLoadingPolicyEvent |
| { |
| private static final long serialVersionUID = 1L; |
| |
| public CollectionLoadingPolicyEventImpl() |
| { |
| super(OptionsImpl.this); |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private final class LobCacheEventImpl extends OptionsEvent implements LobCacheEvent |
| { |
| private static final long serialVersionUID = 1L; |
| |
| public LobCacheEventImpl() |
| { |
| super(OptionsImpl.this); |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private final class PermissionUpdaterEventImpl extends OptionsEvent implements PermissionUpdaterEvent |
| { |
| private static final long serialVersionUID = 1L; |
| |
| public PermissionUpdaterEventImpl() |
| { |
| super(OptionsImpl.this); |
| } |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private class Invalidator extends ExecutorWorkSerializer |
| { |
| private static final boolean DEBUG = false; |
| |
| private final Set<Object> unfinishedLocalCommits = new HashSet<Object>(); |
| |
| private final List<Invalidation> reorderQueue = new ArrayList<Invalidation>(); |
| |
| private boolean terminateIfSessionClosed; |
| |
| public Invalidator() |
| { |
| } |
| |
| public synchronized Object startLocalCommit() |
| { |
| if (!isActive()) |
| { |
| return null; |
| } |
| |
| final String threadName = Thread.currentThread().getName(); |
| Object token = new Object() |
| { |
| @Override |
| public String toString() |
| { |
| return threadName; |
| } |
| }; |
| |
| unfinishedLocalCommits.add(token); |
| return token; |
| } |
| |
| public synchronized void endLocalCommit(Object token) |
| { |
| unfinishedLocalCommits.remove(token); |
| } |
| |
| public synchronized void reorderInvalidations(CDOCommitInfo commitInfo, InternalCDOTransaction sender, |
| boolean clearResourcePathCache, byte securityImpact, Map<CDOID, CDOPermission> newPermissions) |
| { |
| Invalidation invalidation = new Invalidation(commitInfo, sender, clearResourcePathCache, securityImpact, |
| newPermissions); |
| |
| reorderQueue.add(invalidation); |
| Collections.sort(reorderQueue); |
| |
| if (DEBUG) |
| { |
| IOUtil.OUT() |
| .println(CDOSessionImpl.this + " [" + getLastUpdateTime() % 10000 + "] " |
| + commitInfo.getPreviousTimeStamp() % 10000 + " --> " + commitInfo.getTimeStamp() % 10000 |
| + " reorderQueue=" + reorderQueue + " unfinishedLocalCommits=" + unfinishedLocalCommits); |
| } |
| |
| scheduleInvalidations(); |
| } |
| |
| public synchronized void scheduleInvalidations() |
| { |
| while (!reorderQueue.isEmpty() && canProcess(reorderQueue.get(0))) |
| { |
| Invalidation invalidation0 = reorderQueue.remove(0); |
| addWork(invalidation0); |
| } |
| } |
| |
| protected boolean canProcess(Invalidation invalidation) |
| { |
| if (options().isPassiveUpdateEnabled()) |
| { |
| long previousTimeStamp = invalidation.getPreviousTimeStamp(); |
| long lastUpdateTime = getLastUpdateTime(); |
| return previousTimeStamp <= lastUpdateTime; // Can be smaller in replication scenarios |
| } |
| |
| return unfinishedLocalCommits.size() == 1; // Ourselves |
| } |
| |
| @Override |
| protected void noWork() |
| { |
| if (isClosed() && terminateIfSessionClosed) |
| { |
| dispose(); |
| } |
| } |
| |
| @Override |
| protected void doAfterActivate() throws Exception |
| { |
| super.doAfterActivate(); |
| terminateIfSessionClosed = true; |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private final class Invalidation implements Comparable<Invalidation>, Runnable |
| { |
| private final CDOCommitInfo commitInfo; |
| |
| private final InternalCDOTransaction sender; |
| |
| private final boolean clearResourcePathCache; |
| |
| private final byte securityImpact; |
| |
| private final Map<CDOID, CDOPermission> newPermissions; |
| |
| public Invalidation(CDOCommitInfo commitInfo, InternalCDOTransaction sender, boolean clearResourcePathCache, |
| byte securityImpact, Map<CDOID, CDOPermission> newPermissions) |
| { |
| this.commitInfo = commitInfo; |
| this.sender = sender; |
| this.clearResourcePathCache = clearResourcePathCache; |
| this.securityImpact = securityImpact; |
| this.newPermissions = newPermissions; |
| } |
| |
| public long getTimeStamp() |
| { |
| return commitInfo.getTimeStamp(); |
| } |
| |
| public long getPreviousTimeStamp() |
| { |
| return commitInfo.getPreviousTimeStamp(); |
| } |
| |
| public int compareTo(Invalidation o) |
| { |
| return CDOCommonUtil.compareTimeStamps(getTimeStamp(), o.getTimeStamp()); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return Long.toString(commitInfo.getTimeStamp() % 10000); |
| } |
| |
| public void run() |
| { |
| long timeStamp = commitInfo.getTimeStamp(); |
| |
| if (Invalidator.DEBUG) |
| { |
| IOUtil.OUT().println( |
| CDOSessionImpl.this + " [" + getLastUpdateTime() % 10000 + "] " + timeStamp % 10000 + " INVALIDATE"); |
| } |
| |
| try |
| { |
| InternalCDOView[] views = getViews(); |
| Map<CDORevision, CDOPermission> oldPermissions = null; |
| Map<CDOID, InternalCDORevision> oldRevisions = null; |
| |
| boolean success = commitInfo.getBranch() != null; |
| if (success) |
| { |
| oldRevisions = reviseRevisions(); |
| |
| if (securityImpact != CommitNotificationInfo.IMPACT_NONE) |
| { |
| oldPermissions = updatePermissions(views); |
| } |
| } |
| |
| if (options.isPassiveUpdateEnabled()/* || sender != null */) |
| { |
| setLastUpdateTime(timeStamp); |
| } |
| |
| if (success) |
| { |
| CDOSessionImpl.this.fireEvent(new InvalidationEvent(sender, commitInfo, securityImpact, oldPermissions)); |
| commitInfoManager.notifyCommitInfoHandlers(commitInfo); |
| } |
| |
| for (InternalCDOView view : views) |
| { |
| invalidateView(commitInfo, view, sender, oldRevisions, clearResourcePathCache); |
| } |
| } |
| catch (RuntimeException ex) |
| { |
| if (isActive()) |
| { |
| throw ex; |
| } |
| |
| if (TRACER.isEnabled()) |
| { |
| TRACER.trace(Messages.getString("CDOSessionImpl.2")); //$NON-NLS-1$ |
| } |
| } |
| finally |
| { |
| // setLastUpdateTime() is not synchronized with the Invalidator. |
| // Give the Invalidator another chance to schedule Invalidations. |
| invalidator.scheduleInvalidations(); |
| } |
| } |
| |
| private Map<CDORevision, CDOPermission> updatePermissions(InternalCDOView[] views) |
| { |
| CDOPermissionUpdater permissionUpdater = options().getPermissionUpdater(); |
| if (permissionUpdater != null) |
| { |
| CDOBranchPoint head = getBranchManager().getMainBranch().getHead(); |
| Set<InternalCDORevision> revisions = new HashSet<InternalCDORevision>(); |
| |
| for (int i = 0; i < views.length; i++) |
| { |
| InternalCDOView view = views[i]; |
| if (!head.equals(view)) |
| { |
| throw new IllegalStateException("Security not supported with auditing or branching"); |
| } |
| |
| for (InternalCDOObject object : view.getObjects().values()) |
| { |
| InternalCDORevision revision; |
| |
| CDOState state = object.cdoState(); |
| switch (state) |
| { |
| case CLEAN: |
| revision = object.cdoRevision(); |
| break; |
| |
| case DIRTY: |
| case CONFLICT: |
| CDOID id = object.cdoID(); |
| revision = getRevisionManager().getRevision(id, head, 0, CDORevision.DEPTH_NONE, false); |
| break; |
| |
| default: |
| continue; |
| } |
| |
| if (revision != null && !commitInfo.getDetachedObjects().contains(CDOIDUtil.createIDAndVersion(revision))) |
| { |
| revisions.add(revision); |
| } |
| } |
| } |
| |
| return permissionUpdater.updatePermissions(CDOSessionImpl.this, revisions); |
| } |
| |
| return null; |
| } |
| |
| private Map<CDOID, InternalCDORevision> reviseRevisions() |
| { |
| Map<CDOID, InternalCDORevision> oldRevisions = null; |
| CDOBranch newBranch = commitInfo.getBranch(); |
| long timeStamp = commitInfo.getTimeStamp(); |
| |
| // Cache new revisions |
| for (CDOIDAndVersion key : commitInfo.getNewObjects()) |
| { |
| if (key instanceof InternalCDORevision) |
| { |
| InternalCDORevision newRevision = (InternalCDORevision)key; |
| addNewRevision(newRevision); |
| } |
| } |
| |
| // Apply deltas and cache the resulting new revisions, if possible... |
| for (CDORevisionKey key : commitInfo.getChangedObjects()) |
| { |
| CDOID id = key.getID(); |
| if (key instanceof CDORevisionDelta) |
| { |
| CDORevisionDelta revisionDelta = (CDORevisionDelta)key; |
| |
| InternalCDORevision oldRevision = revisionManager.getRevisionByVersion(id, revisionDelta, |
| CDORevision.UNCHUNKED, false); |
| if (oldRevision != null) |
| { |
| addOldValuesToDelta(oldRevision, revisionDelta); |
| |
| InternalCDORevision newRevision = oldRevision.copy(); |
| newRevision.adjustForCommit(commitInfo.getBranch(), commitInfo.getTimeStamp()); |
| |
| CDORevisable target = revisionDelta.getTarget(); |
| if (target != null) |
| { |
| newRevision.setVersion(target.getVersion()); |
| } |
| |
| boolean bypassPermissionChecks = newRevision.bypassPermissionChecks(true); |
| |
| try |
| { |
| revisionDelta.applyTo(newRevision); |
| } |
| finally |
| { |
| newRevision.bypassPermissionChecks(bypassPermissionChecks); |
| newRevision.freeze(); |
| } |
| |
| addNewRevision(newRevision); |
| if (oldRevisions == null) |
| { |
| oldRevisions = CDOIDUtil.createMap(); |
| } |
| |
| oldRevisions.put(id, oldRevision); |
| } |
| } |
| else |
| { |
| // ... otherwise try to revise old revision if it is in the same branch |
| if (key.getBranch() == newBranch) |
| { |
| revisionManager.reviseVersion(id, key, timeStamp); |
| } |
| } |
| } |
| |
| // Revise old revisions |
| for (CDOIDAndVersion key : commitInfo.getDetachedObjects()) |
| { |
| CDOID id = key.getID(); |
| revisionManager.reviseLatest(id, newBranch); |
| } |
| |
| return oldRevisions; |
| } |
| |
| private void addNewRevision(InternalCDORevision newRevision) |
| { |
| if (newPermissions != null) |
| { |
| CDOPermission newPermission = newPermissions.get(newRevision.getID()); |
| if (newPermission != null) |
| { |
| newRevision.setPermission(newPermission); |
| } |
| } |
| |
| revisionManager.addRevision(newRevision); |
| } |
| |
| private void invalidateView(CDOCommitInfo commitInfo, InternalCDOView view, InternalCDOTransaction sender, |
| Map<CDOID, InternalCDORevision> oldRevisions, boolean clearResourcePathCache) |
| { |
| try |
| { |
| long lastUpdateTime = commitInfo.getTimeStamp(); |
| if (view == sender) |
| { |
| // The committing view (sender) is already valid, just the timestamp must be set "in sequence". |
| // Setting the sender's timestamp synchronously can lead to deadlock |
| view.invalidate(null, lastUpdateTime, null, null, null, true, false); |
| } |
| else |
| { |
| CDOBranch branch = commitInfo.getBranch(); |
| List<CDORevisionKey> allChangedObjects = commitInfo.getChangedObjects(); |
| List<CDOIDAndVersion> allDetachedObjects = commitInfo.getDetachedObjects(); |
| |
| view.invalidate(branch, lastUpdateTime, allChangedObjects, allDetachedObjects, oldRevisions, true, |
| clearResourcePathCache); |
| } |
| } |
| catch (RuntimeException ex) |
| { |
| if (view.isActive()) |
| { |
| OM.LOG.error(ex); |
| } |
| else |
| { |
| if (TRACER.isEnabled()) |
| { |
| TRACER.trace(Messages.getString("CDOSessionImpl.1")); //$NON-NLS-1$ |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private final class InvalidationEvent extends Event implements InternalCDOSessionInvalidationEvent |
| { |
| private static final long serialVersionUID = 1L; |
| |
| private InternalCDOTransaction sender; |
| |
| private CDOCommitInfo commitInfo; |
| |
| private byte securityImpact; |
| |
| private Map<CDORevision, CDOPermission> oldPermissions; |
| |
| public InvalidationEvent(InternalCDOTransaction sender, CDOCommitInfo commitInfo, byte securityImpact, |
| Map<CDORevision, CDOPermission> oldPermissions) |
| { |
| super(CDOSessionImpl.this); |
| this.sender = sender; |
| this.commitInfo = commitInfo; |
| this.securityImpact = securityImpact; |
| this.oldPermissions = oldPermissions; |
| } |
| |
| @Override |
| public CDOSession getSource() |
| { |
| return (CDOSession)super.getSource(); |
| } |
| |
| public CDOCommitInfoManager getCommitInfoManager() |
| { |
| return commitInfo.getCommitInfoManager(); |
| } |
| |
| public CDOTransaction getLocalTransaction() |
| { |
| return sender; |
| } |
| |
| @Deprecated |
| public InternalCDOView getView() |
| { |
| return sender; |
| } |
| |
| public boolean isRemote() |
| { |
| return sender == null; |
| } |
| |
| public CDOBranch getBranch() |
| { |
| return commitInfo.getBranch(); |
| } |
| |
| public long getTimeStamp() |
| { |
| return commitInfo.getTimeStamp(); |
| } |
| |
| public long getPreviousTimeStamp() |
| { |
| return commitInfo.getPreviousTimeStamp(); |
| } |
| |
| public String getUserID() |
| { |
| return commitInfo.getUserID(); |
| } |
| |
| public String getComment() |
| { |
| return commitInfo.getComment(); |
| } |
| |
| public boolean isEmpty() |
| { |
| return false; |
| } |
| |
| public boolean isInitialCommit() |
| { |
| return commitInfo.isInitialCommit(); |
| } |
| |
| public boolean isCommitDataLoaded() |
| { |
| return commitInfo.isCommitDataLoaded(); |
| } |
| |
| public CDOChangeSetData copy() |
| { |
| return commitInfo.copy(); |
| } |
| |
| public void merge(CDOChangeSetData changeSetData) |
| { |
| commitInfo.merge(changeSetData); |
| } |
| |
| public List<CDOPackageUnit> getNewPackageUnits() |
| { |
| return commitInfo.getNewPackageUnits(); |
| } |
| |
| public List<CDOIDAndVersion> getNewObjects() |
| { |
| return commitInfo.getNewObjects(); |
| } |
| |
| public List<CDORevisionKey> getChangedObjects() |
| { |
| return commitInfo.getChangedObjects(); |
| } |
| |
| public List<CDOIDAndVersion> getDetachedObjects() |
| { |
| return commitInfo.getDetachedObjects(); |
| } |
| |
| public Map<CDOID, CDOChangeKind> getChangeKinds() |
| { |
| return commitInfo.getChangeKinds(); |
| } |
| |
| public CDOChangeKind getChangeKind(CDOID id) |
| { |
| return commitInfo.getChangeKind(id); |
| } |
| |
| public byte getSecurityImpact() |
| { |
| return securityImpact; |
| } |
| |
| public Map<CDORevision, CDOPermission> getOldPermissions() |
| { |
| return oldPermissions; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "CDOSessionInvalidationEvent[" + commitInfo + "]"; //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| /** |
| * @author Caspar De Groot |
| * @since 4.1 |
| */ |
| private final class LocksChangedEvent extends DefaultLocksChangedEvent implements CDOSessionLocksChangedEvent |
| { |
| private static final long serialVersionUID = 1L; |
| |
| public LocksChangedEvent(InternalCDOView sender, CDOLockChangeInfo lockChangeInfo) |
| { |
| super(CDOSessionImpl.this, sender, lockChangeInfo); |
| } |
| |
| @Override |
| public CDOSession getSource() |
| { |
| return (CDOSession)super.getSource(); |
| } |
| } |
| } |