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