001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.internal.impl; 020 021import javax.inject.Inject; 022import javax.inject.Named; 023import javax.inject.Singleton; 024 025import java.io.IOException; 026import java.nio.file.Files; 027import java.nio.file.Path; 028import java.util.ArrayList; 029import java.util.Collection; 030import java.util.Collections; 031import java.util.HashMap; 032import java.util.IdentityHashMap; 033import java.util.List; 034import java.util.ListIterator; 035import java.util.Map; 036 037import org.eclipse.aether.ConfigurationProperties; 038import org.eclipse.aether.RepositoryEvent; 039import org.eclipse.aether.RepositoryEvent.EventType; 040import org.eclipse.aether.RepositorySystemSession; 041import org.eclipse.aether.RequestTrace; 042import org.eclipse.aether.SyncContext; 043import org.eclipse.aether.artifact.Artifact; 044import org.eclipse.aether.impl.Installer; 045import org.eclipse.aether.impl.MetadataGenerator; 046import org.eclipse.aether.impl.MetadataGeneratorFactory; 047import org.eclipse.aether.impl.RepositoryEventDispatcher; 048import org.eclipse.aether.installation.InstallRequest; 049import org.eclipse.aether.installation.InstallResult; 050import org.eclipse.aether.installation.InstallationException; 051import org.eclipse.aether.metadata.MergeableMetadata; 052import org.eclipse.aether.metadata.Metadata; 053import org.eclipse.aether.repository.LocalArtifactRegistration; 054import org.eclipse.aether.repository.LocalMetadataRegistration; 055import org.eclipse.aether.repository.LocalRepositoryManager; 056import org.eclipse.aether.spi.artifact.generator.ArtifactGenerator; 057import org.eclipse.aether.spi.artifact.generator.ArtifactGeneratorFactory; 058import org.eclipse.aether.spi.artifact.transformer.ArtifactTransformer; 059import org.eclipse.aether.spi.io.PathProcessor; 060import org.eclipse.aether.spi.synccontext.SyncContextFactory; 061import org.eclipse.aether.util.ConfigUtils; 062import org.slf4j.Logger; 063import org.slf4j.LoggerFactory; 064 065import static java.util.Objects.requireNonNull; 066 067/** 068 */ 069@Singleton 070@Named 071public class DefaultInstaller implements Installer { 072 private final Logger logger = LoggerFactory.getLogger(getClass()); 073 074 private final PathProcessor pathProcessor; 075 076 private final RepositoryEventDispatcher repositoryEventDispatcher; 077 078 private final Map<String, ArtifactGeneratorFactory> artifactFactories; 079 080 private final Map<String, MetadataGeneratorFactory> metadataFactories; 081 082 private final Map<String, ArtifactTransformer> artifactTransformers; 083 084 private final SyncContextFactory syncContextFactory; 085 086 @Inject 087 public DefaultInstaller( 088 PathProcessor pathProcessor, 089 RepositoryEventDispatcher repositoryEventDispatcher, 090 Map<String, ArtifactGeneratorFactory> artifactFactories, 091 Map<String, MetadataGeneratorFactory> metadataFactories, 092 Map<String, ArtifactTransformer> artifactTransformers, 093 SyncContextFactory syncContextFactory) { 094 this.pathProcessor = requireNonNull(pathProcessor, "path processor cannot be null"); 095 this.repositoryEventDispatcher = 096 requireNonNull(repositoryEventDispatcher, "repository event dispatcher cannot be null"); 097 this.artifactFactories = Collections.unmodifiableMap(artifactFactories); 098 this.metadataFactories = Collections.unmodifiableMap(metadataFactories); 099 this.artifactTransformers = Collections.unmodifiableMap(artifactTransformers); 100 this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null"); 101 } 102 103 @Override 104 public InstallResult install(RepositorySystemSession session, InstallRequest request) throws InstallationException { 105 requireNonNull(session, "session cannot be null"); 106 requireNonNull(request, "request cannot be null"); 107 for (ArtifactTransformer transformer : artifactTransformers.values()) { 108 request = transformer.transformInstallArtifacts(session, request); 109 } 110 try (SyncContext syncContext = syncContextFactory.newInstance(session, false)) { 111 return install(syncContext, session, request); 112 } 113 } 114 115 private InstallResult install(SyncContext syncContext, RepositorySystemSession session, InstallRequest request) 116 throws InstallationException { 117 InstallResult result = new InstallResult(request); 118 119 RequestTrace trace = RequestTrace.newChild(request.getTrace(), request); 120 121 List<Artifact> artifacts = new ArrayList<>(request.getArtifacts()); 122 List<? extends ArtifactGenerator> artifactGenerators = 123 Utils.getArtifactGenerators(session, artifactFactories, request); 124 try { 125 List<Artifact> generatedArtifacts = new ArrayList<>(); 126 for (ArtifactGenerator artifactGenerator : artifactGenerators) { 127 Collection<? extends Artifact> generated = artifactGenerator.generate(generatedArtifacts); 128 for (Artifact generatedArtifact : generated) { 129 Map<String, String> properties = new HashMap<>(generatedArtifact.getProperties()); 130 properties.put( 131 ArtifactGeneratorFactory.ARTIFACT_GENERATOR_ID, 132 requireNonNull(artifactGenerator.generatorId(), "generatorId")); 133 Artifact ga = generatedArtifact.setProperties(properties); 134 generatedArtifacts.add(ga); 135 } 136 } 137 artifacts.addAll(generatedArtifacts); 138 139 List<? extends MetadataGenerator> metadataGenerators = 140 Utils.getMetadataGenerators(session, metadataFactories, request); 141 142 IdentityHashMap<Metadata, Object> processedMetadata = new IdentityHashMap<>(); 143 144 List<Metadata> metadatas = Utils.prepareMetadata(metadataGenerators, artifacts); 145 146 syncContext.acquire(artifacts, Utils.combine(request.getMetadata(), metadatas)); 147 148 for (Metadata metadata : metadatas) { 149 install(session, trace, metadata); 150 processedMetadata.put(metadata, null); 151 result.addMetadata(metadata); 152 } 153 154 boolean hardLink = ConfigUtils.getBoolean( 155 session, 156 ConfigurationProperties.DEFAULT_INSTALLER_HARD_LINK, 157 ConfigurationProperties.INSTALLER_HARD_LINK); 158 159 Exception linkFailure = null; 160 for (ListIterator<Artifact> iterator = artifacts.listIterator(); iterator.hasNext(); ) { 161 Artifact artifact = iterator.next(); 162 163 for (MetadataGenerator generator : metadataGenerators) { 164 artifact = generator.transformArtifact(artifact); 165 } 166 167 iterator.set(artifact); 168 169 Exception failure = install(session, trace, artifact, hardLink); 170 if (failure != null && linkFailure == null) { 171 linkFailure = failure; 172 } else if (failure != null) { 173 linkFailure.addSuppressed(failure); 174 } 175 if (artifact.getProperty(ArtifactGeneratorFactory.ARTIFACT_GENERATOR_ID, null) == null) { 176 result.addArtifact(artifact); 177 } 178 } 179 if (linkFailure != null) { 180 logger.warn( 181 "Hard link not supported, falling back to copy for all artifacts: {}", 182 linkFailure.getMessage(), 183 linkFailure); 184 } 185 186 metadatas = Utils.finishMetadata(metadataGenerators, artifacts); 187 188 syncContext.acquire(null, metadatas); 189 190 for (Metadata metadata : metadatas) { 191 install(session, trace, metadata); 192 processedMetadata.put(metadata, null); 193 result.addMetadata(metadata); 194 } 195 196 for (Metadata metadata : request.getMetadata()) { 197 if (!processedMetadata.containsKey(metadata)) { 198 install(session, trace, metadata); 199 result.addMetadata(metadata); 200 } 201 } 202 203 return result; 204 } finally { 205 for (ArtifactGenerator artifactGenerator : artifactGenerators) { 206 try { 207 artifactGenerator.close(); 208 } catch (Exception e) { 209 logger.warn("ArtifactGenerator close failure: {}", artifactGenerator.generatorId(), e); 210 } 211 } 212 } 213 } 214 215 /** 216 * Installs a single artifact. Returns the link failure exception if hard-linking was attempted but failed 217 * (caller logs it once after the loop), or {@code null} if the artifact was installed successfully via hard link 218 * or if {@code hardLink} is {@code false}. 219 */ 220 private Exception install(RepositorySystemSession session, RequestTrace trace, Artifact artifact, boolean hardLink) 221 throws InstallationException { 222 final LocalRepositoryManager lrm = session.getLocalRepositoryManager(); 223 final Path srcPath = artifact.getPath(); 224 final Path dstPath = lrm.getAbsolutePathForLocalArtifact(artifact); 225 226 artifactInstalling(session, trace, artifact, dstPath); 227 228 Exception linkFailure = null; 229 Exception exception = null; 230 try { 231 if (dstPath.equals(srcPath)) { 232 throw new IllegalStateException("cannot install " + dstPath + " to same path"); 233 } 234 235 if (hardLink) { 236 Files.createDirectories(dstPath.getParent()); 237 if (Files.isDirectory(dstPath)) { 238 throw new IOException("destination is a directory: " + dstPath); 239 } 240 try { 241 Files.deleteIfExists(dstPath); 242 Files.createLink(dstPath, srcPath); 243 } catch (UnsupportedOperationException | IllegalArgumentException | IOException e) { 244 // cross-device link (EXDEV), provider mismatch (e.g. Jimfs), or unsupported FS — fall back to copy 245 linkFailure = e; 246 pathProcessor.copyWithTimestamp(srcPath, dstPath); 247 } 248 } else { 249 pathProcessor.copyWithTimestamp(srcPath, dstPath); 250 } 251 lrm.add(session, new LocalArtifactRegistration(artifact)); 252 } catch (Exception e) { 253 exception = e; 254 throw new InstallationException("Failed to install artifact " + artifact + ": " + e.getMessage(), e); 255 } finally { 256 artifactInstalled(session, trace, artifact, dstPath, exception); 257 } 258 return linkFailure; 259 } 260 261 private void install(RepositorySystemSession session, RequestTrace trace, Metadata metadata) 262 throws InstallationException { 263 LocalRepositoryManager lrm = session.getLocalRepositoryManager(); 264 265 Path dstPath = lrm.getAbsolutePathForLocalMetadata(metadata); 266 267 metadataInstalling(session, trace, metadata, dstPath); 268 269 Exception exception = null; 270 try { 271 if (metadata instanceof MergeableMetadata) { 272 ((MergeableMetadata) metadata).merge(dstPath, dstPath); 273 } else { 274 if (dstPath.equals(metadata.getPath())) { 275 throw new IllegalStateException("cannot install " + dstPath + " to same path"); 276 } 277 pathProcessor.copy(metadata.getPath(), dstPath); 278 } 279 280 lrm.add(session, new LocalMetadataRegistration(metadata)); 281 } catch (Exception e) { 282 exception = e; 283 throw new InstallationException("Failed to install metadata " + metadata + ": " + e.getMessage(), e); 284 } finally { 285 metadataInstalled(session, trace, metadata, dstPath, exception); 286 } 287 } 288 289 private void artifactInstalling( 290 RepositorySystemSession session, RequestTrace trace, Artifact artifact, Path dstPath) { 291 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_INSTALLING); 292 event.setTrace(trace); 293 event.setArtifact(artifact); 294 event.setRepository(session.getLocalRepositoryManager().getRepository()); 295 event.setPath(dstPath); 296 297 repositoryEventDispatcher.dispatch(event.build()); 298 } 299 300 private void artifactInstalled( 301 RepositorySystemSession session, RequestTrace trace, Artifact artifact, Path dstPath, Exception exception) { 302 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_INSTALLED); 303 event.setTrace(trace); 304 event.setArtifact(artifact); 305 event.setRepository(session.getLocalRepositoryManager().getRepository()); 306 event.setPath(dstPath); 307 event.setException(exception); 308 309 repositoryEventDispatcher.dispatch(event.build()); 310 } 311 312 private void metadataInstalling( 313 RepositorySystemSession session, RequestTrace trace, Metadata metadata, Path dstPath) { 314 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_INSTALLING); 315 event.setTrace(trace); 316 event.setMetadata(metadata); 317 event.setRepository(session.getLocalRepositoryManager().getRepository()); 318 event.setPath(dstPath); 319 320 repositoryEventDispatcher.dispatch(event.build()); 321 } 322 323 private void metadataInstalled( 324 RepositorySystemSession session, RequestTrace trace, Metadata metadata, Path dstPath, Exception exception) { 325 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_INSTALLED); 326 event.setTrace(trace); 327 event.setMetadata(metadata); 328 event.setRepository(session.getLocalRepositoryManager().getRepository()); 329 event.setPath(dstPath); 330 event.setException(exception); 331 332 repositoryEventDispatcher.dispatch(event.build()); 333 } 334}