| /* |
| * 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); |
| } |
| } |
| } |