| /******************************************************************************* |
| * Copyright (c) 2011, 2017 Obeo 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: |
| * Obeo - initial API and implementation |
| * Martin Fleck - bug 512677 |
| *******************************************************************************/ |
| package org.eclipse.emf.compare.ide.ui.internal.logical.resolver; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IStorage; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin; |
| import org.eclipse.emf.compare.ide.ui.logical.IStorageProvider; |
| import org.eclipse.emf.compare.ide.ui.logical.IStorageProviderAccessor; |
| import org.eclipse.emf.compare.ide.ui.logical.IStorageProviderAccessor.DiffSide; |
| import org.eclipse.emf.compare.ide.utils.ResourceUtil; |
| import org.eclipse.emf.compare.ide.utils.StorageURIConverter; |
| import org.eclipse.emf.ecore.resource.URIConverter; |
| |
| /** |
| * This {@link URIConverter} will be used in order to fetch remote content instead of local content when |
| * loading resources. |
| * |
| * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> |
| */ |
| public final class RevisionedURIConverter extends StorageURIConverter { |
| /** The accessor that will provide us with resource content. */ |
| private final IStorageProviderAccessor storageAccessor; |
| |
| /** The side we are currently resolving. */ |
| private final DiffSide side; |
| |
| /** |
| * We can have <code>null</code> input streams from here (if the resource does not exist on the remote |
| * sides). However, this is not supported by the resource implementations (Resource#load(...)), which |
| * would fail in NPEs. Since we can only detect that late (when actually trying for the input stream), we |
| * have to "prefetch" this input stream from the resource set and check, from there, before creating the |
| * resource, that we will actually be able to load it. This field will be used to keep track of the |
| * prefetched stream so that we can avoid loading it twice. |
| */ |
| private final ConcurrentMap<URI, InputStream> prefetchedStreams = new ConcurrentHashMap<URI, InputStream>(); |
| |
| /** |
| * Cache for the existence of remote resources to reduce the number of expensive existence checks. As key, |
| * we use the {@link #normalize(URI) normalized} form of the URI. |
| */ |
| private final ConcurrentMap<URI, Boolean> existsCache = new ConcurrentHashMap<>(); |
| |
| /** |
| * Instantiates our URI converter given its delegate. |
| * |
| * @param delegate |
| * Our delegate URI converter. |
| * @param storageAccessor |
| * The accessor that will provide synchronization information for the loaded files. |
| * @param side |
| * The side we are currently resolving. |
| */ |
| public RevisionedURIConverter(URIConverter delegate, IStorageProviderAccessor storageAccessor, |
| DiffSide side) { |
| super(delegate); |
| this.storageAccessor = storageAccessor; |
| this.side = side; |
| } |
| |
| /** |
| * Prefetches the input stream for the given URI if any. Note that the stream will be left opened and |
| * cached by this URIConverter, only to be closed when the associated "load resource" is called. |
| * <p> |
| * See comments on {@link #prefetchedStreams}. This is used to avoid loading a single URI more than once. |
| * </p> |
| * |
| * @param uri |
| * see {@link #createInputStream(URI, Map)} |
| * @param options |
| * see {@link #createInputStream(URI, Map)} |
| * @return <code>true</code> if there is an input stream accessible for the given uri, <code>false</code> |
| * otherwise. |
| * @throws IOException |
| * if an IO problem occurs. |
| * @see #createInputStream(URI, Map) |
| * @see #prefetchedStreams |
| */ |
| // Suppressing the warning. It is the responsibility of the caller to then use #createInputStream(...) |
| // somehow and close the stream then. |
| @SuppressWarnings("resource") |
| public boolean prefetchStream(URI uri, Map<?, ?> options) throws IOException { |
| InputStream stream = createInputStream(uri, options); |
| if (stream != null) { |
| prefetchedStreams.put(uri, stream); |
| } |
| return stream != null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.compare.ide.utils.StorageURIConverter#createInputStream(org.eclipse.emf.common.util.URI, |
| * java.util.Map) |
| */ |
| @Override |
| public InputStream createInputStream(URI uri, Map<?, ?> options) throws IOException { |
| InputStream stream = prefetchedStreams.remove(uri); |
| if (stream != null) { |
| return stream; |
| } |
| |
| final URI normalizedUri = normalize(uri); |
| // If this uri points to the plugins directory, load it directly |
| if (normalizedUri.isPlatformPlugin() || normalizedUri.toString().matches("(\\.\\./)+?plugins/.*")) { //$NON-NLS-1$ |
| // This uri will be used later on to determine whether the storage has already been loaded or not. |
| // Use the unmodified URI instead of the normalized one here. |
| stream = super.createInputStream(uri, options); |
| } else { |
| // Otherwise, load it from the repository (resource might not yet (or no longer) exist locally) |
| final IResource targetFile = ResourceUtil.getResourceFromURI(normalizedUri); |
| if (targetFile != null) { |
| stream = openRevisionStream(targetFile); |
| } else { |
| stream = super.createInputStream(uri, options); |
| } |
| } |
| |
| return stream; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.compare.ide.utils.StorageURIConverter#exists(org.eclipse.emf.common.util.URI, |
| * java.util.Map) |
| */ |
| @Override |
| public boolean exists(URI uri, Map<?, ?> options) { |
| boolean exists = false; |
| try { |
| final URI normalizedUri = normalize(uri); |
| |
| // check cache |
| Boolean cachedExists = existsCache.get(normalizedUri); |
| if (cachedExists != null) { |
| return cachedExists.booleanValue(); |
| } |
| |
| // query storage provider |
| IStorageProvider storageProvider = storageAccessor |
| .getStorageProvider(ResourceUtil.getResourceFromURI(normalizedUri), side); |
| if (storageProvider != null) { |
| exists = storageProvider.getStorage(new NullProgressMonitor()) != null; |
| } else { |
| exists = super.exists(normalizedUri, options); |
| } |
| |
| // store in cache only when no exception is thrown |
| existsCache.put(normalizedUri, Boolean.valueOf(exists)); |
| } catch (CoreException e) { |
| EMFCompareIDEUIPlugin.getDefault().log(IStatus.ERROR, e.getMessage()); |
| } |
| return exists; |
| } |
| |
| /** |
| * Opens an input stream on the contents of the given file as provided by the registered |
| * {@link #subscriber}. |
| * <p> |
| * Take good note that the <em>targetFile</em> may not exist locally. |
| * </p> |
| * |
| * @param targetFile |
| * The resource we seek a revision of. |
| * @return The opened input stream. May be <code>null</code> if we failed to open it. |
| */ |
| private InputStream openRevisionStream(IResource targetFile) { |
| InputStream stream = null; |
| if (storageAccessor == null) { |
| // FIXME can this happen? does it matter? Fall back to local content for now. |
| } else { |
| try { |
| final IStorageProvider provider = storageAccessor.getStorageProvider(targetFile, side); |
| IStorage storage = null; |
| |
| if (provider != null) { |
| storage = provider.getStorage(new NullProgressMonitor()); |
| // If we can't get a storage from this provider, but the target is "in-sync", |
| // then we can use the local data as fall back. see bug #532069. |
| if (storage == null && targetFile instanceof IFile |
| && storageAccessor.isInSync(targetFile)) { |
| storage = (IFile)targetFile; |
| } |
| if (storage != null |
| && (!(storage instanceof IResource) || ((IResource)storage).exists())) { |
| getLoadedRevisions().add(storage); |
| stream = storage.getContents(); |
| } |
| } else if (targetFile instanceof IFile && targetFile.isAccessible() |
| && storageAccessor.isInSync(targetFile)) { |
| // If we couldn't get a provider, but the target is "in-sync", |
| // then we can use the local data as fall back. see bug #532069. |
| getLoadedRevisions().add((IFile)targetFile); |
| stream = ((IFile)targetFile).getContents(); |
| } |
| } catch (CoreException e) { |
| logError(e); |
| } |
| } |
| |
| return stream; |
| } |
| |
| /** |
| * Logs the given exception as an error. |
| * |
| * @param e |
| * The exception we need to log. |
| */ |
| private static void logError(Exception e) { |
| final IStatus status = new Status(IStatus.ERROR, EMFCompareIDEUIPlugin.PLUGIN_ID, e.getMessage(), e); |
| EMFCompareIDEUIPlugin.getDefault().getLog().log(status); |
| } |
| } |