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.Arrays;
029import java.util.Collections;
030import java.util.List;
031import java.util.Set;
032import java.util.stream.Collectors;
033
034import org.eclipse.aether.RepositorySystemSession;
035import org.eclipse.aether.artifact.Artifact;
036import org.eclipse.aether.internal.impl.checksum.DefaultChecksumAlgorithmFactorySelector;
037import org.eclipse.aether.metadata.Metadata;
038import org.eclipse.aether.repository.RemoteRepository;
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    public static final String CONFIG_PROP_CHECKSUMS_ALGORITHMS = "aether.checksums.algorithms";
057
058    private static final String DEFAULT_CHECKSUMS_ALGORITHMS = "SHA-1,MD5";
059
060    public static final String CONFIG_PROP_OMIT_CHECKSUMS_FOR_EXTENSIONS =
061            "aether.checksums.omitChecksumsForExtensions";
062
063    private static final String DEFAULT_OMIT_CHECKSUMS_FOR_EXTENSIONS = ".asc,.sigstore";
064
065    private float priority;
066
067    private final ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector;
068
069    public float getPriority() {
070        return priority;
071    }
072
073    /**
074     * Service locator ctor.
075     */
076    @Deprecated
077    public Maven2RepositoryLayoutFactory() {
078        this(new DefaultChecksumAlgorithmFactorySelector());
079    }
080
081    @Inject
082    public Maven2RepositoryLayoutFactory(ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector) {
083        this.checksumAlgorithmFactorySelector = requireNonNull(checksumAlgorithmFactorySelector);
084    }
085
086    /**
087     * Sets the priority of this component.
088     *
089     * @param priority The priority.
090     * @return This component for chaining, never {@code null}.
091     */
092    public Maven2RepositoryLayoutFactory setPriority(float priority) {
093        this.priority = priority;
094        return this;
095    }
096
097    public RepositoryLayout newInstance(RepositorySystemSession session, RemoteRepository repository)
098            throws NoRepositoryLayoutException {
099        requireNonNull(session, "session cannot be null");
100        requireNonNull(repository, "repository cannot be null");
101        if (!"default".equals(repository.getContentType())) {
102            throw new NoRepositoryLayoutException(repository);
103        }
104
105        List<ChecksumAlgorithmFactory> checksumsAlgorithms = checksumAlgorithmFactorySelector.selectList(
106                ConfigUtils.parseCommaSeparatedUniqueNames(ConfigUtils.getString(
107                        session,
108                        DEFAULT_CHECKSUMS_ALGORITHMS,
109                        CONFIG_PROP_CHECKSUMS_ALGORITHMS + "." + repository.getId(),
110                        CONFIG_PROP_CHECKSUMS_ALGORITHMS)));
111
112        // ensure uniqueness of (potentially user set) extension list
113        Set<String> omitChecksumsForExtensions = Arrays.stream(ConfigUtils.getString(
114                                session,
115                                DEFAULT_OMIT_CHECKSUMS_FOR_EXTENSIONS,
116                                CONFIG_PROP_OMIT_CHECKSUMS_FOR_EXTENSIONS)
117                        .split(","))
118                .filter(s -> s != null && !s.trim().isEmpty())
119                .collect(Collectors.toSet());
120
121        // validation: enforce that all strings in this set are having leading dot
122        if (omitChecksumsForExtensions.stream().anyMatch(s -> !s.startsWith("."))) {
123            throw new IllegalArgumentException(String.format(
124                    "The configuration %s contains illegal values: %s (all entries must start with '.' (dot))",
125                    CONFIG_PROP_OMIT_CHECKSUMS_FOR_EXTENSIONS, omitChecksumsForExtensions));
126        }
127
128        return new Maven2RepositoryLayout(
129                checksumAlgorithmFactorySelector, checksumsAlgorithms, omitChecksumsForExtensions);
130    }
131
132    private static class Maven2RepositoryLayout implements RepositoryLayout {
133        private final ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector;
134
135        private final List<ChecksumAlgorithmFactory> configuredChecksumAlgorithms;
136
137        private final Set<String> extensionsWithoutChecksums;
138
139        private Maven2RepositoryLayout(
140                ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector,
141                List<ChecksumAlgorithmFactory> configuredChecksumAlgorithms,
142                Set<String> extensionsWithoutChecksums) {
143            this.checksumAlgorithmFactorySelector = requireNonNull(checksumAlgorithmFactorySelector);
144            this.configuredChecksumAlgorithms = Collections.unmodifiableList(configuredChecksumAlgorithms);
145            this.extensionsWithoutChecksums = requireNonNull(extensionsWithoutChecksums);
146        }
147
148        private URI toUri(String path) {
149            try {
150                return new URI(null, null, path, null);
151            } catch (URISyntaxException e) {
152                throw new IllegalStateException(e);
153            }
154        }
155
156        @Override
157        public List<ChecksumAlgorithmFactory> getChecksumAlgorithmFactories() {
158            return configuredChecksumAlgorithms;
159        }
160
161        @Override
162        public boolean hasChecksums(Artifact artifact) {
163            String artifactExtension = artifact.getExtension(); // ie. pom.asc
164            for (String extensionWithoutChecksums : extensionsWithoutChecksums) {
165                if (artifactExtension.endsWith(extensionWithoutChecksums)) {
166                    return false;
167                }
168            }
169            return true;
170        }
171
172        @Override
173        public URI getLocation(Artifact artifact, boolean upload) {
174            StringBuilder path = new StringBuilder(128);
175
176            path.append(artifact.getGroupId().replace('.', '/')).append('/');
177
178            path.append(artifact.getArtifactId()).append('/');
179
180            path.append(artifact.getBaseVersion()).append('/');
181
182            path.append(artifact.getArtifactId()).append('-').append(artifact.getVersion());
183
184            if (artifact.getClassifier().length() > 0) {
185                path.append('-').append(artifact.getClassifier());
186            }
187
188            if (artifact.getExtension().length() > 0) {
189                path.append('.').append(artifact.getExtension());
190            }
191
192            return toUri(path.toString());
193        }
194
195        @Override
196        public URI getLocation(Metadata metadata, boolean upload) {
197            StringBuilder path = new StringBuilder(128);
198
199            if (metadata.getGroupId().length() > 0) {
200                path.append(metadata.getGroupId().replace('.', '/')).append('/');
201
202                if (metadata.getArtifactId().length() > 0) {
203                    path.append(metadata.getArtifactId()).append('/');
204
205                    if (metadata.getVersion().length() > 0) {
206                        path.append(metadata.getVersion()).append('/');
207                    }
208                }
209            }
210
211            path.append(metadata.getType());
212
213            return toUri(path.toString());
214        }
215
216        @Override
217        public List<ChecksumLocation> getChecksumLocations(Artifact artifact, boolean upload, URI location) {
218            if (!hasChecksums(artifact) || isChecksum(artifact.getExtension())) {
219                return Collections.emptyList();
220            }
221            return getChecksumLocations(location);
222        }
223
224        @Override
225        public List<ChecksumLocation> getChecksumLocations(Metadata metadata, boolean upload, URI location) {
226            return getChecksumLocations(location);
227        }
228
229        private List<ChecksumLocation> getChecksumLocations(URI location) {
230            List<ChecksumLocation> checksumLocations = new ArrayList<>(configuredChecksumAlgorithms.size());
231            for (ChecksumAlgorithmFactory checksumAlgorithmFactory : configuredChecksumAlgorithms) {
232                checksumLocations.add(ChecksumLocation.forLocation(location, checksumAlgorithmFactory));
233            }
234            return checksumLocations;
235        }
236
237        private boolean isChecksum(String extension) {
238            return checksumAlgorithmFactorySelector.isChecksumExtension(extension);
239        }
240    }
241}