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