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.net.URI;
026import java.net.URISyntaxException;
027import java.util.ArrayList;
028import java.util.Collections;
029import java.util.LinkedHashMap;
030import java.util.List;
031
032import org.eclipse.aether.ConfigurationProperties;
033import org.eclipse.aether.RepositorySystemSession;
034import org.eclipse.aether.artifact.Artifact;
035import org.eclipse.aether.metadata.Metadata;
036import org.eclipse.aether.repository.RemoteRepository;
037import org.eclipse.aether.spi.artifact.ArtifactPredicate;
038import org.eclipse.aether.spi.artifact.ArtifactPredicateFactory;
039import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
040import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactorySelector;
041import org.eclipse.aether.spi.connector.layout.RepositoryLayout;
042import org.eclipse.aether.spi.connector.layout.RepositoryLayoutFactory;
043import org.eclipse.aether.transfer.NoRepositoryLayoutException;
044import org.eclipse.aether.util.ConfigUtils;
045
046import static java.util.Objects.requireNonNull;
047import static org.eclipse.aether.util.PathUtils.validateArtifactComponents;
048import static org.eclipse.aether.util.PathUtils.validateMetadataComponents;
049
050/**
051 * Provides a Maven-2 repository layout for repositories with content type {@code "default"}.
052 */
053@Singleton
054@Named(Maven2RepositoryLayoutFactory.NAME)
055public final class Maven2RepositoryLayoutFactory implements RepositoryLayoutFactory {
056    public static final String NAME = "maven2";
057
058    /**
059     * Comma-separated list of checksum algorithms with which checksums are validated (downloaded) and generated
060     * (uploaded) with this layout. Resolver by default supports following algorithms: MD5, SHA-1, SHA-256 and
061     * SHA-512. New algorithms can be added by implementing ChecksumAlgorithmFactory component. To configure separately
062     * checksums for download or upload, use {@link #CONFIG_PROP_DOWNLOAD_CHECKSUMS_ALGORITHMS} and
063     * {@link #CONFIG_PROP_UPLOAD_CHECKSUMS_ALGORITHMS} respectively.
064     *
065     * @since 1.8.0
066     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
067     * @configurationType {@link java.lang.String}
068     * @configurationDefaultValue {@link #DEFAULT_CHECKSUMS_ALGORITHMS}
069     * @configurationRepoIdSuffix Yes
070     */
071    public static final String CONFIG_PROP_CHECKSUMS_ALGORITHMS =
072            ConfigurationProperties.PREFIX_CHECKSUMS + "checksumAlgorithms";
073
074    public static final String DEFAULT_CHECKSUMS_ALGORITHMS = "SHA-1,MD5";
075
076    /**
077     * Comma-separated list of checksum algorithms with which checksums are generated and uploaded
078     * with this layout. Resolver by default supports following algorithms: MD5, SHA-1, SHA-256 and
079     * SHA-512. New algorithms can be added by implementing ChecksumAlgorithmFactory component.
080     * If this property is set, it <em>overrides</em> the value set in {@link #CONFIG_PROP_CHECKSUMS_ALGORITHMS} for
081     * uploads.
082     *
083     * @since 2.0.15
084     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
085     * @configurationType {@link java.lang.String}
086     * @configurationDefaultValue {@link #DEFAULT_CHECKSUMS_ALGORITHMS}
087     * @configurationRepoIdSuffix Yes
088     */
089    public static final String CONFIG_PROP_UPLOAD_CHECKSUMS_ALGORITHMS =
090            ConfigurationProperties.PREFIX_CHECKSUMS + "uploadChecksumAlgorithms";
091
092    /**
093     * Comma-separated list of checksum algorithms with which checksums are validated (downloaded) with this layout.
094     * Resolver by default supports following algorithms: MD5, SHA-1, SHA-256 and SHA-512.
095     * New algorithms can be added by implementing ChecksumAlgorithmFactory component.
096     * If this property is set, it <em>overrides</em> the value set in {@link #CONFIG_PROP_CHECKSUMS_ALGORITHMS} for
097     * downloads.
098     *
099     * @since 2.0.15
100     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
101     * @configurationType {@link java.lang.String}
102     * @configurationDefaultValue {@link #DEFAULT_CHECKSUMS_ALGORITHMS}
103     * @configurationRepoIdSuffix Yes
104     */
105    public static final String CONFIG_PROP_DOWNLOAD_CHECKSUMS_ALGORITHMS =
106            ConfigurationProperties.PREFIX_CHECKSUMS + "downloadChecksumAlgorithms";
107
108    private float priority;
109
110    private final ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector;
111
112    private final ArtifactPredicateFactory artifactPredicateFactory;
113
114    public float getPriority() {
115        return priority;
116    }
117
118    @Inject
119    public Maven2RepositoryLayoutFactory(
120            ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector,
121            ArtifactPredicateFactory artifactPredicateFactory) {
122        this.checksumAlgorithmFactorySelector = requireNonNull(checksumAlgorithmFactorySelector);
123        this.artifactPredicateFactory = requireNonNull(artifactPredicateFactory);
124    }
125
126    /**
127     * Sets the priority of this component.
128     *
129     * @param priority The priority.
130     * @return This component for chaining, never {@code null}.
131     */
132    public Maven2RepositoryLayoutFactory setPriority(float priority) {
133        this.priority = priority;
134        return this;
135    }
136
137    public RepositoryLayout newInstance(RepositorySystemSession session, RemoteRepository repository)
138            throws NoRepositoryLayoutException {
139        requireNonNull(session, "session cannot be null");
140        requireNonNull(repository, "repository cannot be null");
141        if (!"default".equals(repository.getContentType())) {
142            throw new NoRepositoryLayoutException(repository);
143        }
144
145        // explicit property for download (will be empty if not configured)
146        List<String> downloadChecksumsAlgorithmNames = ConfigUtils.parseCommaSeparatedUniqueNames(ConfigUtils.getString(
147                session,
148                DEFAULT_CHECKSUMS_ALGORITHMS,
149                CONFIG_PROP_DOWNLOAD_CHECKSUMS_ALGORITHMS + "." + repository.getId(),
150                CONFIG_PROP_DOWNLOAD_CHECKSUMS_ALGORITHMS,
151                CONFIG_PROP_CHECKSUMS_ALGORITHMS + "." + repository.getId(),
152                CONFIG_PROP_CHECKSUMS_ALGORITHMS,
153                // MRESOLVER-701: support legacy properties for simpler transitioning
154                "aether.checksums.algorithms." + repository.getId(),
155                "aether.checksums.algorithms"));
156
157        // explicit property for upload (will be empty if not configured)
158        List<String> uploadChecksumsAlgorithmNames = ConfigUtils.parseCommaSeparatedUniqueNames(ConfigUtils.getString(
159                session,
160                DEFAULT_CHECKSUMS_ALGORITHMS,
161                CONFIG_PROP_UPLOAD_CHECKSUMS_ALGORITHMS + "." + repository.getId(),
162                CONFIG_PROP_UPLOAD_CHECKSUMS_ALGORITHMS,
163                CONFIG_PROP_CHECKSUMS_ALGORITHMS + "." + repository.getId(),
164                CONFIG_PROP_CHECKSUMS_ALGORITHMS,
165                // MRESOLVER-701: support legacy properties for simpler transitioning
166                "aether.checksums.algorithms." + repository.getId(),
167                "aether.checksums.algorithms"));
168
169        return new Maven2RepositoryLayout(
170                checksumAlgorithmFactorySelector.selectList(downloadChecksumsAlgorithmNames),
171                checksumAlgorithmFactorySelector.selectList(uploadChecksumsAlgorithmNames),
172                artifactPredicateFactory.newInstance(session));
173    }
174
175    private static class Maven2RepositoryLayout implements RepositoryLayout {
176        private final List<ChecksumAlgorithmFactory> configuredDownloadChecksumAlgorithms;
177        private final List<ChecksumAlgorithmFactory> configuredUploadChecksumAlgorithms;
178        private final ArtifactPredicate artifactPredicate;
179
180        private Maven2RepositoryLayout(
181                List<ChecksumAlgorithmFactory> configuredDownloadChecksumAlgorithms,
182                List<ChecksumAlgorithmFactory> configuredUploadChecksumAlgorithms,
183                ArtifactPredicate artifactPredicate) {
184            this.configuredDownloadChecksumAlgorithms =
185                    Collections.unmodifiableList(configuredDownloadChecksumAlgorithms);
186            this.configuredUploadChecksumAlgorithms = Collections.unmodifiableList(configuredUploadChecksumAlgorithms);
187            this.artifactPredicate = requireNonNull(artifactPredicate);
188        }
189
190        private URI toUri(String path) {
191            try {
192                return new URI(null, null, path, null);
193            } catch (URISyntaxException e) {
194                throw new IllegalStateException(e);
195            }
196        }
197
198        @Override
199        public List<ChecksumAlgorithmFactory> getChecksumAlgorithmFactories() {
200            LinkedHashMap<String, ChecksumAlgorithmFactory> factories = new LinkedHashMap<>();
201            configuredDownloadChecksumAlgorithms.forEach(f -> factories.putIfAbsent(f.getName(), f));
202            configuredUploadChecksumAlgorithms.forEach(f -> factories.putIfAbsent(f.getName(), f));
203            return Collections.unmodifiableList(new ArrayList<>(factories.values()));
204        }
205
206        @Override
207        public List<ChecksumAlgorithmFactory> getChecksumAlgorithmFactories(boolean upload) {
208            if (upload) {
209                return configuredUploadChecksumAlgorithms;
210            } else {
211                return configuredDownloadChecksumAlgorithms;
212            }
213        }
214
215        @Override
216        public boolean hasChecksums(Artifact artifact) {
217            return !artifactPredicate.isWithoutChecksum(artifact);
218        }
219
220        @Override
221        public URI getLocation(Artifact artifact, boolean upload) {
222            validateArtifactComponents(artifact);
223
224            StringBuilder path = new StringBuilder(128);
225
226            path.append(artifact.getGroupId().replace('.', '/')).append('/');
227
228            path.append(artifact.getArtifactId()).append('/');
229
230            path.append(artifact.getBaseVersion()).append('/');
231
232            path.append(artifact.getArtifactId()).append('-').append(artifact.getVersion());
233
234            if (!artifact.getClassifier().isEmpty()) {
235                path.append('-').append(artifact.getClassifier());
236            }
237
238            if (!artifact.getExtension().isEmpty()) {
239                path.append('.').append(artifact.getExtension());
240            }
241
242            return toUri(path.toString());
243        }
244
245        @Override
246        public URI getLocation(Metadata metadata, boolean upload) {
247            validateMetadataComponents(metadata);
248
249            StringBuilder path = new StringBuilder(128);
250
251            if (!metadata.getGroupId().isEmpty()) {
252                path.append(metadata.getGroupId().replace('.', '/')).append('/');
253
254                if (!metadata.getArtifactId().isEmpty()) {
255                    path.append(metadata.getArtifactId()).append('/');
256
257                    if (!metadata.getVersion().isEmpty()) {
258                        path.append(metadata.getVersion()).append('/');
259                    }
260                }
261            }
262
263            path.append(metadata.getType());
264
265            return toUri(path.toString());
266        }
267
268        @Override
269        public List<ChecksumLocation> getChecksumLocations(Artifact artifact, boolean upload, URI location) {
270            if (artifactPredicate.isWithoutChecksum(artifact) || artifactPredicate.isChecksum(artifact)) {
271                return Collections.emptyList();
272            }
273            return getChecksumLocations(location, upload);
274        }
275
276        @Override
277        public List<ChecksumLocation> getChecksumLocations(Metadata metadata, boolean upload, URI location) {
278            return getChecksumLocations(location, upload);
279        }
280
281        private List<ChecksumLocation> getChecksumLocations(URI location, boolean upload) {
282            List<ChecksumAlgorithmFactory> checksumAlgorithmFactories =
283                    upload ? configuredUploadChecksumAlgorithms : configuredDownloadChecksumAlgorithms;
284            List<ChecksumLocation> checksumLocations = new ArrayList<>(checksumAlgorithmFactories.size());
285            for (ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories) {
286                checksumLocations.add(ChecksumLocation.forLocation(location, checksumAlgorithmFactory));
287            }
288            return checksumLocations;
289        }
290    }
291}