blob: d89063f8d35aec9e7d151afd67ccae023103157d [file] [log] [blame]
/*
* Copyright (c) 2018 CEA, 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
*
*/
package org.eclipse.uml2.uml.cdo.internal.util;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.emf.cdo.CDOObject;
import org.eclipse.emf.cdo.CDOObjectReference;
import org.eclipse.emf.cdo.CDOState;
import org.eclipse.emf.cdo.util.CDOUtil;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.emf.cdo.view.CDOViewInvalidationEvent;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EContentsEList.FeatureIterator;
import org.eclipse.emf.ecore.util.ECrossReferenceAdapter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.internal.cdo.CDOObjectImpl;
import org.eclipse.emf.spi.cdo.InternalCDOObject;
import org.eclipse.net4j.util.event.IEvent;
import org.eclipse.net4j.util.event.IListener;
import org.eclipse.net4j.util.lifecycle.ILifecycle;
import org.eclipse.net4j.util.lifecycle.LifecycleEventAdapter;
import org.eclipse.uml2.common.util.CacheAdapter;
/**
* A {@link CacheAdapter cache adapter} with special treatment for persistent
* {@link CDOObjectImpl CDO objects}.
* <p>
* This adapter works for:
* <p>
* <ol>
* <li>Legacy objects (objects that are not instances of {@link CDOObjectImpl}).
* <li>Transient native objects (objects with object.cdoState() ==
* {@link CDOState#TRANSIENT}).
* <li>Persistent native objects (objects with object.cdoState() !=
* {@link CDOState#TRANSIENT}).
* </ol>
* <p>
* For all objects except persistent native objects (case 3) this adapter
* behaves exactly like the standard UML {@link CacheAdapter}. For persistent
* native objects (case 3) this adapter behaves differently in the following two
* aspects:
* <p>
* <ol>
* <li>The results of {@link #getInverseReferences(EObject)
* getInverseReferences()} and {@link #getNonNavigableInverseReferences(EObject)
* getNonNavigableInverseReferences()} are computed by
* {@link CDOView#queryXRefs(Set, EReference...) querying} the underlying CDO
* repository instead of self-adapting the entire model in-memory.
* <li>An invalidation of the cache of derived values (see
* {@link #put(EObject, Object, Object)}, {@link #get(EObject, Object)}, etc.)
* happens for remote changes, in addition to just local changes.
* </ol>
* <p>
* <b>Implementation note:</b> This adapter is carefully implemented such that
* it is never contained in the list of {@link Notifier#eAdapters() adapters} of
* a persistent native object (case 3). In particular, an existing cache adapter
* is removed from an object that transitions from case 2 to case 3, i.e., that
* is <i>attached</i> to a CDO {@link CDOView view}.
* <p>
* *
*
* @author Eike Stepper
*/
public class CDOCacheAdapter
extends CacheAdapter {
private final IListener viewListener = new LifecycleEventAdapter() {
@Override
protected void onAboutToDeactivate(ILifecycle lifecycle) {
disconnectView((CDOView) lifecycle);
};
@Override
protected void notifyOtherEvent(IEvent event) {
if (event instanceof CDOViewInvalidationEvent) {
CDOViewInvalidationEvent e = (CDOViewInvalidationEvent) event;
Set<Resource> resources = new HashSet<Resource>();
collectResources(resources, e.getDirtyObjects());
collectResources(resources, e.getDetachedObjects());
// Newly attached objects are not known here, but can impact
// cached results. As long as no containment proxies are
// involved all should be good because the cache is invalidated
// through the dirtiness of the container.
for (Resource resource : resources) {
clear(resource);
}
}
}
private void collectResources(Set<Resource> resources,
Set<CDOObject> objects) {
for (CDOObject object : objects) {
resources.add(object.eResource());
}
}
};
private final Set<CDOView> connectedViews = new HashSet<CDOView>();
public CDOCacheAdapter() {
}
public static EcoreUtil.CrossReferencer getInverseCrossReferencer() {
try {
Field field = ECrossReferenceAdapter.class
.getDeclaredField("inverseCrossReferencer");
field.setAccessible(true);
return (EcoreUtil.CrossReferencer) field.get(getInstance());
} catch (Throwable throwable) {
// ignore
}
return null;
}
public static void register(CacheAdapter cacheAdapter) {
if (THREAD_LOCAL == null) {
try {
Field field = CacheAdapter.class.getDeclaredField("INSTANCE");
field.setAccessible(true);
field.set(null, cacheAdapter);
} catch (Throwable throwable) {
// ignore
}
} else {
THREAD_LOCAL.set(cacheAdapter);
}
}
@Override
protected void selfAdapt(Notification notification) {
CDOView view = getView(notification.getNotifier());
if (view == null) {
super.selfAdapt(notification);
}
}
@Override
public boolean adapt(Notifier notifier) {
CDOView view = getView(notifier);
if (view != null) {
connectView(view);
return false;
}
return super.adapt(notifier);
}
@Override
protected void addAdapter(Notifier notifier) {
CDOView view = getView(notifier);
if (view != null) {
connectView(view);
return;
}
super.addAdapter(notifier);
}
@Override
protected ECrossReferenceAdapter provideCrossReferenceAdapter(
EObject eObject) {
CDOView view = getView(eObject);
if (view != null) {
return this;
}
return super.provideCrossReferenceAdapter(eObject);
}
private CDOView getView(Object notifier) {
if (notifier instanceof EObject) {
EObject eObject = (EObject) notifier;
CDOObject cdoObject = CDOUtil.getCDOObject(eObject, false);
if (cdoObject != null) {
return cdoObject.cdoView();
}
}
return null;
}
private Collection<Setting> collectInverseReferences(CDOObject cdoObject,
CDOView view, boolean nonNavigable) {
List<EStructuralFeature.Setting> result = new ArrayList<EStructuralFeature.Setting>();
for (CDOObjectReference xref : view
.queryXRefs(Collections.singleton(cdoObject))) {
EReference sourceReference = xref.getSourceReference();
if (!nonNavigable || sourceReference.getEOpposite() == null) {
InternalCDOObject object = (InternalCDOObject) view
.getObject(xref.getSourceID());
result.add(object.eSetting(sourceReference));
}
}
return result;
}
@Override
public Collection<Setting> getNonNavigableInverseReferences(
EObject eObject) {
CDOObject cdoObject = CDOUtil.getCDOObject(eObject, false);
if (cdoObject != null) {
CDOView view = cdoObject.cdoView();
if (view != null) {
return collectInverseReferences(cdoObject, view, true);
}
}
return super.getNonNavigableInverseReferences(eObject);
}
@Override
public Collection<Setting> getInverseReferences(EObject eObject) {
CDOObject cdoObject = CDOUtil.getCDOObject(eObject, false);
if (cdoObject != null) {
CDOView view = cdoObject.cdoView();
if (view != null) {
return collectInverseReferences(cdoObject, view, false);
}
}
return super.getInverseReferences(eObject);
}
@Override
protected InverseCrossReferencer createInverseCrossReferencer() {
return new InverseCrossReferencer();
}
private void connectView(CDOView view) {
boolean added;
synchronized (connectedViews) {
added = connectedViews.add(view);
}
if (added) {
view.addListener(viewListener);
}
}
private void disconnectView(CDOView view) {
boolean removed;
synchronized (connectedViews) {
removed = connectedViews.remove(view);
}
if (removed) {
view.removeListener(viewListener);
clear();
}
}
/**
* @author Eike Stepper
*/
protected class InverseCrossReferencer
extends CacheAdapter.InverseCrossReferencer {
private static final long serialVersionUID = 1L;
/**
* Make the protected super class method visible.
*/
@Override
public FeatureIterator<EObject> getCrossReferences(EObject eObject) {
return super.getCrossReferences(eObject);
}
@Override
protected Collection<Setting> getCollection(Object key) {
return super.getCollection(key);
}
}
}