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 checksumsAlgorithms, 146 omitChecksumsForExtensions 147 ); 148 } 149 150 private static class Maven2RepositoryLayout 151 implements RepositoryLayout 152 { 153 154 private final List<ChecksumAlgorithmFactory> checksumAlgorithms; 155 156 private final Set<String> extensionsWithoutChecksums; 157 158 private Maven2RepositoryLayout( List<ChecksumAlgorithmFactory> checksumAlgorithms, 159 Set<String> extensionsWithoutChecksums ) 160 { 161 this.checksumAlgorithms = Collections.unmodifiableList( checksumAlgorithms ); 162 this.extensionsWithoutChecksums = requireNonNull( extensionsWithoutChecksums ); 163 } 164 165 private URI toUri( String path ) 166 { 167 try 168 { 169 return new URI( null, null, path, null ); 170 } 171 catch ( URISyntaxException e ) 172 { 173 throw new IllegalStateException( e ); 174 } 175 } 176 177 @Override 178 public List<ChecksumAlgorithmFactory> getChecksumAlgorithmFactories() 179 { 180 return checksumAlgorithms; 181 } 182 183 @Override 184 public boolean hasChecksums( Artifact artifact ) 185 { 186 String artifactExtension = artifact.getExtension(); // ie. pom.asc 187 for ( String extensionWithoutChecksums : extensionsWithoutChecksums ) 188 { 189 if ( artifactExtension.endsWith( extensionWithoutChecksums ) ) 190 { 191 return false; 192 } 193 } 194 return true; 195 } 196 197 @Override 198 public URI getLocation( Artifact artifact, boolean upload ) 199 { 200 StringBuilder path = new StringBuilder( 128 ); 201 202 path.append( artifact.getGroupId().replace( '.', '/' ) ).append( '/' ); 203 204 path.append( artifact.getArtifactId() ).append( '/' ); 205 206 path.append( artifact.getBaseVersion() ).append( '/' ); 207 208 path.append( artifact.getArtifactId() ).append( '-' ).append( artifact.getVersion() ); 209 210 if ( artifact.getClassifier().length() > 0 ) 211 { 212 path.append( '-' ).append( artifact.getClassifier() ); 213 } 214 215 if ( artifact.getExtension().length() > 0 ) 216 { 217 path.append( '.' ).append( artifact.getExtension() ); 218 } 219 220 return toUri( path.toString() ); 221 } 222 223 @Override 224 public URI getLocation( Metadata metadata, boolean upload ) 225 { 226 StringBuilder path = new StringBuilder( 128 ); 227 228 if ( metadata.getGroupId().length() > 0 ) 229 { 230 path.append( metadata.getGroupId().replace( '.', '/' ) ).append( '/' ); 231 232 if ( metadata.getArtifactId().length() > 0 ) 233 { 234 path.append( metadata.getArtifactId() ).append( '/' ); 235 236 if ( metadata.getVersion().length() > 0 ) 237 { 238 path.append( metadata.getVersion() ).append( '/' ); 239 } 240 } 241 } 242 243 path.append( metadata.getType() ); 244 245 return toUri( path.toString() ); 246 } 247 248 @Override 249 public List<ChecksumLocation> getChecksumLocations( Artifact artifact, boolean upload, URI location ) 250 { 251 if ( !hasChecksums( artifact ) || isChecksum( artifact.getExtension() ) ) 252 { 253 return Collections.emptyList(); 254 } 255 return getChecksumLocations( location ); 256 } 257 258 @Override 259 public List<ChecksumLocation> getChecksumLocations( Metadata metadata, boolean upload, URI location ) 260 { 261 return getChecksumLocations( location ); 262 } 263 264 private List<ChecksumLocation> getChecksumLocations( URI location ) 265 { 266 List<ChecksumLocation> checksumLocations = new ArrayList<>( checksumAlgorithms.size() ); 267 for ( ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithms ) 268 { 269 checksumLocations.add( ChecksumLocation.forLocation( location, checksumAlgorithmFactory ) ); 270 } 271 return checksumLocations; 272 } 273 274 private boolean isChecksum( String extension ) 275 { 276 return checksumAlgorithms.stream().anyMatch( a -> extension.endsWith( "." + a.getFileExtension() ) ); 277 } 278 } 279}