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.ConfigurationProperties; 035import org.eclipse.aether.RepositorySystemSession; 036import org.eclipse.aether.artifact.Artifact; 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 private static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_LAYOUT + NAME + "."; 057 058 /** 059 * Comma-separated list of checksum algorithms with which checksums are validated (downloaded) and generated 060 * (uploaded) with this layout. Resolver by default supports following algorithms: MD5, SHA-1, SHA-256 and 061 * SHA-512. New algorithms can be added by implementing ChecksumAlgorithmFactory component. 062 * 063 * @since 1.8.0 064 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 065 * @configurationType {@link java.lang.String} 066 * @configurationDefaultValue {@link #DEFAULT_CHECKSUMS_ALGORITHMS} 067 */ 068 public static final String CONFIG_PROP_CHECKSUMS_ALGORITHMS = CONFIG_PROPS_PREFIX + "checksumAlgorithms"; 069 070 public static final String DEFAULT_CHECKSUMS_ALGORITHMS = "SHA-1,MD5"; 071 072 /** 073 * Comma-separated list of extensions with leading dot (example ".asc") that should have checksums omitted. 074 * These are applied to sub-artifacts only. Note: to achieve 1.7.x aether.checksums.forSignature=true behaviour, 075 * pass empty string as value for this property. 076 * 077 * @since 1.8.0 078 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 079 * @configurationType {@link java.lang.String} 080 * @configurationDefaultValue {@link #DEFAULT_OMIT_CHECKSUMS_FOR_EXTENSIONS} 081 */ 082 public static final String CONFIG_PROP_OMIT_CHECKSUMS_FOR_EXTENSIONS = 083 CONFIG_PROPS_PREFIX + "omitChecksumsForExtensions"; 084 085 public static final String DEFAULT_OMIT_CHECKSUMS_FOR_EXTENSIONS = ".asc,.sigstore"; 086 087 private float priority; 088 089 private final ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector; 090 091 public float getPriority() { 092 return priority; 093 } 094 095 @Inject 096 public Maven2RepositoryLayoutFactory(ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector) { 097 this.checksumAlgorithmFactorySelector = requireNonNull(checksumAlgorithmFactorySelector); 098 } 099 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}