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