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.RepositoryEvent;
038import org.eclipse.aether.RepositoryEvent.EventType;
039import org.eclipse.aether.RepositoryException;
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.deployment.DeployRequest;
045import org.eclipse.aether.deployment.DeployResult;
046import org.eclipse.aether.deployment.DeploymentException;
047import org.eclipse.aether.impl.Deployer;
048import org.eclipse.aether.impl.MetadataGenerator;
049import org.eclipse.aether.impl.MetadataGeneratorFactory;
050import org.eclipse.aether.impl.OfflineController;
051import org.eclipse.aether.impl.RemoteRepositoryManager;
052import org.eclipse.aether.impl.RepositoryConnectorProvider;
053import org.eclipse.aether.impl.RepositoryEventDispatcher;
054import org.eclipse.aether.impl.UpdateCheck;
055import org.eclipse.aether.impl.UpdateCheckManager;
056import org.eclipse.aether.metadata.MergeableMetadata;
057import org.eclipse.aether.metadata.Metadata;
058import org.eclipse.aether.repository.LocalRepositoryManager;
059import org.eclipse.aether.repository.RemoteRepository;
060import org.eclipse.aether.repository.RepositoryPolicy;
061import org.eclipse.aether.spi.artifact.generator.ArtifactGenerator;
062import org.eclipse.aether.spi.artifact.generator.ArtifactGeneratorFactory;
063import org.eclipse.aether.spi.artifact.transformer.ArtifactTransformer;
064import org.eclipse.aether.spi.connector.ArtifactUpload;
065import org.eclipse.aether.spi.connector.MetadataDownload;
066import org.eclipse.aether.spi.connector.MetadataUpload;
067import org.eclipse.aether.spi.connector.RepositoryConnector;
068import org.eclipse.aether.spi.io.PathProcessor;
069import org.eclipse.aether.spi.synccontext.SyncContextFactory;
070import org.eclipse.aether.transfer.ArtifactTransferException;
071import org.eclipse.aether.transfer.MetadataNotFoundException;
072import org.eclipse.aether.transfer.MetadataTransferException;
073import org.eclipse.aether.transfer.NoRepositoryConnectorException;
074import org.eclipse.aether.transfer.RepositoryOfflineException;
075import org.eclipse.aether.transfer.TransferCancelledException;
076import org.eclipse.aether.transfer.TransferEvent;
077import org.slf4j.Logger;
078import org.slf4j.LoggerFactory;
079
080import static java.util.Objects.requireNonNull;
081
082/**
083 */
084@Singleton
085@Named
086public class DefaultDeployer implements Deployer {
087    private final Logger logger = LoggerFactory.getLogger(getClass());
088
089    private final PathProcessor pathProcessor;
090
091    private final RepositoryEventDispatcher repositoryEventDispatcher;
092
093    private final RepositoryConnectorProvider repositoryConnectorProvider;
094
095    private final RemoteRepositoryManager remoteRepositoryManager;
096
097    private final UpdateCheckManager updateCheckManager;
098
099    private final Map<String, ArtifactGeneratorFactory> artifactFactories;
100
101    private final Map<String, MetadataGeneratorFactory> metadataFactories;
102
103    private final Map<String, ArtifactTransformer> artifactTransformers;
104
105    private final SyncContextFactory syncContextFactory;
106
107    private final OfflineController offlineController;
108
109    @SuppressWarnings("checkstyle:parameternumber")
110    @Inject
111    public DefaultDeployer(
112            PathProcessor pathProcessor,
113            RepositoryEventDispatcher repositoryEventDispatcher,
114            RepositoryConnectorProvider repositoryConnectorProvider,
115            RemoteRepositoryManager remoteRepositoryManager,
116            UpdateCheckManager updateCheckManager,
117            Map<String, ArtifactGeneratorFactory> artifactFactories,
118            Map<String, MetadataGeneratorFactory> metadataFactories,
119            Map<String, ArtifactTransformer> artifactTransformers,
120            SyncContextFactory syncContextFactory,
121            OfflineController offlineController) {
122        this.pathProcessor = requireNonNull(pathProcessor, "path processor cannot be null");
123        this.repositoryEventDispatcher =
124                requireNonNull(repositoryEventDispatcher, "repository event dispatcher cannot be null");
125        this.repositoryConnectorProvider =
126                requireNonNull(repositoryConnectorProvider, "repository connector provider cannot be null");
127        this.remoteRepositoryManager =
128                requireNonNull(remoteRepositoryManager, "remote repository provider cannot be null");
129        this.updateCheckManager = requireNonNull(updateCheckManager, "update check manager cannot be null");
130        this.artifactFactories = Collections.unmodifiableMap(artifactFactories);
131        this.metadataFactories = Collections.unmodifiableMap(metadataFactories);
132        this.artifactTransformers = Collections.unmodifiableMap(artifactTransformers);
133        this.syncContextFactory = requireNonNull(syncContextFactory, "sync context factory cannot be null");
134        this.offlineController = requireNonNull(offlineController, "offline controller cannot be null");
135    }
136
137    @Override
138    public DeployResult deploy(RepositorySystemSession session, DeployRequest request) throws DeploymentException {
139        requireNonNull(session, "session cannot be null");
140        requireNonNull(request, "request cannot be null");
141        try {
142            Utils.checkOffline(session, offlineController, request.getRepository());
143        } catch (RepositoryOfflineException e) {
144            throw new DeploymentException(
145                    "Cannot deploy while " + request.getRepository().getId() + " ("
146                            + request.getRepository().getUrl() + ") is in offline mode",
147                    e);
148        }
149
150        for (ArtifactTransformer transformer : artifactTransformers.values()) {
151            request = transformer.transformDeployArtifacts(session, request);
152        }
153        try (SyncContext syncContext = syncContextFactory.newInstance(session, true)) {
154            return deploy(syncContext, session, request);
155        }
156    }
157
158    private DeployResult deploy(SyncContext syncContext, RepositorySystemSession session, DeployRequest request)
159            throws DeploymentException {
160        DeployResult result = new DeployResult(request);
161
162        RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
163
164        RemoteRepository repository = request.getRepository();
165
166        RepositoryConnector connector;
167        try {
168            connector = repositoryConnectorProvider.newRepositoryConnector(session, repository);
169        } catch (NoRepositoryConnectorException e) {
170            throw new DeploymentException("Failed to deploy artifacts/metadata: " + e.getMessage(), e);
171        }
172
173        List<Artifact> artifacts = new ArrayList<>(request.getArtifacts());
174        List<? extends ArtifactGenerator> artifactGenerators =
175                Utils.getArtifactGenerators(session, artifactFactories, request);
176        try {
177            List<Artifact> generatedArtifacts = new ArrayList<>();
178            for (ArtifactGenerator artifactGenerator : artifactGenerators) {
179                Collection<? extends Artifact> generated = artifactGenerator.generate(generatedArtifacts);
180                for (Artifact generatedArtifact : generated) {
181                    Map<String, String> properties = new HashMap<>(generatedArtifact.getProperties());
182                    properties.put(
183                            ArtifactGeneratorFactory.ARTIFACT_GENERATOR_ID,
184                            requireNonNull(artifactGenerator.generatorId(), "generatorId"));
185                    Artifact ga = generatedArtifact.setProperties(properties);
186                    generatedArtifacts.add(ga);
187                }
188            }
189            artifacts.addAll(generatedArtifacts);
190
191            List<? extends MetadataGenerator> metadataGenerators =
192                    Utils.getMetadataGenerators(session, metadataFactories, request);
193
194            List<ArtifactUpload> artifactUploads = new ArrayList<>();
195            List<MetadataUpload> metadataUploads = new ArrayList<>();
196            IdentityHashMap<Metadata, Object> processedMetadata = new IdentityHashMap<>();
197
198            EventCatapult catapult = new EventCatapult(session, trace, repository, repositoryEventDispatcher);
199
200            List<Metadata> metadatas = Utils.prepareMetadata(metadataGenerators, artifacts);
201
202            syncContext.acquire(artifacts, Utils.combine(request.getMetadata(), metadatas));
203
204            for (Metadata metadata : metadatas) {
205                upload(metadataUploads, session, metadata, repository, connector, catapult);
206                processedMetadata.put(metadata, null);
207            }
208
209            for (ListIterator<Artifact> iterator = artifacts.listIterator(); iterator.hasNext(); ) {
210                Artifact artifact = iterator.next();
211
212                for (MetadataGenerator generator : metadataGenerators) {
213                    artifact = generator.transformArtifact(artifact);
214                }
215
216                iterator.set(artifact);
217
218                ArtifactUpload upload = new ArtifactUpload(artifact, artifact.getPath());
219                upload.setTrace(trace);
220                upload.setListener(new ArtifactUploadListener(catapult, upload));
221                artifactUploads.add(upload);
222            }
223
224            connector.put(artifactUploads, null);
225
226            for (ArtifactUpload upload : artifactUploads) {
227                if (upload.getException() != null) {
228                    throw new DeploymentException(
229                            "Failed to deploy artifacts: "
230                                    + upload.getException().getMessage(),
231                            upload.getException());
232                }
233                if (upload.getArtifact().getProperty(ArtifactGeneratorFactory.ARTIFACT_GENERATOR_ID, null) == null) {
234                    result.addArtifact(upload.getArtifact());
235                }
236            }
237
238            metadatas = Utils.finishMetadata(metadataGenerators, artifacts);
239
240            syncContext.acquire(null, metadatas);
241
242            for (Metadata metadata : metadatas) {
243                upload(metadataUploads, session, metadata, repository, connector, catapult);
244                processedMetadata.put(metadata, null);
245            }
246
247            for (Metadata metadata : request.getMetadata()) {
248                if (!processedMetadata.containsKey(metadata)) {
249                    upload(metadataUploads, session, metadata, repository, connector, catapult);
250                    processedMetadata.put(metadata, null);
251                }
252            }
253
254            connector.put(null, metadataUploads);
255
256            for (MetadataUpload upload : metadataUploads) {
257                if (upload.getException() != null) {
258                    throw new DeploymentException(
259                            "Failed to deploy metadata: "
260                                    + upload.getException().getMessage(),
261                            upload.getException());
262                }
263                result.addMetadata(upload.getMetadata());
264            }
265        } finally {
266            connector.close();
267            for (ArtifactGenerator artifactGenerator : artifactGenerators) {
268                try {
269                    artifactGenerator.close();
270                } catch (Exception e) {
271                    logger.warn("ArtifactGenerator close failure: {}", artifactGenerator.generatorId(), e);
272                }
273            }
274        }
275
276        return result;
277    }
278
279    private void upload(
280            Collection<MetadataUpload> metadataUploads,
281            RepositorySystemSession session,
282            Metadata metadata,
283            RemoteRepository repository,
284            RepositoryConnector connector,
285            EventCatapult catapult)
286            throws DeploymentException {
287        LocalRepositoryManager lrm = session.getLocalRepositoryManager();
288        Path dstPath = lrm.getAbsolutePathForRemoteMetadata(metadata, repository, "");
289
290        if (metadata instanceof MergeableMetadata) {
291            if (!((MergeableMetadata) metadata).isMerged()) {
292                RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVING);
293                event.setTrace(catapult.getTrace());
294                event.setMetadata(metadata);
295                event.setRepository(repository);
296                repositoryEventDispatcher.dispatch(event.build());
297
298                event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADING);
299                event.setTrace(catapult.getTrace());
300                event.setMetadata(metadata);
301                event.setRepository(repository);
302                repositoryEventDispatcher.dispatch(event.build());
303
304                RepositoryPolicy policy = getPolicy(session, repository, metadata.getNature());
305                MetadataDownload download = new MetadataDownload();
306                download.setMetadata(metadata);
307                download.setPath(dstPath);
308                download.setChecksumPolicy(policy.getChecksumPolicy());
309                download.setListener(SafeTransferListener.wrap(session));
310                download.setTrace(catapult.getTrace());
311                connector.get(null, Collections.singletonList(download));
312
313                Exception error = download.getException();
314
315                if (error instanceof MetadataNotFoundException) {
316                    try {
317                        Files.deleteIfExists(dstPath);
318                    } catch (IOException e) {
319                        throw new DeploymentException(
320                                "Failed to delete cached metadata " + metadata + ": " + e.getMessage(), e);
321                    }
322                }
323
324                event = new RepositoryEvent.Builder(session, EventType.METADATA_DOWNLOADED);
325                event.setTrace(catapult.getTrace());
326                event.setMetadata(metadata);
327                event.setRepository(repository);
328                event.setException(error);
329                event.setPath(dstPath);
330                repositoryEventDispatcher.dispatch(event.build());
331
332                event = new RepositoryEvent.Builder(session, EventType.METADATA_RESOLVED);
333                event.setTrace(catapult.getTrace());
334                event.setMetadata(metadata);
335                event.setRepository(repository);
336                event.setException(error);
337                event.setPath(dstPath);
338                repositoryEventDispatcher.dispatch(event.build());
339
340                if (error != null && !(error instanceof MetadataNotFoundException)) {
341                    throw new DeploymentException(
342                            "Failed to retrieve remote metadata " + metadata + ": " + error.getMessage(), error);
343                }
344            }
345
346            try {
347                ((MergeableMetadata) metadata).merge(dstPath, dstPath);
348            } catch (RepositoryException e) {
349                throw new DeploymentException("Failed to update metadata " + metadata + ": " + e.getMessage(), e);
350            }
351        } else {
352            if (metadata.getPath() == null) {
353                throw new DeploymentException("Failed to update metadata " + metadata + ": No file attached.");
354            }
355            try {
356                pathProcessor.copy(metadata.getPath(), dstPath);
357            } catch (IOException e) {
358                throw new DeploymentException("Failed to update metadata " + metadata + ": " + e.getMessage(), e);
359            }
360        }
361
362        UpdateCheck<Metadata, MetadataTransferException> check = new UpdateCheck<>();
363        check.setItem(metadata);
364        check.setPath(dstPath);
365        check.setRepository(repository);
366        check.setAuthoritativeRepository(repository);
367        updateCheckManager.touchMetadata(session, check);
368
369        MetadataUpload upload = new MetadataUpload(metadata, dstPath);
370        upload.setTrace(catapult.getTrace());
371        upload.setListener(new MetadataUploadListener(catapult, upload));
372        metadataUploads.add(upload);
373    }
374
375    private RepositoryPolicy getPolicy(
376            RepositorySystemSession session, RemoteRepository repository, Metadata.Nature nature) {
377        boolean releases = !Metadata.Nature.SNAPSHOT.equals(nature);
378        boolean snapshots = !Metadata.Nature.RELEASE.equals(nature);
379        return remoteRepositoryManager.getPolicy(session, repository, releases, snapshots);
380    }
381
382    static final class EventCatapult {
383
384        private final RepositorySystemSession session;
385
386        private final RequestTrace trace;
387
388        private final RemoteRepository repository;
389
390        private final RepositoryEventDispatcher dispatcher;
391
392        EventCatapult(
393                RepositorySystemSession session,
394                RequestTrace trace,
395                RemoteRepository repository,
396                RepositoryEventDispatcher dispatcher) {
397            this.session = session;
398            this.trace = trace;
399            this.repository = repository;
400            this.dispatcher = dispatcher;
401        }
402
403        public RepositorySystemSession getSession() {
404            return session;
405        }
406
407        public RequestTrace getTrace() {
408            return trace;
409        }
410
411        public void artifactDeploying(Artifact artifact, Path path) {
412            RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DEPLOYING);
413            event.setTrace(trace);
414            event.setArtifact(artifact);
415            event.setRepository(repository);
416            event.setPath(path);
417
418            dispatcher.dispatch(event.build());
419        }
420
421        public void artifactDeployed(Artifact artifact, Path path, ArtifactTransferException exception) {
422            RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.ARTIFACT_DEPLOYED);
423            event.setTrace(trace);
424            event.setArtifact(artifact);
425            event.setRepository(repository);
426            event.setPath(path);
427            event.setException(exception);
428
429            dispatcher.dispatch(event.build());
430        }
431
432        public void metadataDeploying(Metadata metadata, Path path) {
433            RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DEPLOYING);
434            event.setTrace(trace);
435            event.setMetadata(metadata);
436            event.setRepository(repository);
437            event.setPath(path);
438
439            dispatcher.dispatch(event.build());
440        }
441
442        public void metadataDeployed(Metadata metadata, Path path, Exception exception) {
443            RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_DEPLOYED);
444            event.setTrace(trace);
445            event.setMetadata(metadata);
446            event.setRepository(repository);
447            event.setPath(path);
448            event.setException(exception);
449
450            dispatcher.dispatch(event.build());
451        }
452    }
453
454    static final class ArtifactUploadListener extends SafeTransferListener {
455
456        private final EventCatapult catapult;
457
458        private final ArtifactUpload transfer;
459
460        ArtifactUploadListener(EventCatapult catapult, ArtifactUpload transfer) {
461            super(catapult.getSession());
462            this.catapult = catapult;
463            this.transfer = transfer;
464        }
465
466        @Override
467        public void transferInitiated(TransferEvent event) throws TransferCancelledException {
468            super.transferInitiated(event);
469            requireNonNull(event, "event cannot be null");
470            catapult.artifactDeploying(transfer.getArtifact(), transfer.getPath());
471        }
472
473        @Override
474        public void transferFailed(TransferEvent event) {
475            super.transferFailed(event);
476            requireNonNull(event, "event cannot be null");
477            catapult.artifactDeployed(transfer.getArtifact(), transfer.getPath(), transfer.getException());
478        }
479
480        @Override
481        public void transferSucceeded(TransferEvent event) {
482            super.transferSucceeded(event);
483            requireNonNull(event, "event cannot be null");
484            catapult.artifactDeployed(transfer.getArtifact(), transfer.getPath(), null);
485        }
486    }
487
488    static final class MetadataUploadListener extends SafeTransferListener {
489
490        private final EventCatapult catapult;
491
492        private final MetadataUpload transfer;
493
494        MetadataUploadListener(EventCatapult catapult, MetadataUpload transfer) {
495            super(catapult.getSession());
496            this.catapult = catapult;
497            this.transfer = transfer;
498        }
499
500        @Override
501        public void transferInitiated(TransferEvent event) throws TransferCancelledException {
502            super.transferInitiated(event);
503            requireNonNull(event, "event cannot be null");
504            catapult.metadataDeploying(transfer.getMetadata(), transfer.getPath());
505        }
506
507        @Override
508        public void transferFailed(TransferEvent event) {
509            super.transferFailed(event);
510            requireNonNull(event, "event cannot be null");
511            catapult.metadataDeployed(transfer.getMetadata(), transfer.getPath(), transfer.getException());
512        }
513
514        @Override
515        public void transferSucceeded(TransferEvent event) {
516            super.transferSucceeded(event);
517            requireNonNull(event, "event cannot be null");
518            catapult.metadataDeployed(transfer.getMetadata(), transfer.getPath(), null);
519        }
520    }
521}