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.List;
030
031import org.eclipse.aether.ConfigurationProperties;
032import org.eclipse.aether.RepositorySystemSession;
033import org.eclipse.aether.artifact.Artifact;
034import org.eclipse.aether.metadata.Metadata;
035import org.eclipse.aether.repository.RemoteRepository;
036import org.eclipse.aether.spi.artifact.ArtifactPredicate;
037import org.eclipse.aether.spi.artifact.ArtifactPredicateFactory;
038import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
039import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactorySelector;
040import org.eclipse.aether.spi.connector.layout.RepositoryLayout;
041import org.eclipse.aether.spi.connector.layout.RepositoryLayoutFactory;
042import org.eclipse.aether.transfer.NoRepositoryLayoutException;
043import org.eclipse.aether.util.ConfigUtils;
044
045import static java.util.Objects.requireNonNull;
046
047/**
048 * Provides a Maven-2 repository layout for repositories with content type {@code "default"}.
049 */
050@Singleton
051@Named(Maven2RepositoryLayoutFactory.NAME)
052public final class Maven2RepositoryLayoutFactory implements RepositoryLayoutFactory {
053    public static final String NAME = "maven2";
054
055    private static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_LAYOUT + NAME + ".";
056
057    /**
058     * Comma-separated list of checksum algorithms with which checksums are validated (downloaded) and generated
059     * (uploaded) with this layout. Resolver by default supports following algorithms: MD5, SHA-1, SHA-256 and
060     * SHA-512. New algorithms can be added by implementing ChecksumAlgorithmFactory component.
061     *
062     * @since 1.8.0
063     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
064     * @configurationType {@link java.lang.String}
065     * @configurationDefaultValue {@link #DEFAULT_CHECKSUMS_ALGORITHMS}
066     * @configurationRepoIdSuffix Yes
067     */
068    public static final String CONFIG_PROP_CHECKSUMS_ALGORITHMS = CONFIG_PROPS_PREFIX + "checksumAlgorithms";
069
070    public static final String DEFAULT_CHECKSUMS_ALGORITHMS = "SHA-1,MD5";
071
072    private float priority;
073
074    private final ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector;
075
076    private final ArtifactPredicateFactory artifactPredicateFactory;
077
078    public float getPriority() {
079        return priority;
080    }
081
082    @Inject
083    public Maven2RepositoryLayoutFactory(
084            ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector,
085            ArtifactPredicateFactory artifactPredicateFactory) {
086        this.checksumAlgorithmFactorySelector = requireNonNull(checksumAlgorithmFactorySelector);
087        this.artifactPredicateFactory = requireNonNull(artifactPredicateFactory);
088    }
089
090    /**
091     * Sets the priority of this component.
092     *
093     * @param priority The priority.
094     * @return This component for chaining, never {@code null}.
095     */
096    public Maven2RepositoryLayoutFactory setPriority(float priority) {
097        this.priority = priority;
098        return this;
099    }
100
101    public RepositoryLayout newInstance(RepositorySystemSession session, RemoteRepository repository)
102            throws NoRepositoryLayoutException {
103        requireNonNull(session, "session cannot be null");
104        requireNonNull(repository, "repository cannot be null");
105        if (!"default".equals(repository.getContentType())) {
106            throw new NoRepositoryLayoutException(repository);
107        }
108
109        List<ChecksumAlgorithmFactory> checksumsAlgorithms = checksumAlgorithmFactorySelector.selectList(
110                ConfigUtils.parseCommaSeparatedUniqueNames(ConfigUtils.getString(
111                        session,
112                        DEFAULT_CHECKSUMS_ALGORITHMS,
113                        CONFIG_PROP_CHECKSUMS_ALGORITHMS + "." + repository.getId(),
114                        CONFIG_PROP_CHECKSUMS_ALGORITHMS,
115                        // MRESOLVER-701: support legacy properties for simpler transitioning
116                        "aether.checksums.algorithms",
117                        "aether.checksums.algorithms." + repository.getId())));
118
119        return new Maven2RepositoryLayout(checksumsAlgorithms, artifactPredicateFactory.newInstance(session));
120    }
121
122    private static class Maven2RepositoryLayout implements RepositoryLayout {
123        private final List<ChecksumAlgorithmFactory> configuredChecksumAlgorithms;
124        private final ArtifactPredicate artifactPredicate;
125
126        private Maven2RepositoryLayout(
127                List<ChecksumAlgorithmFactory> configuredChecksumAlgorithms, ArtifactPredicate artifactPredicate) {
128            this.configuredChecksumAlgorithms = Collections.unmodifiableList(configuredChecksumAlgorithms);
129            this.artifactPredicate = requireNonNull(artifactPredicate);
130        }
131
132        private URI toUri(String path) {
133            try {
134                return new URI(null, null, path, null);
135            } catch (URISyntaxException e) {
136                throw new IllegalStateException(e);
137            }
138        }
139
140        @Override
141        public List<ChecksumAlgorithmFactory> getChecksumAlgorithmFactories() {
142            return configuredChecksumAlgorithms;
143        }
144
145        @Override
146        public boolean hasChecksums(Artifact artifact) {
147            return !artifactPredicate.isWithoutChecksum(artifact);
148        }
149
150        @Override
151        public URI getLocation(Artifact artifact, boolean upload) {
152            StringBuilder path = new StringBuilder(128);
153
154            path.append(artifact.getGroupId().replace('.', '/')).append('/');
155
156            path.append(artifact.getArtifactId()).append('/');
157
158            path.append(artifact.getBaseVersion()).append('/');
159
160            path.append(artifact.getArtifactId()).append('-').append(artifact.getVersion());
161
162            if (!artifact.getClassifier().isEmpty()) {
163                path.append('-').append(artifact.getClassifier());
164            }
165
166            if (!artifact.getExtension().isEmpty()) {
167                path.append('.').append(artifact.getExtension());
168            }
169
170            return toUri(path.toString());
171        }
172
173        @Override
174        public URI getLocation(Metadata metadata, boolean upload) {
175            StringBuilder path = new StringBuilder(128);
176
177            if (!metadata.getGroupId().isEmpty()) {
178                path.append(metadata.getGroupId().replace('.', '/')).append('/');
179
180                if (!metadata.getArtifactId().isEmpty()) {
181                    path.append(metadata.getArtifactId()).append('/');
182
183                    if (!metadata.getVersion().isEmpty()) {
184                        path.append(metadata.getVersion()).append('/');
185                    }
186                }
187            }
188
189            path.append(metadata.getType());
190
191            return toUri(path.toString());
192        }
193
194        @Override
195        public List<ChecksumLocation> getChecksumLocations(Artifact artifact, boolean upload, URI location) {
196            if (artifactPredicate.isWithoutChecksum(artifact) || artifactPredicate.isChecksum(artifact)) {
197                return Collections.emptyList();
198            }
199            return getChecksumLocations(location);
200        }
201
202        @Override
203        public List<ChecksumLocation> getChecksumLocations(Metadata metadata, boolean upload, URI location) {
204            return getChecksumLocations(location);
205        }
206
207        private List<ChecksumLocation> getChecksumLocations(URI location) {
208            List<ChecksumLocation> checksumLocations = new ArrayList<>(configuredChecksumAlgorithms.size());
209            for (ChecksumAlgorithmFactory checksumAlgorithmFactory : configuredChecksumAlgorithms) {
210                checksumLocations.add(ChecksumLocation.forLocation(location, checksumAlgorithmFactory));
211            }
212            return checksumLocations;
213        }
214    }
215}