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