View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.internal.impl;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.net.URI;
26  import java.net.URISyntaxException;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Collections;
30  import java.util.List;
31  import java.util.Set;
32  import java.util.stream.Collectors;
33  
34  import org.eclipse.aether.ConfigurationProperties;
35  import org.eclipse.aether.RepositorySystemSession;
36  import org.eclipse.aether.artifact.Artifact;
37  import org.eclipse.aether.metadata.Metadata;
38  import org.eclipse.aether.repository.RemoteRepository;
39  import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
40  import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactorySelector;
41  import org.eclipse.aether.spi.connector.layout.RepositoryLayout;
42  import org.eclipse.aether.spi.connector.layout.RepositoryLayoutFactory;
43  import org.eclipse.aether.transfer.NoRepositoryLayoutException;
44  import org.eclipse.aether.util.ConfigUtils;
45  
46  import static java.util.Objects.requireNonNull;
47  
48  /**
49   * Provides a Maven-2 repository layout for repositories with content type {@code "default"}.
50   */
51  @Singleton
52  @Named(Maven2RepositoryLayoutFactory.NAME)
53  public final class Maven2RepositoryLayoutFactory implements RepositoryLayoutFactory {
54      public static final String NAME = "maven2";
55  
56      private static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_LAYOUT + NAME + ".";
57  
58      /**
59       * Comma-separated list of checksum algorithms with which checksums are validated (downloaded) and generated
60       * (uploaded) with this layout. Resolver by default supports following algorithms: MD5, SHA-1, SHA-256 and
61       * SHA-512. New algorithms can be added by implementing ChecksumAlgorithmFactory component.
62       *
63       * @since 1.8.0
64       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
65       * @configurationType {@link java.lang.String}
66       * @configurationDefaultValue {@link #DEFAULT_CHECKSUMS_ALGORITHMS}
67       */
68      public static final String CONFIG_PROP_CHECKSUMS_ALGORITHMS = CONFIG_PROPS_PREFIX + "checksumAlgorithms";
69  
70      public static final String DEFAULT_CHECKSUMS_ALGORITHMS = "SHA-1,MD5";
71  
72      /**
73       * Comma-separated list of extensions with leading dot (example ".asc") that should have checksums omitted.
74       * These are applied to sub-artifacts only. Note: to achieve 1.7.x aether.checksums.forSignature=true behaviour,
75       * pass empty string as value for this property.
76       *
77       * @since 1.8.0
78       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
79       * @configurationType {@link java.lang.String}
80       * @configurationDefaultValue {@link #DEFAULT_OMIT_CHECKSUMS_FOR_EXTENSIONS}
81       */
82      public static final String CONFIG_PROP_OMIT_CHECKSUMS_FOR_EXTENSIONS =
83              CONFIG_PROPS_PREFIX + "omitChecksumsForExtensions";
84  
85      public static final String DEFAULT_OMIT_CHECKSUMS_FOR_EXTENSIONS = ".asc,.sigstore";
86  
87      private float priority;
88  
89      private final ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector;
90  
91      public float getPriority() {
92          return priority;
93      }
94  
95      @Inject
96      public Maven2RepositoryLayoutFactory(ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector) {
97          this.checksumAlgorithmFactorySelector = requireNonNull(checksumAlgorithmFactorySelector);
98      }
99  
100     /**
101      * Sets the priority of this component.
102      *
103      * @param priority The priority.
104      * @return This component for chaining, never {@code null}.
105      */
106     public Maven2RepositoryLayoutFactory setPriority(float priority) {
107         this.priority = priority;
108         return this;
109     }
110 
111     public RepositoryLayout newInstance(RepositorySystemSession session, RemoteRepository repository)
112             throws NoRepositoryLayoutException {
113         requireNonNull(session, "session cannot be null");
114         requireNonNull(repository, "repository cannot be null");
115         if (!"default".equals(repository.getContentType())) {
116             throw new NoRepositoryLayoutException(repository);
117         }
118 
119         List<ChecksumAlgorithmFactory> checksumsAlgorithms = checksumAlgorithmFactorySelector.selectList(
120                 ConfigUtils.parseCommaSeparatedUniqueNames(ConfigUtils.getString(
121                         session,
122                         DEFAULT_CHECKSUMS_ALGORITHMS,
123                         CONFIG_PROP_CHECKSUMS_ALGORITHMS + "." + repository.getId(),
124                         CONFIG_PROP_CHECKSUMS_ALGORITHMS)));
125 
126         // ensure uniqueness of (potentially user set) extension list
127         Set<String> omitChecksumsForExtensions = Arrays.stream(ConfigUtils.getString(
128                                 session,
129                                 DEFAULT_OMIT_CHECKSUMS_FOR_EXTENSIONS,
130                                 CONFIG_PROP_OMIT_CHECKSUMS_FOR_EXTENSIONS)
131                         .split(","))
132                 .filter(s -> s != null && !s.trim().isEmpty())
133                 .collect(Collectors.toSet());
134 
135         // validation: enforce that all strings in this set are having leading dot
136         if (omitChecksumsForExtensions.stream().anyMatch(s -> !s.startsWith("."))) {
137             throw new IllegalArgumentException(String.format(
138                     "The configuration %s contains illegal values: %s (all entries must start with '.' (dot))",
139                     CONFIG_PROP_OMIT_CHECKSUMS_FOR_EXTENSIONS, omitChecksumsForExtensions));
140         }
141 
142         return new Maven2RepositoryLayout(
143                 checksumAlgorithmFactorySelector, checksumsAlgorithms, omitChecksumsForExtensions);
144     }
145 
146     private static class Maven2RepositoryLayout implements RepositoryLayout {
147         private final ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector;
148 
149         private final List<ChecksumAlgorithmFactory> configuredChecksumAlgorithms;
150 
151         private final Set<String> extensionsWithoutChecksums;
152 
153         private Maven2RepositoryLayout(
154                 ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector,
155                 List<ChecksumAlgorithmFactory> configuredChecksumAlgorithms,
156                 Set<String> extensionsWithoutChecksums) {
157             this.checksumAlgorithmFactorySelector = requireNonNull(checksumAlgorithmFactorySelector);
158             this.configuredChecksumAlgorithms = Collections.unmodifiableList(configuredChecksumAlgorithms);
159             this.extensionsWithoutChecksums = requireNonNull(extensionsWithoutChecksums);
160         }
161 
162         private URI toUri(String path) {
163             try {
164                 return new URI(null, null, path, null);
165             } catch (URISyntaxException e) {
166                 throw new IllegalStateException(e);
167             }
168         }
169 
170         @Override
171         public List<ChecksumAlgorithmFactory> getChecksumAlgorithmFactories() {
172             return configuredChecksumAlgorithms;
173         }
174 
175         @Override
176         public boolean hasChecksums(Artifact artifact) {
177             String artifactExtension = artifact.getExtension(); // ie. pom.asc
178             for (String extensionWithoutChecksums : extensionsWithoutChecksums) {
179                 if (artifactExtension.endsWith(extensionWithoutChecksums)) {
180                     return false;
181                 }
182             }
183             return true;
184         }
185 
186         @Override
187         public URI getLocation(Artifact artifact, boolean upload) {
188             StringBuilder path = new StringBuilder(128);
189 
190             path.append(artifact.getGroupId().replace('.', '/')).append('/');
191 
192             path.append(artifact.getArtifactId()).append('/');
193 
194             path.append(artifact.getBaseVersion()).append('/');
195 
196             path.append(artifact.getArtifactId()).append('-').append(artifact.getVersion());
197 
198             if (!artifact.getClassifier().isEmpty()) {
199                 path.append('-').append(artifact.getClassifier());
200             }
201 
202             if (!artifact.getExtension().isEmpty()) {
203                 path.append('.').append(artifact.getExtension());
204             }
205 
206             return toUri(path.toString());
207         }
208 
209         @Override
210         public URI getLocation(Metadata metadata, boolean upload) {
211             StringBuilder path = new StringBuilder(128);
212 
213             if (!metadata.getGroupId().isEmpty()) {
214                 path.append(metadata.getGroupId().replace('.', '/')).append('/');
215 
216                 if (!metadata.getArtifactId().isEmpty()) {
217                     path.append(metadata.getArtifactId()).append('/');
218 
219                     if (!metadata.getVersion().isEmpty()) {
220                         path.append(metadata.getVersion()).append('/');
221                     }
222                 }
223             }
224 
225             path.append(metadata.getType());
226 
227             return toUri(path.toString());
228         }
229 
230         @Override
231         public List<ChecksumLocation> getChecksumLocations(Artifact artifact, boolean upload, URI location) {
232             if (!hasChecksums(artifact) || isChecksum(artifact.getExtension())) {
233                 return Collections.emptyList();
234             }
235             return getChecksumLocations(location);
236         }
237 
238         @Override
239         public List<ChecksumLocation> getChecksumLocations(Metadata metadata, boolean upload, URI location) {
240             return getChecksumLocations(location);
241         }
242 
243         private List<ChecksumLocation> getChecksumLocations(URI location) {
244             List<ChecksumLocation> checksumLocations = new ArrayList<>(configuredChecksumAlgorithms.size());
245             for (ChecksumAlgorithmFactory checksumAlgorithmFactory : configuredChecksumAlgorithms) {
246                 checksumLocations.add(ChecksumLocation.forLocation(location, checksumAlgorithmFactory));
247             }
248             return checksumLocations;
249         }
250 
251         private boolean isChecksum(String extension) {
252             return checksumAlgorithmFactorySelector.isChecksumExtension(extension);
253         }
254     }
255 }