/** * Copyright (c) 2010, 2013 Darmstadt University of Technology. * 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: * Marcel Bruch - initial API and implementation. * Patrick Gottschaemmer, Olav Lenz - Introduced ProxySelector * Olav Lenz - externalize Strings. * Andreas Sewe - modernized use of Aether */ package org.eclipse.recommenders.models; import static java.util.concurrent.TimeUnit.HOURS; import static org.eclipse.aether.ConfigurationProperties.PERSISTED_CHECKSUMS; import static org.eclipse.aether.repository.RepositoryPolicy.CHECKSUM_POLICY_FAIL; import static org.eclipse.aether.resolution.ArtifactDescriptorPolicy.IGNORE_MISSING; import static org.eclipse.aether.resolution.ResolutionErrorPolicy.*; import java.io.File; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.maven.repository.internal.MavenRepositorySystemUtils; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; import org.eclipse.aether.impl.DefaultServiceLocator; import org.eclipse.aether.repository.Authentication; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.Proxy; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.repository.RemoteRepository.Builder; import org.eclipse.aether.repository.RepositoryPolicy; import org.eclipse.aether.resolution.ArtifactRequest; import org.eclipse.aether.resolution.ArtifactResolutionException; import org.eclipse.aether.resolution.ArtifactResult; import org.eclipse.aether.spi.connector.RepositoryConnectorFactory; import org.eclipse.aether.spi.connector.transport.TransporterFactory; import org.eclipse.aether.spi.log.NullLoggerFactory; import org.eclipse.aether.transfer.TransferCancelledException; import org.eclipse.aether.transfer.TransferEvent; import org.eclipse.aether.transfer.TransferListener; import org.eclipse.aether.transport.file.FileTransporterFactory; import org.eclipse.aether.transport.http.HttpTransporterFactory; import org.eclipse.aether.util.repository.AuthenticationBuilder; import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy; import org.eclipse.aether.util.repository.SimpleResolutionErrorPolicy; import org.eclipse.recommenders.internal.models.AetherUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; /** * This class is thread-safe. */ public class ModelRepository implements IModelRepository { private static final Logger LOG = LoggerFactory.getLogger(ModelRepository.class); private final RepositorySystem system; private final RepositorySystemSession defaultSession; private final RemoteRepository defaultRemoteRepo; private Authentication authentication; private Proxy proxy; public ModelRepository(File basedir, String remoteUrl) { this(createRepositorySystem(), basedir, remoteUrl); } /** * @param system * instance of {@link RepositorySystem}. Here, {@code Object} so that {@code RepositorySystem} does not * become part of our public API. */ @VisibleForTesting public ModelRepository(Object system, File basedir, String remoteUri) { this.system = (RepositorySystem) system; this.defaultSession = createDefaultSession(basedir); this.defaultRemoteRepo = AetherUtils.createRemoteRepository("models", remoteUri); } private static RepositorySystem createRepositorySystem() { DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator(); locator.setService(org.eclipse.aether.spi.log.LoggerFactory.class, NullLoggerFactory.class); locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class); locator.addService(TransporterFactory.class, FileTransporterFactory.class); locator.addService(TransporterFactory.class, HttpTransporterFactory.class); return locator.getService(RepositorySystem.class); } /** * Provides a default session that can be further customized. */ private RepositorySystemSession createDefaultSession(File basedir) { DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); LocalRepository localRepo = new LocalRepository(basedir); session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo)); // Do not expect POMs in a model repository. session.setArtifactDescriptorPolicy(new SimpleArtifactDescriptorPolicy(IGNORE_MISSING)); // Do expect checksums... session.setChecksumPolicy(CHECKSUM_POLICY_FAIL); // ...but do not store them. session.setConfigProperty(PERSISTED_CHECKSUMS, false); // Use timestamps in snapshot artifacts' names; do not keep (duplicate) artifacts named "SNAPSHOT". session.setConfigProperty("aether.artifactResolver.snapshotNormalization", false); // Ensure that the update policy set is honored. session.setConfigProperty("aether.versionResolver.noCache", true); session.setConfigProperty("aether.updateCheckManager.sessionState", "bypass"); return session; } /** * Maps model coordinates where resolution is currently in-progress to the file they previously resolved to, if any. * This makes it possible to immediately answer offline resolution requests while an online request is overwriting, * e.g., the <code>maven-metadata.xml</code>. */ private final Map<ModelCoordinate, Optional<File>> inProgressResolutions = new HashMap<>(); /** * {@inheritDoc} * * Note: This implementation ignores the <code>prefetch</code> parameter. */ @Override public Optional<File> getLocation(ModelCoordinate mc, boolean prefetch) { synchronized (inProgressResolutions) { if (inProgressResolutions.containsKey(mc)) { return inProgressResolutions.get(mc); } RepositorySystemSession offlineSession = newOfflineSession(); return resolveInternal(mc, offlineSession); } } @Override public Optional<File> resolve(ModelCoordinate mc, boolean force) { return resolve(mc, force, DownloadCallback.NULL); } @Override public Optional<File> resolve(ModelCoordinate mc, boolean force, final DownloadCallback callback) { synchronized (inProgressResolutions) { RepositorySystemSession offlineSession = newOfflineSession(); Optional<File> previousFile = resolveInternal(mc, offlineSession); inProgressResolutions.put(mc, previousFile); } try { // TODO Synchronization is still rather coarse-grained. We should consider SyncContext backed by // ReadWriteLock. synchronized (this) { RepositorySystemSession onlineSession = newOnlineSession(callback, force); return resolveInternal(mc, onlineSession); } } finally { synchronized (inProgressResolutions) { inProgressResolutions.remove(mc); } } } /** * <em>Not</em> thread-safe. Synchronization needs to be done by the caller. */ private Optional<File> resolveInternal(ModelCoordinate mc, RepositorySystemSession session) { try { final Artifact coord = toSnapshotArtifact(mc); Builder remoteRepoBuilder = new RemoteRepository.Builder(defaultRemoteRepo).setProxy(proxy); if (authentication != null) { remoteRepoBuilder.setAuthentication(authentication); } RemoteRepository remoteRepo = remoteRepoBuilder.build(); ArtifactRequest request = new ArtifactRequest(coord, Collections.singletonList(remoteRepo), null); ArtifactResult result = system.resolveArtifact(session, request); return Optional.of(result.getArtifact().getFile()); } catch (ArtifactResolutionException e) { if (!session.isOffline()) { LOG.warn("Failed to download {}", mc, e); } return Optional.absent(); } } private RepositorySystemSession newOfflineSession() { DefaultRepositorySystemSession offlineSession = new DefaultRepositorySystemSession(defaultSession); offlineSession.setOffline(true); return offlineSession; } private DefaultRepositorySystemSession newOnlineSession(final DownloadCallback callback, boolean forceDownloads) { final DefaultRepositorySystemSession onlineSession = new DefaultRepositorySystemSession(defaultSession); if (forceDownloads) { onlineSession.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS); onlineSession.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(CACHE_DISABLED)); } else { // Try to update any models older than 1 hour. onlineSession.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_INTERVAL + ":" + HOURS.toMinutes(1)); // Do not retry downloading missing models until the update interval has elapsed. // Do not retry downloading models after a failed download. onlineSession.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(CACHE_ALL)); } onlineSession.setTransferListener(new TransferListener() { @Override public void transferInitiated(TransferEvent e) throws TransferCancelledException { callback.downloadInitiated(e.getResource().getResourceName()); } @Override public void transferStarted(TransferEvent e) throws TransferCancelledException { callback.downloadStarted(e.getResource().getResourceName()); } @Override public void transferProgressed(TransferEvent e) throws TransferCancelledException { callback.downloadProgressed(e.getResource().getResourceName(), e.getTransferredBytes(), e.getResource().getContentLength()); } @Override public void transferSucceeded(TransferEvent e) { callback.downloadSucceeded(e.getResource().getResourceName()); } @Override public void transferFailed(TransferEvent e) { callback.downloadFailed(e.getResource().getResourceName()); } @Override public void transferCorrupted(TransferEvent e) throws TransferCancelledException { callback.downloadCorrupted(e.getResource().getResourceName()); } }); return onlineSession; } @Beta public void setProxy(String type, String host, int port, String user, String pass) { Authentication proxyAuthentication = new AuthenticationBuilder().addUsername(user).addPassword(pass).build(); proxy = type == null ? null : new Proxy(type, host, port, proxyAuthentication); } @Beta public void unsetProxy() { proxy = null; } @Beta public void setAuthentication(String username, String password) { authentication = new AuthenticationBuilder().addUsername(username).addPassword(password).build(); } @Override public String toString() { return defaultSession.getLocalRepository().getBasedir().toString(); } private Artifact toSnapshotArtifact(ModelCoordinate mc) { return new DefaultArtifact(mc.getGroupId(), mc.getArtifactId(), mc.getClassifier(), mc.getExtension(), mc.getVersion() + "-SNAPSHOT"); } }