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