/* * This file is part of the Illarion project. * * Copyright © 2015 - Illarion e.V. * * Illarion is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Illarion is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ package illarion.download.maven; import com.google.common.util.concurrent.ThreadFactoryBuilder; import illarion.common.config.Config; import illarion.common.util.AppIdent; import illarion.common.util.DirectoryManager; import illarion.common.util.DirectoryManager.Directory; import illarion.common.util.ProgressMonitor; import illarion.download.maven.MavenDownloaderCallback.State; import org.apache.maven.model.building.DefaultModelBuilderFactory; import org.apache.maven.model.building.ModelBuilder; import org.apache.maven.repository.internal.MavenRepositorySystemUtils; import org.eclipse.aether.ConfigurationProperties; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.collection.CollectRequest; import org.eclipse.aether.collection.CollectResult; import org.eclipse.aether.collection.DependencyCollectionException; import org.eclipse.aether.collection.DependencySelector; import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.DependencyFilter; import org.eclipse.aether.graph.DependencyVisitor; import org.eclipse.aether.impl.DefaultServiceLocator; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.LocalRepositoryManager; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.repository.RemoteRepository.Builder; import org.eclipse.aether.repository.RepositoryPolicy; import org.eclipse.aether.resolution.ArtifactResult; import org.eclipse.aether.resolution.VersionRangeRequest; import org.eclipse.aether.resolution.VersionRangeResolutionException; import org.eclipse.aether.resolution.VersionRangeResult; import org.eclipse.aether.spi.connector.RepositoryConnectorFactory; import org.eclipse.aether.spi.connector.transport.TransporterFactory; import org.eclipse.aether.spi.locator.ServiceLocator; import org.eclipse.aether.transport.http.HttpTransporterFactory; import org.eclipse.aether.util.filter.DependencyFilterUtils; import org.eclipse.aether.util.graph.selector.AndDependencySelector; import org.eclipse.aether.util.graph.selector.OptionalDependencySelector; import org.eclipse.aether.util.graph.selector.ScopeDependencySelector; import org.eclipse.aether.util.graph.visitor.FilteringDependencyVisitor; import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.nio.file.Path; import java.util.*; import java.util.concurrent.*; import static illarion.download.maven.MavenDownloaderCallback.State.ResolvingArtifacts; import static illarion.download.maven.MavenDownloaderCallback.State.ResolvingDependencies; import static org.eclipse.aether.repository.RepositoryPolicy.*; import static org.eclipse.aether.util.artifact.JavaScopes.*; /** * @author Martin Karing <nitram@illarion.org> */ public class MavenDownloader { @Nonnull private static final AppIdent APPLICATION = new AppIdent("Illarion Launcher"); @Nonnull private static final Logger log = LoggerFactory.getLogger(MavenDownloader.class); /** * The list of repositories that are used. */ @Nonnull private final List<RemoteRepository> repositories; @Nullable private RemoteRepository illarionRepository; /** * The repository system that is used by this downloader. This stores the repositories that are queried for the * artifacts and the location of the local repository. */ @Nonnull private final RepositorySystem system; /** * The maven session that stores the current temporary states of the maven resolver. */ @Nonnull private final DefaultRepositorySystemSession session; /** * In case this is set {@code true} the downloader will accept snapshot versions of the root artifacts. */ private final boolean snapshot; /** * This value is set in case the application is running in offline mode. */ private final boolean offline; /** * The configuration provider. */ @Nonnull private final Config config; @Nonnull private final MavenRepositoryListener repositoryListener; /** * Create a new instance of the downloader along with the information if its supposed to download snapshot * versions of the main application. * * @param snapshot {@code true} in case the downloader is supposed to use snapshot versions of the main application * @param attempts the indicator how many times downloading was already tried and failed. * @param cfg the configuration provider */ public MavenDownloader(boolean snapshot, int attempts, @Nonnull Config cfg) { log.trace("Creating Maven Downloader. Attempt number: {}", attempts); this.snapshot = snapshot; config = cfg; boolean offlineFlag = false; int requestTimeOut = 60000; // 1 minute try { log.trace("Starting connection test."); //noinspection ResultOfMethodCallIgnored InetAddress.getByName("illarion.org"); } catch (IOException e) { log.warn("No internet connection. Activating offline mode."); offlineFlag = true; } offline = offlineFlag; log.debug("Setting offline flag: {}", offlineFlag); ServiceLocator serviceLocator = setupServiceLocator(); RepositorySystem system = serviceLocator.getService(RepositorySystem.class); if (system == null) { throw new IllegalStateException("Failed to init repository system."); } this.system = system; session = MavenRepositorySystemUtils.newSession(); repositoryListener = new MavenRepositoryListener(); session.setTransferListener(new MavenTransferListener()); session.setRepositoryListener(repositoryListener); session.setConfigProperty(ConfigurationProperties.USER_AGENT, APPLICATION.getApplicationIdentifier()); session.setConfigProperty(ConfigurationProperties.REQUEST_TIMEOUT, requestTimeOut); session.setUpdatePolicy(UPDATE_POLICY_ALWAYS); session.setChecksumPolicy(CHECKSUM_POLICY_FAIL); log.info("Used request timeout: {}ms", requestTimeOut); repositories = new ArrayList<>(); setupRepositories(); } /** * Download the artifacts. This will check if the files are present and download all missing files. * * @param groupId the group id of the artifact to download * @param artifactId the artifact id of the artifact to download * @param callback the callback implementation to report to * @return the files that have to be in the classpath to run the application */ @Nullable public Collection<File> downloadArtifact( @Nonnull String groupId, @Nonnull String artifactId, @Nonnull MavenDownloaderCallback callback) throws DependencyCollectionException, InterruptedException, ExecutionException { Artifact artifact = new DefaultArtifact(groupId, artifactId, "jar", "[1,]"); try { callback.reportNewState(State.SearchingNewVersion, null, offline, null); VersionRangeRequest request = new VersionRangeRequest(); request.setArtifact(artifact); request.setRepositories(Collections.singletonList(illarionRepository)); request.setRequestContext(RUNTIME); VersionRangeResult result = system.resolveVersionRange(session, request); NavigableSet<String> versions = new TreeSet<>(new MavenVersionComparator()); result.getVersions().stream().filter(version -> snapshot || !version.toString().contains("SNAPSHOT")).forEach(version -> { log.info("Found {}:{}:jar:{}", groupId, artifactId, version); versions.add(version.toString()); }); if (!versions.isEmpty()) { artifact = new DefaultArtifact(groupId, artifactId, "jar", versions.pollLast()); } } catch (VersionRangeResolutionException ignored) { } callback.reportNewState(ResolvingDependencies, null, offline, null); repositoryListener.setCallback(callback); repositoryListener.setOffline(offline); Dependency dependency = new Dependency(artifact, RUNTIME, false); List<String> usedScopes = Arrays.asList(COMPILE, RUNTIME, SYSTEM); DependencySelector selector = new AndDependencySelector(new OptionalDependencySelector(), new ScopeDependencySelector(usedScopes, null)); session.setDependencySelector( selector.deriveChildSelector(new DefaultDependencyCollectionContext(session, dependency))); DependencyFilter filter = DependencyFilterUtils.classpathFilter(RUNTIME, COMPILE); try { CollectRequest collectRequest = new CollectRequest(); collectRequest.setRoot(dependency); collectRequest.setRepositories(repositories); CollectResult collectResult = system.collectDependencies(session, collectRequest); ProgressMonitor progressMonitor = new ProgressMonitor(); ArtifactRequestTracer tracer = new DefaultArtifactRequestTracer(offline, callback, progressMonitor); ArtifactRequestBuilder builder = new ArtifactRequestBuilder(system, session, tracer); DependencyVisitor visitor = new FilteringDependencyVisitor(builder, filter); visitor = new TreeDependencyVisitor(visitor); collectResult.getRoot().accept(visitor); List<FutureArtifactRequest> requests = builder.getRequests(); for (@Nonnull FutureArtifactRequest request : requests) { progressMonitor.addChild(request.getProgressMonitor()); } callback.reportNewState(ResolvingArtifacts, progressMonitor, offline, null); ExecutorService executorService = Executors.newFixedThreadPool(4, new ThreadFactoryBuilder() .setDaemon(false) .setNameFormat("Download Thread-%d") .build() ); List<Future<ArtifactResult>> results = executorService.invokeAll(requests); executorService.shutdown(); while (!executorService.awaitTermination(1, TimeUnit.MINUTES)) { log.info("Downloading is not done yet."); } Collection<File> result = new ArrayList<>(); for (@Nonnull Future<ArtifactResult> artifactResult : results) { result.add(artifactResult.get().getArtifact().getFile()); } if (result.isEmpty()) { callback.resolvingDone(Collections.<File>emptyList()); return null; } callback.resolvingDone(result); return result; } catch (@Nonnull DependencyCollectionException | InterruptedException | ExecutionException e) { callback.resolvingFailed(e); throw e; } } private void setupRepositories() { repositories.add(setupRepository("central", "http://repo1.maven.org/maven2/", setupRepository("ibiblio.org", "http://mirrors.ibiblio.org/maven2/"), setupRepository("antelink", ".com/content/repositories/central/"), setupRepository("exist", "http://repo.exist.com/maven2/"), setupRepository("ibiblio.net", "http://www.ibiblio.net/pub/packages/maven2/"), setupRepository("central-uk", "http://uk.maven.org/maven2/"))); illarionRepository = setupRepository("illarion", "http://illarion.org/media/java/maven", true, snapshot); repositories.add(illarionRepository); repositories.add(setupRepository("oss-sonatype", "http://oss.sonatype.org/content/repositories/releases/")); session.setOffline(offline); Path localDir = DirectoryManager.getInstance().getDirectory(Directory.Data); LocalRepository localRepo = new LocalRepository(localDir.toFile()); LocalRepositoryManager manager = system.newLocalRepositoryManager(session, localRepo); session.setLocalRepositoryManager(manager); } @Nonnull private RemoteRepository setupRepository(@Nonnull String id, @Nonnull String url, @Nonnull RemoteRepository... mirrors) { return setupRepository(id, url, false, false, mirrors); } @Nonnull private RemoteRepository setupRepository(@Nonnull String id, @Nonnull String url, boolean alwaysCheck, boolean enableSnapshots, @Nonnull RemoteRepository... mirrors) { Builder repo = new Builder(id, "default", url); String checksumPolicy = config.getBoolean("verifyArtifactChecksum") ? CHECKSUM_POLICY_FAIL : CHECKSUM_POLICY_IGNORE; String updatePolicy = alwaysCheck ? UPDATE_POLICY_ALWAYS : UPDATE_POLICY_NEVER; if (enableSnapshots) { repo.setSnapshotPolicy(new RepositoryPolicy(true, updatePolicy, checksumPolicy)); } else { repo.setSnapshotPolicy(new RepositoryPolicy(false, UPDATE_POLICY_NEVER, checksumPolicy)); } repo.setReleasePolicy(new RepositoryPolicy(true, updatePolicy, checksumPolicy)); for (RemoteRepository mirror : mirrors) { repo.addMirroredRepository(mirror); } return repo.build(); } @Nonnull private static ServiceLocator setupServiceLocator() { DefaultServiceLocator serviceLocator = MavenRepositorySystemUtils.newServiceLocator(); serviceLocator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class); serviceLocator.addService(TransporterFactory.class, HttpTransporterFactory.class); serviceLocator.setServices(ModelBuilder.class, new DefaultModelBuilderFactory().newInstance()); return serviceLocator; } }