View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.internal.impl;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.IOException;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.HashMap;
32  import java.util.IdentityHashMap;
33  import java.util.List;
34  import java.util.ListIterator;
35  import java.util.Map;
36  
37  import org.eclipse.aether.ConfigurationProperties;
38  import org.eclipse.aether.RepositoryEvent;
39  import org.eclipse.aether.RepositoryEvent.EventType;
40  import org.eclipse.aether.RepositorySystemSession;
41  import org.eclipse.aether.RequestTrace;
42  import org.eclipse.aether.SyncContext;
43  import org.eclipse.aether.artifact.Artifact;
44  import org.eclipse.aether.impl.Installer;
45  import org.eclipse.aether.impl.MetadataGenerator;
46  import org.eclipse.aether.impl.MetadataGeneratorFactory;
47  import org.eclipse.aether.impl.RepositoryEventDispatcher;
48  import org.eclipse.aether.installation.InstallRequest;
49  import org.eclipse.aether.installation.InstallResult;
50  import org.eclipse.aether.installation.InstallationException;
51  import org.eclipse.aether.metadata.MergeableMetadata;
52  import org.eclipse.aether.metadata.Metadata;
53  import org.eclipse.aether.repository.LocalArtifactRegistration;
54  import org.eclipse.aether.repository.LocalMetadataRegistration;
55  import org.eclipse.aether.repository.LocalRepositoryManager;
56  import org.eclipse.aether.spi.artifact.generator.ArtifactGenerator;
57  import org.eclipse.aether.spi.artifact.generator.ArtifactGeneratorFactory;
58  import org.eclipse.aether.spi.artifact.transformer.ArtifactTransformer;
59  import org.eclipse.aether.spi.io.PathProcessor;
60  import org.eclipse.aether.spi.synccontext.SyncContextFactory;
61  import org.eclipse.aether.util.ConfigUtils;
62  import org.slf4j.Logger;
63  import org.slf4j.LoggerFactory;
64  
65  import static java.util.Objects.requireNonNull;
66  
67  /**
68   */
69  @Singleton
70  @Named
71  public class DefaultInstaller implements Installer {
72      private final Logger logger = LoggerFactory.getLogger(getClass());
73  
74      private final PathProcessor pathProcessor;
75  
76      private final RepositoryEventDispatcher repositoryEventDispatcher;
77  
78      private final Map<String, ArtifactGeneratorFactory> artifactFactories;
79  
80      private final Map<String, MetadataGeneratorFactory> metadataFactories;
81  
82      private final Map<String, ArtifactTransformer> artifactTransformers;
83  
84      private final SyncContextFactory syncContextFactory;
85  
86      @Inject
87      public DefaultInstaller(
88              PathProcessor pathProcessor,
89              RepositoryEventDispatcher repositoryEventDispatcher,
90              Map<String, ArtifactGeneratorFactory> artifactFactories,
91              Map<String, MetadataGeneratorFactory> metadataFactories,
92              Map<String, ArtifactTransformer> artifactTransformers,
93              SyncContextFactory syncContextFactory) {
94          this.pathProcessor = requireNonNull(pathProcessor, "path processor cannot be null");
95          this.repositoryEventDispatcher =
96                  requireNonNull(repositoryEventDispatcher, "repository event dispatcher cannot be null");
97          this.artifactFactories = Collections.unmodifiableMap(artifactFactories);
98          this.metadataFactories = Collections.unmodifiableMap(metadataFactories);
99          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 }