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