blob: 75327520eb880b2fbf853d1c6681d9a2235b9985 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2008-2019 See4sys, BMW Car IT, itemis and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
*
* Contributors:
* See4sys - Initial API and implementation
* BMW Car IT - [374883] Improve handling of out-of-sync workspace files during descriptor initialization
* BMW Car IT - Avoid usage of Object.finalize
* itemis - [409014] Listener URIChangeDetector registered for all transactional editing domains
* Elektrobit - [570596] - Eliminate risk of deadlocks related to ResourceProblemHandler
*
* </copyright>
*/
package org.eclipse.sphinx.emf.internal.resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.transaction.NotificationFilter;
import org.eclipse.emf.transaction.ResourceSetChangeEvent;
import org.eclipse.emf.transaction.ResourceSetListenerImpl;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.sphinx.emf.Activator;
import org.eclipse.sphinx.emf.domain.factory.AbstractResourceSetListenerInstaller;
import org.eclipse.sphinx.emf.internal.messages.Messages;
import org.eclipse.sphinx.emf.saving.SaveIndicatorUtil;
import org.eclipse.sphinx.emf.util.EcorePlatformUtil;
import org.eclipse.sphinx.emf.util.WorkspaceEditingDomainUtil;
import org.eclipse.sphinx.platform.IExtendedPlatformConstants;
import org.eclipse.sphinx.platform.resources.DefaultResourceChangeHandler;
import org.eclipse.sphinx.platform.resources.MarkerJob;
import org.eclipse.sphinx.platform.resources.ResourceDeltaVisitor;
import org.eclipse.sphinx.platform.util.ExtendedPlatform;
import org.eclipse.sphinx.platform.util.PlatformLogUtil;
import org.eclipse.sphinx.platform.util.StatusUtil;
/**
* Listens for {@link Resource resource}s that have been loaded or saved and requests the problem markers of underlying
* {@link IFile file}s to be updated according to the {@link Resource#getErrors() errors} and
* {@link Resource#getWarnings() warnings} of each loaded or saved {@link Resource} resource.
*
* @see ResourceProblemMarkerService#updateProblemMarkers(Collection, boolean,
* org.eclipse.core.runtime.IProgressMonitor)
*/
public class ResourceProblemHandler extends ResourceSetListenerImpl implements IResourceChangeListener {
public static class ResourceProblemHandlerInstaller extends AbstractResourceSetListenerInstaller<ResourceProblemHandler> {
public ResourceProblemHandlerInstaller() {
super(ResourceProblemHandler.class);
}
}
/**
* Default constructor.
*/
public ResourceProblemHandler() {
super(NotificationFilter.createFeatureFilter(EcorePackage.eINSTANCE.getEResource(), Resource.RESOURCE__IS_LOADED)
.or(NotificationFilter.createFeatureFilter(EcorePackage.eINSTANCE.getEResourceSet(), ResourceSet.RESOURCE_SET__RESOURCES)));
}
@Override
public void setTarget(TransactionalEditingDomain domain) {
super.setTarget(domain);
ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
}
@Override
public void unsetTarget(TransactionalEditingDomain domain) {
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
super.unsetTarget(domain);
}
/*
* @see org.eclipse.emf.transaction.ResourceSetListenerImpl#resourceSetChanged(org.eclipse.emf.transaction.
* ResourceSetChangeEvent)
*/
@Override
public void resourceSetChanged(ResourceSetChangeEvent event) {
Set<Resource> loadedResources = new HashSet<Resource>();
// Analyze notifications for loaded resources; record loaded resources regardless of whether or not they have
// got unloaded subsequently or not
for (Notification notification : event.getNotifications()) {
Object notifier = notification.getNotifier();
if (notifier instanceof Resource) {
Resource resource = (Resource) notifier;
Boolean newValue = (Boolean) notification.getNewValue();
if (newValue) {
loadedResources.add(resource);
}
} else if (notifier instanceof ResourceSet) {
Object newValue = notification.getNewValue();
if (notification.getEventType() == Notification.ADD || notification.getEventType() == Notification.ADD_MANY) {
List<Resource> newResources = new ArrayList<Resource>();
if (newValue instanceof List<?>) {
@SuppressWarnings("unchecked")
List<Resource> newResourcesValue = (List<Resource>) newValue;
newResources.addAll(newResourcesValue);
} else if (newValue instanceof Resource) {
newResources.add((Resource) newValue);
}
loadedResources.addAll(newResources);
}
}
}
// Handle loaded resources
handleLoadedResources(loadedResources);
}
/*
* @see org.eclipse.emf.transaction.ResourceSetListenerImpl#isPostcommitOnly()
*/
@Override
public boolean isPostcommitOnly() {
return true;
}
protected void handleLoadedResources(Collection<Resource> resources) {
if (!resources.isEmpty()) {
Job job = new Job(Messages.job_addingProblemMarkers) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
ResourceProblemMarkerService.INSTANCE.addProblemMarkers(resources, monitor);
return Status.OK_STATUS;
} catch (OperationCanceledException ex) {
return Status.CANCEL_STATUS;
} catch (Exception ex) {
return StatusUtil.createErrorStatus(Activator.getPlugin(), ex);
}
}
@Override
public boolean belongsTo(Object family) {
return IExtendedPlatformConstants.FAMILY_LONG_RUNNING.equals(family);
}
};
job.setPriority(Job.SHORT);
job.setRule(ExtendedPlatform.createModifySchedulingRule(getFiles(resources)));
job.setSystem(true);
job.schedule();
}
}
private Collection<IFile> getFiles(Collection<Resource> resources) {
Collection<IFile> files = new ArrayList<>();
for (Resource resource : resources) {
IFile file = EcorePlatformUtil.getFile(resource);
files.add(file);
}
return files;
}
/*
* @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(IResourceChangeEvent)
*/
@Override
public void resourceChanged(IResourceChangeEvent event) {
try {
IResourceDelta delta = event.getDelta();
if (delta != null) {
final Set<IFile> changedFiles = new HashSet<IFile>();
final Set<IFile> savedFiles = new HashSet<IFile>();
// Investigate resource delta on saved files
IResourceDeltaVisitor visitor = new ResourceDeltaVisitor(event.getType(), new DefaultResourceChangeHandler() {
@Override
public void handleFileChanged(int eventType, IFile file) {
changedFiles.add(file);
/*
* !! Important Note !! We must not try to obtain the model resource behind the changed file in
* the present execution context. This would require requesting exclusive access to underlying
* editing domain by creating a read transaction. However, the workspace is locked during
* resource change event processing. Any attempt of obtaining exclusive editing domain access
* while this is the case would therefore introduce a major risk of deadlocks. Some other thread
* might be waiting for exclusive workspace access but already have exclusive editing domain
* access.
*/
TransactionalEditingDomain editingDomain = WorkspaceEditingDomainUtil.getMappedEditingDomain(file);
URI uri = EcorePlatformUtil.createURI(file.getFullPath());
if (SaveIndicatorUtil.isSaved(editingDomain, uri)) {
savedFiles.add(file);
}
}
});
delta.accept(visitor);
// Handle changed files
handleChangedFiles(changedFiles);
// Handle saved files
handleSavedFiles(savedFiles);
}
} catch (Exception ex) {
PlatformLogUtil.logAsError(Activator.getDefault(), ex);
}
}
protected void handleChangedFiles(Collection<IFile> files) {
Assert.isNotNull(files);
if (files.size() > 0) {
/*
* !! Important Note !! Perform as asynchronous operation with exclusive access to the affected files in
* order to avoid deadlocks. The workspace is locked while IResourceChangeListeners are processed (exclusive
* workspace access) and updating a resource's problem markers may involve creating transactions (exclusive
* model access). In cases where another thread is around while we are called here which already has
* exclusive model access but waits for exclusive workspace access we would end up in a deadlock otherwise.
*/
Job job = new Job(Messages.job_removingProblemMarkers) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
ResourceProblemMarkerService.INSTANCE.removeProblemMarkers(files, monitor);
MarkerJob.INSTANCE.schedule();
return Status.OK_STATUS;
} catch (OperationCanceledException ex) {
return Status.CANCEL_STATUS;
} catch (Exception ex) {
return StatusUtil.createErrorStatus(Activator.getPlugin(), ex);
}
}
@Override
public boolean belongsTo(Object family) {
return IExtendedPlatformConstants.FAMILY_LONG_RUNNING.equals(family);
}
};
job.setPriority(Job.SHORT);
job.setRule(ExtendedPlatform.createModifySchedulingRule(files));
job.setSystem(true);
job.schedule();
}
}
protected void handleSavedFiles(Collection<IFile> files) {
Assert.isNotNull(files);
if (files.size() > 0) {
/*
* !! Important Note !! Perform as asynchronous operation with exclusive access to the affected files in
* order to avoid deadlocks. The workspace is locked while IResourceChangeListeners are processed (exclusive
* workspace access) and updating a resource's problem markers may involve creating transactions (exclusive
* model access). In cases where another thread is around while we are called here which already has
* exclusive model access but waits for exclusive workspace access we would end up in a deadlock otherwise.
*/
Job job = new Job(Messages.job_addingProblemMarkers) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
Set<Resource> resources = new HashSet<Resource>();
for (IFile file : files) {
Resource resource = EcorePlatformUtil.getResource(file);
if (resource != null) {
resources.add(resource);
}
}
ResourceProblemMarkerService.INSTANCE.addProblemMarkers(resources, monitor);
return Status.OK_STATUS;
} catch (OperationCanceledException ex) {
return Status.CANCEL_STATUS;
} catch (Exception ex) {
return StatusUtil.createErrorStatus(Activator.getPlugin(), ex);
}
}
@Override
public boolean belongsTo(Object family) {
return IExtendedPlatformConstants.FAMILY_LONG_RUNNING.equals(family);
}
};
job.setPriority(Job.SHORT);
job.setRule(ExtendedPlatform.createModifySchedulingRule(files));
job.setSystem(true);
job.schedule();
}
}
}