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