001package org.eclipse.aether.internal.impl; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.net.URI; 023import java.net.URISyntaxException; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.Collections; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Set; 030import java.util.stream.Collectors; 031 032import javax.inject.Inject; 033import javax.inject.Named; 034import javax.inject.Singleton; 035 036import org.eclipse.aether.RepositorySystemSession; 037import org.eclipse.aether.artifact.Artifact; 038import org.eclipse.aether.internal.impl.checksum.DefaultChecksumAlgorithmFactorySelector; 039import org.eclipse.aether.metadata.Metadata; 040import org.eclipse.aether.repository.RemoteRepository; 041import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory; 042import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactorySelector; 043import org.eclipse.aether.spi.connector.layout.RepositoryLayout; 044import org.eclipse.aether.spi.connector.layout.RepositoryLayoutFactory; 045import org.eclipse.aether.transfer.NoRepositoryLayoutException; 046import org.eclipse.aether.util.ConfigUtils; 047 048import static java.util.Objects.requireNonNull; 049 050/** 051 * Provides a Maven-2 repository layout for repositories with content type {@code "default"}. 052 */ 053@Singleton 054@Named( "maven2" ) 055public final class Maven2RepositoryLayoutFactory 056 implements RepositoryLayoutFactory 057{ 058 059 public static final String CONFIG_PROP_CHECKSUMS_ALGORITHMS = "aether.checksums.algorithms"; 060 061 private static final String DEFAULT_CHECKSUMS_ALGORITHMS = "SHA-1,MD5"; 062 063 public static final String CONFIG_PROP_OMIT_CHECKSUMS_FOR_EXTENSIONS = 064 "aether.checksums.omitChecksumsForExtensions"; 065 066 private static final String DEFAULT_OMIT_CHECKSUMS_FOR_EXTENSIONS = ".asc"; 067 068 private float priority; 069 070 private final ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector; 071 072 public float getPriority() 073 { 074 return priority; 075 } 076 077 /** 078 * Service locator ctor. 079 */ 080 @Deprecated 081 public Maven2RepositoryLayoutFactory() 082 { 083 this( new DefaultChecksumAlgorithmFactorySelector() ); 084 } 085 086 @Inject 087 public Maven2RepositoryLayoutFactory( ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector ) 088 { 089 this.checksumAlgorithmFactorySelector = requireNonNull( checksumAlgorithmFactorySelector ); 090 } 091 092 /** 093 * Sets the priority of this component. 094 * 095 * @param priority The priority. 096 * @return This component for chaining, never {@code null}. 097 */ 098 public Maven2RepositoryLayoutFactory setPriority( float priority ) 099 { 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}