/***************************************************************************
* Copyright (c) 2004 - 2008 Eike Stepper, Germany.
* 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 - maintenance
* Victor Roldan Betancort - http://bugs.eclipse.org/208689
**************************************************************************/
package org.eclipse.emf.internal.cdo;
import org.eclipse.emf.cdo.CDOChangeSubscriptionPolicy;
import org.eclipse.emf.cdo.CDOObject;
import org.eclipse.emf.cdo.CDORevisionPrefetchingPolicy;
import org.eclipse.emf.cdo.CDOState;
import org.eclipse.emf.cdo.CDOView;
import org.eclipse.emf.cdo.CDOViewEvent;
import org.eclipse.emf.cdo.CDOViewSet;
import org.eclipse.emf.cdo.analyzer.CDOFeatureAnalyzer;
import org.eclipse.emf.cdo.common.CDOProtocolConstants;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.id.CDOIDAndVersion;
import org.eclipse.emf.cdo.common.id.CDOIDMeta;
import org.eclipse.emf.cdo.common.id.CDOIDProvider;
import org.eclipse.emf.cdo.common.id.CDOIDUtil;
import org.eclipse.emf.cdo.common.model.CDOClass;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.common.revision.CDORevisionResolver;
import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta;
import org.eclipse.emf.cdo.common.util.CDOException;
import org.eclipse.emf.cdo.common.util.TransportException;
import org.eclipse.emf.cdo.eresource.CDOResource;
import org.eclipse.emf.cdo.eresource.impl.CDOResourceImpl;
import org.eclipse.emf.cdo.query.CDOQuery;
import org.eclipse.emf.cdo.spi.common.InternalCDORevision;
import org.eclipse.emf.cdo.util.CDOURIUtil;
import org.eclipse.emf.cdo.util.CDOUtil;
import org.eclipse.emf.cdo.util.ReadOnlyException;
import org.eclipse.emf.internal.cdo.bundle.OM;
import org.eclipse.emf.internal.cdo.protocol.ChangeSubscriptionRequest;
import org.eclipse.emf.internal.cdo.protocol.ResourceIDRequest;
import org.eclipse.emf.internal.cdo.protocol.ResourcePathRequest;
import org.eclipse.emf.internal.cdo.query.CDOQueryImpl;
import org.eclipse.emf.internal.cdo.util.FSMUtil;
import org.eclipse.emf.internal.cdo.util.ModelUtil;
import org.eclipse.net4j.signal.failover.IFailOverStrategy;
import org.eclipse.net4j.util.ImplementationError;
import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump;
import org.eclipse.net4j.util.om.trace.ContextTracer;
import org.eclipse.net4j.util.ref.ReferenceValueMap;
import org.eclipse.net4j.util.transaction.TransactionException;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.impl.NotificationImpl;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Eike Stepper
*/
public class CDOViewImpl extends org.eclipse.net4j.util.event.Notifier implements CDOView, CDOIDProvider
{
private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_VIEW, CDOViewImpl.class);
private int viewID;
private CDOSessionImpl session;
private CDOViewSet viewSet;
private boolean uniqueResourceContents = true;
private boolean invalidationNotificationEnabled;
private CDORevisionPrefetchingPolicy revisionPrefetchingPolicy;
private CDOFeatureAnalyzer featureAnalyzer = CDOFeatureAnalyzer.NOOP;
private ConcurrentMap<CDOID, InternalCDOObject> objects;
private CDOStore store = new CDOStore(this);
private ReentrantLock lock = new ReentrantLock(true);
@ExcludeFromDump
private transient CDOID lastLookupID;
@ExcludeFromDump
private transient InternalCDOObject lastLookupObject;
/**
* @since 2.0
*/
private ChangeSubscriptionManager changeSubscriptionManager = new ChangeSubscriptionManager();
/**
* @since 2.0
*/
private CDOChangeSubscriptionPolicy changeSubscriptionPolicy = CDOChangeSubscriptionPolicy.NONE;
/**
* @since 2.0
*/
public CDOViewImpl(CDOSessionImpl session, int viewID)
{
this.session = session;
this.viewID = viewID;
invalidationNotificationEnabled = OM.PREF_ENABLE_INVALIDATION_NOTIFICATION.getValue();
revisionPrefetchingPolicy = CDOUtil.createRevisionPrefetchingPolicy(OM.PREF_REVISION_LOADING_CHUNK_SIZE.getValue());
objects = createObjectsMap();
}
public int getViewID()
{
return viewID;
}
public Type getViewType()
{
return Type.READONLY;
}
public ResourceSet getResourceSet()
{
return viewSet.getResourceSet();
}
/**
* @since 2.0
*/
public CDOViewSet getViewSet()
{
return viewSet;
}
public CDOSessionImpl getSession()
{
return session;
}
public CDOStore getStore()
{
return store;
}
/**
* @since 2.0
*/
public ReentrantLock getLock()
{
return lock;
}
public boolean isDirty()
{
return false;
}
public boolean hasConflict()
{
return false;
}
@Deprecated
public boolean hasUniqueResourceContents()
{
return uniqueResourceContents;
}
@Deprecated
public void setUniqueResourceContents(boolean uniqueResourceContents)
{
this.uniqueResourceContents = uniqueResourceContents;
}
/**
* @since 2.0
*/
public boolean isInvalidationNotificationEnabled()
{
return invalidationNotificationEnabled;
}
/**
* @since 2.0
*/
public void setInvalidationNotificationEnabled(boolean enabled)
{
invalidationNotificationEnabled = enabled;
}
/**
* @since 2.0
*/
public CDOChangeSubscriptionPolicy getChangeSubscriptionPolicy()
{
return changeSubscriptionPolicy;
}
/**
* @since 2.0
*/
public void setChangeSubscriptionPolicy(CDOChangeSubscriptionPolicy subscriptionPolicy)
{
if (changeSubscriptionPolicy != subscriptionPolicy)
{
changeSubscriptionPolicy = subscriptionPolicy;
changeSubscriptionManager.notifyChangeSubcriptionPolicy();
}
}
/**
* @since 2.0
*/
public CDORevisionPrefetchingPolicy getRevisionPrefetchingPolicy()
{
return revisionPrefetchingPolicy;
}
/**
* @since 2.0
*/
public void setRevisionPrefetchingPolicy(CDORevisionPrefetchingPolicy prefetchingPolicy)
{
if (prefetchingPolicy == null)
{
prefetchingPolicy = CDORevisionPrefetchingPolicy.NO_PREFETCHING;
}
revisionPrefetchingPolicy = prefetchingPolicy;
}
public CDOFeatureAnalyzer getFeatureAnalyzer()
{
return featureAnalyzer;
}
public void setFeatureAnalyzer(CDOFeatureAnalyzer featureAnalyzer)
{
this.featureAnalyzer = featureAnalyzer == null ? CDOFeatureAnalyzer.NOOP : featureAnalyzer;
}
public CDOTransactionImpl toTransaction()
{
checkOpen();
if (this instanceof CDOTransactionImpl)
{
return (CDOTransactionImpl)this;
}
throw new ReadOnlyException("CDO view is read only: " + this);
}
public boolean hasResource(String path)
{
checkOpen();
CDOID id = getResourceID(path);
return id != null && !id.isNull();
}
/**
* @since 2.0
*/
public CDOQuery createQuery(String language, String queryString)
{
checkOpen();
return new CDOQueryImpl(this, language, queryString);
}
public CDOID getResourceID(String path)
{
try
{
CDOResource resource = getResource(path, false);
if (resource != null && resource.cdoID() != null)
{
return resource.cdoID();
}
IFailOverStrategy failOverStrategy = session.getFailOverStrategy();
ResourceIDRequest request = new ResourceIDRequest(session.getChannel(), viewID, path);
return failOverStrategy.send(request);
}
catch (Exception ex)
{
throw new TransactionException(ex);
}
}
/**
* @since 2.0
*/
public InternalCDOObject[] getObjectsArray()
{
synchronized (objects)
{
return objects.values().toArray(new InternalCDOObject[objects.size()]);
}
}
/**
* @since 2.0
*/
public CDOResource getResource(String path)
{
checkOpen();
return getResource(path, true);
}
/**
* @since 2.0
*/
public CDOResource getResource(String path, boolean loadInDemand)
{
checkOpen();
URI uri = CDOURIUtil.createResourceURI(this, path);
return (CDOResource)getResourceSet().getResource(uri, loadInDemand);
}
/**
* @since 2.0
*/
public List<CDOResource> queryResources(String pathPrefix)
{
checkOpen();
CDOQuery resourceQuery = createQuery(CDOProtocolConstants.QUERY_LANGUAGE_RESOURCES, pathPrefix);
return resourceQuery.getResult(CDOResource.class);
}
public CDOResourceImpl getResource(CDOID resourceID)
{
if (resourceID == null || resourceID.isNull())
{
throw new IllegalArgumentException("resourceID: " + resourceID);
}
CDOResourceImpl resource = (CDOResourceImpl)getObject(resourceID);
if (resource != null)
{
return resource;
}
try
{
IFailOverStrategy failOverStrategy = session.getFailOverStrategy();
ResourcePathRequest request = new ResourcePathRequest(session.getChannel(), viewID, resourceID);
String path = failOverStrategy.send(request);
return addResource(resourceID, path);
}
catch (RuntimeException ex)
{
throw ex;
}
catch (Exception ex)
{
throw new TransportException(ex);
}
}
public CDOResourceImpl addResource(CDOID id, String path)
{
URI createURI = CDOURIUtil.createResourceURI(this, path);
CDOResourceImpl resource = (CDOResourceImpl)viewSet.getResourceFactory().createResource(createURI);
resource.setURI(createURI);
InternalCDOObject resourceObject = resource;
resourceObject.cdoInternalSetID(id);
resourceObject.cdoInternalSetView(this);
resourceObject.cdoInternalSetResource(resource);
resourceObject.cdoInternalSetState(CDOState.PROXY);
ResourceSet resourceSet = getResourceSet();
resourceSet.getResources().add(resource);
return resource;
}
public InternalCDOObject newInstance(EClass eClass)
{
EObject eObject = EcoreUtil.create(eClass);
return FSMUtil.adapt(eObject, this);
}
public InternalCDOObject newInstance(CDOClass cdoClass)
{
EClass eClass = ModelUtil.getEClass(cdoClass, session.getPackageRegistry());
if (eClass == null)
{
throw new IllegalStateException("No EClass for " + cdoClass);
}
return newInstance(eClass);
}
public InternalCDORevision getRevision(CDOID id, boolean loadOnDemand)
{
CDORevisionResolver revisionManager = session.getRevisionManager();
int initialChunkSize = session.getCollectionLoadingPolicy().getInitialChunkSize();
return (InternalCDORevision)revisionManager.getRevision(id, initialChunkSize, loadOnDemand);
}
public InternalCDOObject getObject(CDOID id)
{
return getObject(id, true);
}
/**
* Support recursivity and concurrency.
*/
public InternalCDOObject getObject(CDOID id, boolean loadOnDemand)
{
checkOpen();
if (id == null || id.isNull())
{
return null;
}
synchronized (objects)
{
if (id.equals(lastLookupID))
{
return lastLookupObject;
}
// Needed for recursive call to getObject. (from createObject/cleanObject/getResource/getObject)
InternalCDOObject localLookupObject = objects.get(id);
if (localLookupObject == null)
{
if (id.isMeta())
{
localLookupObject = createMetaObject((CDOIDMeta)id);
}
else
{
if (loadOnDemand)
{
localLookupObject = createObject(id);
}
else
{
return null;
}
}
registerObject(localLookupObject);
}
lastLookupID = id;
lastLookupObject = localLookupObject;
return lastLookupObject;
}
}
/**
* @since 2.0
*/
@SuppressWarnings("unchecked")
public <T extends EObject> T getObject(T objectFromDifferentView)
{
checkOpen();
if (objectFromDifferentView instanceof CDOObject)
{
CDOObject object = (CDOObject)objectFromDifferentView;
CDOView view = object.cdoView();
if (view != this)
{
if (view.getSession() != session)
{
throw new IllegalArgumentException("Object is contaiuned in a different session: " + objectFromDifferentView);
}
CDOID id = object.cdoID();
objectFromDifferentView = (T)getObject(id, true);
}
}
return objectFromDifferentView;
}
public boolean isObjectRegistered(CDOID id)
{
checkOpen();
if (id == null || id.isNull())
{
return false;
}
synchronized (objects)
{
return objects.containsKey(id);
}
}
public InternalCDOObject removeObject(CDOID id)
{
synchronized (objects)
{
if (id.equals(lastLookupID))
{
lastLookupID = null;
lastLookupObject = null;
}
return objects.remove(id);
}
}
/**
* @return Never <code>null</code>
*/
private InternalCDOObject createMetaObject(CDOIDMeta id)
{
if (TRACER.isEnabled())
{
TRACER.trace("Creating meta object for " + id);
}
InternalEObject metaInstance = session.lookupMetaInstance(id);
if (metaInstance == null)
{
throw new ImplementationError("No metaInstance for " + id);
}
return new CDOMetaWrapper(this, metaInstance, id);
}
/**
* @return Never <code>null</code>
*/
private InternalCDOObject createObject(CDOID id)
{
if (TRACER.isEnabled())
{
TRACER.trace("Creating object for " + id);
}
InternalCDORevision revision = getRevision(id, true);
CDOUtil.validate(id, revision);
CDOClass cdoClass = revision.getCDOClass();
InternalCDOObject object = newInstance(cdoClass);
cleanObject(object, revision);
return object;
}
/**
* @since 2.0
*/
protected void cleanObject(InternalCDOObject object, InternalCDORevision revision)
{
if (object instanceof CDOResourceImpl)
{
object.cdoInternalSetResource((CDOResourceImpl)object);
}
else
{
CDOID resourceID = revision.getResourceID();
if (!resourceID.isNull())
{
CDOResourceImpl resource = getResource(resourceID);
object.cdoInternalSetResource(resource);
}
}
object.cdoInternalSetView(this);
object.cdoInternalSetRevision(revision);
object.cdoInternalSetID(revision.getID());
object.cdoInternalSetState(CDOState.CLEAN);
object.cdoInternalPostLoad();
}
public CDOID provideCDOID(Object idOrObject)
{
Object shouldBeCDOID = convertObjectToID(idOrObject);
if (shouldBeCDOID instanceof CDOID)
{
CDOID id = (CDOID)shouldBeCDOID;
if (TRACER.isEnabled() && id != idOrObject)
{
TRACER.format("Converted dangling reference: {0} --> {1}", idOrObject, id);
}
return id;
}
else if (idOrObject instanceof InternalEObject)
{
InternalEObject eObject = (InternalEObject)idOrObject;
String uri = EcoreUtil.getURI(eObject).toString();
if (eObject instanceof InternalCDOObject)
{
InternalCDOObject object = (InternalCDOObject)idOrObject;
if (object.cdoView() != null && FSMUtil.isNew(object))
{
return CDOIDUtil.createExternalTemp(uri);
}
}
if (eObject.eResource() != null)
{
return CDOIDUtil.createExternal(uri);
}
}
throw new IllegalStateException("Unable to provideCDOID: " + idOrObject.getClass().getName());
}
public Object convertObjectToID(Object potentialObject)
{
return convertObjectToID(potentialObject, false);
}
/**
* @since 2.0
*/
public Object convertObjectToID(Object potentialObject, boolean onlyPersistedID)
{
if (potentialObject instanceof CDOID)
{
return potentialObject;
}
if (potentialObject instanceof InternalEObject && !(potentialObject instanceof InternalCDOObject))
{
// TODO LEGACY
// try
// {
// InternalEObject eObject = (InternalEObject)potentialObject;
// Object legacyListener = FSMUtil.getLegacyWrapper(eObject);
// if (legacyListener != null)
// {
// potentialObject = legacyListener;
// }
// }
// catch (Throwable ex)
// {
// OM.LOG.warn(ex);
// }
}
if (potentialObject instanceof InternalCDOObject)
{
InternalCDOObject object = (InternalCDOObject)potentialObject;
boolean newOrTransient = FSMUtil.isTransient(object) || FSMUtil.isNew(object);
if (object.cdoView() == this && (!onlyPersistedID || !newOrTransient))
{
return object.cdoID();
}
}
return potentialObject;
}
public Object convertIDToObject(Object potentialID)
{
if (potentialID instanceof CDOID)
{
if (potentialID == CDOID.NULL)
{
return null;
}
CDOID id = (CDOID)potentialID;
if (id.isExternal())
{
return getResourceSet().getEObject(URI.createURI(id.toURIFragment()), true);
}
InternalCDOObject result = getObject(id, true);
if (result == null)
{
throw new ImplementationError("ID not registered: " + id);
}
return result.cdoInternalInstance();
}
return potentialID;
}
/**
* @since 2.0
*/
public boolean registerProxyResource(CDOResourceImpl resource)
{
CDOID id = getResourceID(resource.getPath());
boolean exists = id != null && !id.isNull();
if (exists)
{
resource.cdoInternalSetResource(resource);
resource.cdoInternalSetView(this);
resource.cdoInternalSetID(id);
resource.cdoInternalSetState(CDOState.PROXY);
registerObject(resource);
}
return exists;
}
public void registerObject(InternalCDOObject object)
{
if (TRACER.isEnabled())
{
TRACER.format("Registering {0}", object);
}
InternalCDOObject old;
synchronized (objects)
{
old = objects.put(object.cdoID(), object);
}
if (old != null)
{
throw new IllegalStateException("Duplicate ID: " + object);
}
}
public void deregisterObject(InternalCDOObject object)
{
if (TRACER.isEnabled())
{
TRACER.format("Deregistering {0}", object);
}
removeObject(object.cdoID());
}
public void remapObject(CDOID oldID)
{
CDOID newID;
synchronized (objects)
{
InternalCDOObject object = objects.remove(oldID);
newID = object.cdoID();
objects.put(newID, object);
}
if (TRACER.isEnabled())
{
TRACER.format("Remapping {0} --> {1}", oldID, newID);
}
}
/**
* Turns registered objects into proxies and synchronously delivers invalidation events to registered event listeners.
* <p>
* <b>Implementation note:</b> This implementation guarantees that exceptions from listener code don't propagate up to
* the caller of this method. Runtime exceptions from the implementation of the {@link CDOStateMachine} are propagated
* to the caller of this method but this should not happen in the absence of implementation errors.
* <p>
* Note that this method can block for an uncertain time on the reentrant view lock!
*
* @param timeStamp
* The time stamp of the server transaction if this event was sent as a result of a successfully committed
* transaction or <code>LOCAL_ROLLBACK</code> if this event was sent due to a local rollback.
* @param dirtyOIDs
* A set of the object IDs to be invalidated. <b>Implementation note:</b> This implementation expects the
* dirtyOIDs set to be unmodifiable. It does not wrap the set (again).
* @since 2.0
*/
public void handleInvalidation(long timeStamp, Set<CDOIDAndVersion> dirtyOIDs, Collection<CDOID> detachedObjects)
{
List<InternalCDOObject> dirtyObjects = invalidationNotificationEnabled ? new ArrayList<InternalCDOObject>() : null;
lock.lock();
try
{
for (CDOIDAndVersion dirtyOID : dirtyOIDs)
{
InternalCDOObject dirtyObject;
synchronized (objects)
{
dirtyObject = objects.get(dirtyOID.getID());
if (dirtyObject != null)
{
CDOStateMachine.INSTANCE.invalidate(dirtyObject, timeStamp);
}
}
if (dirtyObject != null && dirtyObjects != null && dirtyObject.eNotificationRequired())
{
dirtyObjects.add(dirtyObject);
}
}
for (CDOID id : detachedObjects)
{
InternalCDOObject cdoObject = removeObject(id);
if (cdoObject != null)
{
CDOStateMachine.INSTANCE.detachRemote(cdoObject);
if (dirtyObjects != null && cdoObject.eNotificationRequired())
{
dirtyObjects.add(cdoObject);
}
}
}
if (dirtyObjects != null)
{
for (InternalCDOObject dirtyObject : dirtyObjects)
{
CDOInvalidationNotificationImpl notification = new CDOInvalidationNotificationImpl(dirtyObject);
dirtyObject.eNotify(notification);
}
}
}
finally
{
lock.unlock();
}
}
/**
* @since 2.0
*/
public void handleChangeSubscription(Collection<CDORevisionDelta> deltas)
{
if (deltas != null && getChangeSubscriptionPolicy() != CDOChangeSubscriptionPolicy.NONE)
{
CDONotificationBuilder builder = new CDONotificationBuilder(getSession().getPackageRegistry());
for (CDORevisionDelta delta : deltas)
{
InternalCDOObject object = objects.get(delta.getID());
if (object != null && object.eNotificationRequired() && changeSubscriptionManager.hasSubscription(object))
{
NotificationImpl notification = builder.buildNotification(object, delta);
if (notification != null)
{
notification.dispatch();
}
}
}
}
}
/**
* @since 2.0
*/
public void subscribe(EObject eObject, Adapter adapter)
{
changeSubscriptionManager.subscribe(eObject, adapter);
}
/**
* @since 2.0
*/
public void unsubscribe(EObject eObject, Adapter adapter)
{
changeSubscriptionManager.unsubscribe(eObject, adapter);
}
/**
* @since 2.0
*/
protected ChangeSubscriptionManager getChangeSubscriptionManager()
{
return changeSubscriptionManager;
}
protected ConcurrentMap<CDOID, InternalCDOObject> createObjectsMap()
{
return new ReferenceValueMap.Weak<CDOID, InternalCDOObject>();
}
/**
* Needed for {@link CDOAuditImpl#setTimeStamp(long)}.
*
* @since 2.0
*/
protected List<InternalCDOObject> getInvalidObjects(long timeStamp)
{
List<InternalCDOObject> result = new ArrayList<InternalCDOObject>();
synchronized (objects)
{
for (InternalCDOObject object : objects.values())
{
CDORevision revision = object.cdoRevision();
if (!revision.isValid(timeStamp))
{
result.add(object);
}
}
}
return result;
}
public int reload(CDOObject... objects)
{
Collection<InternalCDOObject> internalObjects;
// TODO Should objects.length == 0 reload *all* objects, too?
if (objects != null && objects.length != 0)
{
internalObjects = new ArrayList<InternalCDOObject>(objects.length);
for (CDOObject object : objects)
{
if (object instanceof InternalCDOObject)
{
internalObjects.add((InternalCDOObject)object);
}
}
}
else
{
synchronized (this.objects)
{
internalObjects = new ArrayList<InternalCDOObject>(this.objects.values());
}
}
int result = internalObjects.size();
if (result != 0)
{
CDOStateMachine.INSTANCE.reload(internalObjects.toArray(new InternalCDOObject[result]));
}
return result;
}
public void close()
{
session.viewDetached(this);
session = null;
objects = null;
store = null;
viewSet = null;
changeSubscriptionManager = null;
changeSubscriptionPolicy = null;
revisionPrefetchingPolicy = null;
featureAnalyzer = null;
lastLookupID = null;
lastLookupObject = null;
}
/**
* @since 2.0
*/
public boolean isClosed()
{
return session == null;
}
@Override
public String toString()
{
return MessageFormat.format("CDOView({0})", viewID);
}
public boolean isAdapterForType(Object type)
{
return type instanceof ResourceSet;
}
public Notifier getTarget()
{
return getResourceSet();
}
/**
* @since 2.0
*/
public void setViewSet(CDOViewSet viewSet)
{
this.viewSet = viewSet;
}
private void checkOpen()
{
if (isClosed())
{
throw new IllegalStateException("View closed");
}
}
/**
* @author Eike Stepper
*/
protected abstract class Event extends org.eclipse.net4j.util.event.Event implements CDOViewEvent
{
private static final long serialVersionUID = 1L;
public Event()
{
super(CDOViewImpl.this);
}
public CDOViewImpl getView()
{
return CDOViewImpl.this;
}
}
/**
* @author Simon McDuff
* @since 2.0
*/
protected class ChangeSubscriptionManager
{
private Map<InternalCDOObject, Integer> subscriptions = new HashMap<InternalCDOObject, Integer>();
private Map<InternalCDOObject, Integer> pendingSubscriptions = new HashMap<InternalCDOObject, Integer>();
private boolean isPending(InternalCDOObject internalCDOObject)
{
return internalCDOObject.cdoID().isTemporary();
}
protected int getNumberOfValidAdapter(InternalCDOObject object)
{
int count = 0;
if (object.eNotificationRequired())
{
for (Adapter adapter : object.eAdapters())
{
if (changeSubscriptionPolicy.shouldSubscribe(object, adapter))
{
count++;
}
}
}
return count;
}
/**
* Register to the server all objects from the active list
*/
protected void notifyChangeSubcriptionPolicy()
{
synchronized (subscriptions)
{
subscriptions.clear();
pendingSubscriptions.clear();
List<CDOID> cdoIDs = new ArrayList<CDOID>();
if (changeSubscriptionPolicy != CDOChangeSubscriptionPolicy.NONE)
{
for (InternalCDOObject cdoObject : getObjectsArray())
{
int count = getNumberOfValidAdapter(cdoObject);
if (count > 0)
{
cdoIDs.add(cdoObject.cdoID());
boolean isPending = isPending(cdoObject);
Map<InternalCDOObject, Integer> subscribersMap = isPending ? pendingSubscriptions : subscriptions;
subscribersMap.put(cdoObject, count);
}
}
}
request(cdoIDs, true, true);
}
}
protected void request(List<CDOID> cdoIDs, boolean clear, boolean subscribeMode)
{
try
{
ChangeSubscriptionRequest request = new ChangeSubscriptionRequest(getSession().getChannel(), getViewID(),
cdoIDs, subscribeMode, clear);
session.getFailOverStrategy().send(request);
}
catch (Exception ex)
{
throw new TransactionException(ex);
}
}
protected void notifyDirtyObjects()
{
synchronized (subscriptions)
{
List<InternalCDOObject> objectsToRemove = new ArrayList<InternalCDOObject>();
for (Entry<InternalCDOObject, Integer> entry : pendingSubscriptions.entrySet())
{
if (!isPending(entry.getKey()))
{
subscribe(entry.getKey(), entry.getValue());
objectsToRemove.add(entry.getKey());
}
}
for (InternalCDOObject internalCDOObject : objectsToRemove)
{
pendingSubscriptions.remove(internalCDOObject);
}
}
}
protected boolean hasSubscription(InternalCDOObject eObject)
{
return subscriptions.get(eObject) != null;
}
private void subscribe(EObject eObject, Adapter adapter, int adjust)
{
synchronized (subscriptions)
{
if (getChangeSubscriptionPolicy().shouldSubscribe(eObject, adapter))
{
subscribe(eObject, adjust);
}
}
}
private void subscribe(EObject eObject, int adjust)
{
synchronized (subscriptions)
{
InternalCDOObject internalCDOObject = FSMUtil.adapt(eObject, CDOViewImpl.this);
if (internalCDOObject.cdoView() != CDOViewImpl.this)
{
throw new CDOException("Object " + internalCDOObject + " doesn`t belong to this view.");
}
boolean isPending = isPending(internalCDOObject);
Map<InternalCDOObject, Integer> subscribersMap = isPending ? pendingSubscriptions : subscriptions;
Integer count = subscribersMap.get(internalCDOObject);
if (count == null)
{
// Cannot adjust negative value
if (adjust < 0)
{
throw new IllegalStateException("Object " + internalCDOObject.cdoID() + " cannot be unsubscribe");
}
count = 0;
// Notification need to be enable to send correct value to the server
if (!isPending && getChangeSubscriptionPolicy() != CDOChangeSubscriptionPolicy.NONE)
{
request(Collections.singletonList(internalCDOObject.cdoID()), false, true);
}
}
count += adjust;
// Look if objects need to be unsubscribe
if (count <= 0)
{
subscribersMap.remove(internalCDOObject);
// Notification need to be enable to send correct value to the server
if (!isPending && getChangeSubscriptionPolicy() != CDOChangeSubscriptionPolicy.NONE)
{
request(Collections.singletonList(internalCDOObject.cdoID()), false, false);
}
}
else
{
subscribersMap.put(internalCDOObject, count);
}
}
}
public void subscribe(EObject eObject, Adapter adapter)
{
subscribe(eObject, adapter, 1);
}
public void unsubscribe(EObject eObject, Adapter adapter)
{
subscribe(eObject, adapter, -1);
}
}
}