blob: d83bb8b9b03359aedf708d2cdfb386a5dc99a332 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2013, 2017 CEA LIST 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:
* CEA LIST - Initial API and implementation
* Christian W. Damus (CEA) - bug 429242
*
*****************************************************************************/
package org.eclipse.papyrus.cdo.internal.ui.views;
import static org.eclipse.papyrus.infra.core.resource.sasheditor.DiModel.DI_FILE_EXTENSION;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.cdo.eresource.CDOResource;
import org.eclipse.emf.cdo.eresource.CDOResourceFolder;
import org.eclipse.emf.cdo.eresource.EresourcePackage;
import org.eclipse.emf.cdo.view.CDOQuery;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.emf.cdo.view.CDOViewInvalidationEvent;
import org.eclipse.emf.common.util.URI;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.net4j.util.event.IEvent;
import org.eclipse.net4j.util.event.IListener;
import org.eclipse.net4j.util.lifecycle.ILifecycleEvent;
import org.eclipse.net4j.util.lifecycle.ILifecycleEvent.Kind;
import org.eclipse.papyrus.cdo.core.util.JobWaiter;
import org.eclipse.papyrus.cdo.internal.ui.Activator;
import org.eclipse.papyrus.cdo.internal.ui.l10n.Messages;
import org.eclipse.papyrus.infra.core.sashwindows.di.DiPackage;
import org.eclipse.papyrus.infra.core.sashwindows.di.SashModel;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.widgets.Display;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
/**
* A persistent query for DI resources in some CDO view. Server-side queries are
* run in the background to efficiently find all {@link CDOResource}s that
* contain DI {@link SashModel}s. The results of the query are asynchronously
* published to the UI to refresh the tree viewer.
*/
public class DIResourceQuery {
private static final Map<CDOView, DIResourceQuery> instances = Maps.newHashMap();
private final StructuredViewer viewer;
private final CDOQuery legacyQuery;
private final CDOQuery query;
private final Job queryJob = new QueryJob();
private final IListener cdoViewListener = createCDOViewListener();
private AtomicReference<Set<CDOResource>> diResources = new AtomicReference<Set<CDOResource>>(Collections.<CDOResource> emptySet());
private Boolean hasLegacyModels;
private DIResourceQuery(StructuredViewer viewer, CDOView view) {
super();
this.viewer = viewer;
// Query for all SashWindowsMngr instances (legacy DI models, definitely accurate) and
// all resources named *.di that are empty (new-style DI models)
this.legacyQuery = view.createQuery("ocl", //$NON-NLS-1$
"SashWindowsMngr.allInstances()->collect(oclAsType(ecore::EObject).eResource()).oclAsType(eresource::CDOResource)->union(" + //$NON-NLS-1$
"eresource::CDOResource.allInstances()->select(uRI.toString().endsWith('.di') and contents->isEmpty()))", //$NON-NLS-1$
DiPackage.Literals.SASH_MODEL);
// Query for new-style models only, used when the repository does not know the DiPackage
// (because it contains no legacy models)
this.query = view.createQuery("ocl", //$NON-NLS-1$
"eresource::CDOResource.allInstances()->select(uRI.toString().endsWith('.di') and contents->isEmpty())", //$NON-NLS-1$
EresourcePackage.Literals.CDO_RESOURCE);
view.addListener(cdoViewListener);
if (viewer != null) {
viewer.getControl().addDisposeListener(createViewerDisposeListener());
}
runQuery();
}
public static DIResourceQuery initialize(StructuredViewer viewer, CDOView view) {
DIResourceQuery result;
synchronized (instances) {
result = instances.get(view);
if (result == null) {
result = new DIResourceQuery(viewer, view);
instances.put(view, result);
}
}
return result;
}
/**
* Wait for the current in-progress query on the specified {@code view} to finish, if any.
*
* @param view
* a view which we are or may be querying for DI resources
* @param timeout
* a positive timeout
* @param unit
* the time unit for the {@code timeout}
*
* @return {@code true} on successful wait (if required); {@code false} on time-out
*
* @throws InterruptedException
* if the wait is interrupted
*/
public static boolean waitFor(CDOView view, long timeout, TimeUnit unit) throws InterruptedException {
if (timeout <= 0) {
throw new IllegalArgumentException("Non-positive timeout"); //$NON-NLS-1$
}
boolean result;
DIResourceQuery query;
synchronized (instances) {
query = instances.get(view);
}
if (query == null) {
// have nothing to wait for
result = true;
} else {
result = JobWaiter.waitFor(query, timeout, unit);
}
return result;
}
public static Set<CDOResource> getDIResources(CDOView view) {
DIResourceQuery query;
synchronized (instances) {
query = instances.get(view);
}
Set<CDOResource> result;
if (query == null) {
result = Collections.emptySet();
} else {
result = query.getDIResources();
}
return result;
}
public static boolean isUnaffiliatedResource(CDOResource resource) {
return getAffiliateResource(resource) == null;
}
public static CDOResource getAffiliateResource(CDOResource resource) {
CDOResource result = null;
URI uri = resource.getURI();
if (DI_FILE_EXTENSION.equals(uri.fileExtension())) {
// it *is* a DI resource
result = resource;
} else {
uri = uri.trimFileExtension().appendFileExtension(DI_FILE_EXTENSION);
for (CDOResource next : getDIResources(resource.cdoView())) {
if (uri.equals(next.getURI())) {
result = next;
break;
}
}
}
return result;
}
public Set<CDOResource> getDIResources() {
return diResources.get();
}
boolean hasLegacyModels() {
if (hasLegacyModels == null) {
hasLegacyModels = query.getView().getSession().getPackageRegistry().getPackageInfo(DiPackage.eINSTANCE) != null;
}
return hasLegacyModels;
}
private void runQuery() {
// we cannot query for EClasses that the server doesn't know about. And,
// if it doesn't know about an EClass, then a priori, none of its
// instances exist, so we don't need to run the query
queryJob.schedule();
}
void refresh() {
viewer.refresh();
}
private void dispose() {
synchronized (instances) {
CDOView view = query.getView();
view.removeListener(cdoViewListener);
instances.remove(view);
}
}
private IListener createCDOViewListener() {
return new IListener() {
@Override
public void notifyEvent(IEvent event) {
if (event instanceof ILifecycleEvent) {
ILifecycleEvent lifecycleEvent = (ILifecycleEvent) event;
if (lifecycleEvent.getKind() == Kind.DEACTIVATED) {
dispose();
}
} else if (event instanceof CDOViewInvalidationEvent) {
// if my view is invalidated, then some folder or resource
// that I am showing has changed. Run the query again and
// update asynchronously
runQuery();
}
}
};
}
private DisposeListener createViewerDisposeListener() {
return new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
dispose();
}
};
}
//
// Nested types
//
private class QueryJob extends Job {
QueryJob() {
super(Messages.DIResourceQuery_2);
setSystem(true);
}
@Override
public boolean belongsTo(Object family) {
return family == DIResourceQuery.this;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
ImmutableSet.Builder<CDOResource> resultBuilder = ImmutableSet.builder();
List<CDOResource> rawResult = hasLegacyModels() ? legacyQuery.getResult(CDOResource.class) : query.getResult(CDOResource.class);
// don't use an iterator because it won't be able to advance
// past a resource proxy that cannot be resolved
for (int i = 0; i < rawResult.size(); i++) {
try {
CDOResource next = rawResult.get(i);
if (isContained(next)) {
resultBuilder.add(next);
}
} catch (Exception e) {
// can get "node not found" exceptions on incompletely
// deleted resources
Activator.log.error("Error retrieving resource result from CDO query.", e); //$NON-NLS-1$
}
}
Set<CDOResource> result = resultBuilder.build();
diResources.set(ImmutableSet.copyOf(result));
if ((viewer != null) && (viewer.getControl() != null)) {
Display display = viewer.getControl().getDisplay();
if (display != null) {
display.asyncExec(new Runnable() {
@Override
public void run() {
if ((viewer != null) && (viewer.getControl() != null) && !viewer.getControl().isDisposed()) {
refresh();
}
}
});
}
}
return Status.OK_STATUS;
}
private boolean isContained(CDOResource resource) {
// determines whether a resource is properly contained in the view's
// node hierarchy
boolean result = false;
CDOResourceFolder folder = resource.getFolder();
if (folder != null) {
// if we don't have read permission on the folder, then we shouldn't attempt to show any contents
if (folder.cdoPermission().isReadable()) {
result = folder.getNodes().contains(resource);
}
} else {
CDOResource root = resource.cdoResource();
if ((root != null) && root.isRoot()) {
result = root.getContents().contains(resource);
}
}
return result;
}
}
}