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.checksum; 020 021import javax.inject.Inject; 022import javax.inject.Named; 023import javax.inject.Singleton; 024 025import java.io.IOException; 026import java.io.UncheckedIOException; 027import java.nio.file.Files; 028import java.nio.file.Path; 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032import java.util.function.Function; 033 034import org.eclipse.aether.RepositorySystemSession; 035import org.eclipse.aether.artifact.Artifact; 036import org.eclipse.aether.internal.impl.LocalPathComposer; 037import org.eclipse.aether.metadata.Metadata; 038import org.eclipse.aether.repository.ArtifactRepository; 039import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory; 040import org.eclipse.aether.spi.io.ChecksumProcessor; 041import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory; 042import org.eclipse.aether.util.ConfigUtils; 043import org.eclipse.aether.util.PathUtils; 044 045import static java.util.Objects.requireNonNull; 046 047/** 048 * Sparse file {@link FileTrustedChecksumsSourceSupport} implementation that use specified directory as base 049 * directory, where it expects artifacts checksums on standard Maven2 "local" layout. This implementation uses Artifact 050 * coordinates solely to form path from basedir, pretty much as Maven local repository does. Metadata checksums are 051 * covered as well, on the same layout, with the metadata path composed from the origin repository key (for example 052 * {@code g/a/v/maven-metadata-central.xml.sha1}). 053 * <p> 054 * The source by default is "origin aware", it will factor in origin repository ID as well into base directory name 055 * (for example ".checksums/central/..."). 056 * <p> 057 * The checksums files are directly loaded from disk, so in-flight file changes during lifecycle of session are picked 058 * up. This implementation can be simultaneously used to lookup and also write checksums. The written checksums 059 * will become visible across all sessions right after the moment they were written. 060 * <p> 061 * The name of this implementation is "sparseDirectory". 062 * 063 * @see LocalPathComposer 064 * @since 1.9.0 065 */ 066@Singleton 067@Named(SparseDirectoryTrustedChecksumsSource.NAME) 068public final class SparseDirectoryTrustedChecksumsSource extends FileTrustedChecksumsSourceSupport { 069 public static final String NAME = "sparseDirectory"; 070 071 private static final String CONFIG_PROPS_PREFIX = 072 FileTrustedChecksumsSourceSupport.CONFIG_PROPS_PREFIX + NAME + "."; 073 074 /** 075 * Is checksum source enabled? 076 * 077 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 078 * @configurationType {@link java.lang.Boolean} 079 * @configurationDefaultValue false 080 */ 081 public static final String CONFIG_PROP_ENABLED = FileTrustedChecksumsSourceSupport.CONFIG_PROPS_PREFIX + NAME; 082 083 /** 084 * The basedir where checksums are. If relative, is resolved from local repository root. 085 * 086 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 087 * @configurationType {@link java.lang.String} 088 * @configurationDefaultValue {@link #LOCAL_REPO_PREFIX_DIR} 089 */ 090 public static final String CONFIG_PROP_BASEDIR = CONFIG_PROPS_PREFIX + "basedir"; 091 092 public static final String LOCAL_REPO_PREFIX_DIR = ".checksums"; 093 094 /** 095 * Is source origin aware? 096 * 097 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 098 * @configurationType {@link java.lang.Boolean} 099 * @configurationDefaultValue true 100 */ 101 public static final String CONFIG_PROP_ORIGIN_AWARE = CONFIG_PROPS_PREFIX + "originAware"; 102 103 private final ChecksumProcessor checksumProcessor; 104 105 private final LocalPathComposer localPathComposer; 106 107 @Inject 108 public SparseDirectoryTrustedChecksumsSource( 109 RepositoryKeyFunctionFactory repositoryKeyFunctionFactory, 110 ChecksumProcessor checksumProcessor, 111 LocalPathComposer localPathComposer) { 112 super(repositoryKeyFunctionFactory); 113 this.checksumProcessor = requireNonNull(checksumProcessor); 114 this.localPathComposer = requireNonNull(localPathComposer); 115 } 116 117 @Override 118 protected boolean isEnabled(RepositorySystemSession session) { 119 return ConfigUtils.getBoolean(session, false, CONFIG_PROP_ENABLED); 120 } 121 122 private boolean isOriginAware(RepositorySystemSession session) { 123 return ConfigUtils.getBoolean(session, true, CONFIG_PROP_ORIGIN_AWARE); 124 } 125 126 @Override 127 protected Map<String, String> doGetTrustedArtifactChecksums( 128 RepositorySystemSession session, 129 Artifact artifact, 130 ArtifactRepository artifactRepository, 131 List<ChecksumAlgorithmFactory> checksumAlgorithmFactories) { 132 final boolean originAware = isOriginAware(session); 133 final HashMap<String, String> checksums = new HashMap<>(); 134 Path basedir = getBasedir(session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, false); 135 if (Files.isDirectory(basedir)) { 136 for (ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories) { 137 Path checksumFilePath = null; 138 for (String repoKey : repositoryKey(session, artifactRepository)) { 139 Path checksumPath = basedir.resolve( 140 calculateArtifactPath(originAware, artifact, repoKey, checksumAlgorithmFactory)); 141 142 if (Files.isRegularFile(checksumPath)) { 143 checksumFilePath = checksumPath; 144 break; 145 } 146 } 147 148 if (checksumFilePath != null) { 149 try { 150 String checksum = checksumProcessor.readChecksum(checksumFilePath); 151 if (checksum != null) { 152 checksums.putIfAbsent(checksumAlgorithmFactory.getName(), checksum); 153 } 154 } catch (IOException e) { 155 // unexpected, log 156 logger.warn( 157 "Could not read artifact '{}' trusted checksum on path '{}'", 158 artifact, 159 checksumFilePath, 160 e); 161 throw new UncheckedIOException(e); 162 } 163 } 164 } 165 } 166 return checksums; 167 } 168 169 @Override 170 protected Map<String, String> doGetTrustedMetadataChecksums( 171 RepositorySystemSession session, 172 Metadata metadata, 173 ArtifactRepository artifactRepository, 174 List<ChecksumAlgorithmFactory> checksumAlgorithmFactories) { 175 final boolean originAware = isOriginAware(session); 176 final HashMap<String, String> checksums = new HashMap<>(); 177 Path basedir = getBasedir(session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, false); 178 if (Files.isDirectory(basedir)) { 179 for (ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories) { 180 Path checksumFilePath = null; 181 for (String repoKey : repositoryKey(session, artifactRepository)) { 182 Path checksumPath = basedir.resolve( 183 calculateMetadataPath(originAware, metadata, repoKey, checksumAlgorithmFactory)); 184 185 if (Files.isRegularFile(checksumPath)) { 186 checksumFilePath = checksumPath; 187 break; 188 } 189 } 190 191 if (checksumFilePath != null) { 192 try { 193 String checksum = checksumProcessor.readChecksum(checksumFilePath); 194 if (checksum != null) { 195 checksums.putIfAbsent(checksumAlgorithmFactory.getName(), checksum); 196 } 197 } catch (IOException e) { 198 // unexpected, log 199 logger.warn( 200 "Could not read metadata '{}' trusted checksum on path '{}'", 201 metadata, 202 checksumFilePath, 203 e); 204 throw new UncheckedIOException(e); 205 } 206 } 207 } 208 } 209 return checksums; 210 } 211 212 @Override 213 protected Writer doGetTrustedArtifactChecksumsWriter(RepositorySystemSession session) { 214 return new SparseDirectoryWriter( 215 getBasedir(session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, true), 216 isOriginAware(session), 217 r -> repositoryKey(session, r).get(0)); 218 } 219 220 private String calculateArtifactPath( 221 boolean originAware, 222 Artifact artifact, 223 String safeRepositoryId, 224 ChecksumAlgorithmFactory checksumAlgorithmFactory) { 225 String path = localPathComposer.getPathForArtifact(artifact, false) + "." 226 + checksumAlgorithmFactory.getFileExtension(); 227 if (originAware) { 228 // defense in depth: the repository key is spliced into a path under the checksums basedir, 229 // so it must be a safe path segment 230 PathUtils.validatePathComponent(safeRepositoryId, "repository key"); 231 path = safeRepositoryId + "/" + path; 232 } 233 return path; 234 } 235 236 private String calculateMetadataPath( 237 boolean originAware, 238 Metadata metadata, 239 String safeRepositoryId, 240 ChecksumAlgorithmFactory checksumAlgorithmFactory) { 241 String path = localPathComposer.getPathForMetadata(metadata, safeRepositoryId) + "." 242 + checksumAlgorithmFactory.getFileExtension(); 243 if (originAware) { 244 // defense in depth: same guard as for artifact paths, the repository key is spliced into a directory 245 PathUtils.validatePathComponent(safeRepositoryId, "repository key"); 246 path = safeRepositoryId + "/" + path; 247 } 248 return path; 249 } 250 251 private class SparseDirectoryWriter implements Writer { 252 private final Path basedir; 253 254 private final boolean originAware; 255 256 private final Function<ArtifactRepository, String> idToPathSegmentFunction; 257 258 private SparseDirectoryWriter( 259 Path basedir, boolean originAware, Function<ArtifactRepository, String> idToPathSegmentFunction) { 260 this.basedir = basedir; 261 this.originAware = originAware; 262 this.idToPathSegmentFunction = idToPathSegmentFunction; 263 } 264 265 @Override 266 public void addTrustedArtifactChecksums( 267 Artifact artifact, 268 ArtifactRepository artifactRepository, 269 List<ChecksumAlgorithmFactory> checksumAlgorithmFactories, 270 Map<String, String> trustedArtifactChecksums) 271 throws IOException { 272 for (ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories) { 273 Path checksumPath = basedir.resolve(calculateArtifactPath( 274 originAware, 275 artifact, 276 idToPathSegmentFunction.apply(artifactRepository), 277 checksumAlgorithmFactory)); 278 String checksum = requireNonNull(trustedArtifactChecksums.get(checksumAlgorithmFactory.getName())); 279 checksumProcessor.writeChecksum(checksumPath, checksum); 280 } 281 } 282 283 @Override 284 public void addTrustedMetadataChecksums( 285 Metadata metadata, 286 ArtifactRepository artifactRepository, 287 List<ChecksumAlgorithmFactory> checksumAlgorithmFactories, 288 Map<String, String> trustedMetadataChecksums) 289 throws IOException { 290 for (ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories) { 291 Path checksumPath = basedir.resolve(calculateMetadataPath( 292 originAware, 293 metadata, 294 idToPathSegmentFunction.apply(artifactRepository), 295 checksumAlgorithmFactory)); 296 String checksum = requireNonNull(trustedMetadataChecksums.get(checksumAlgorithmFactory.getName())); 297 checksumProcessor.writeChecksum(checksumPath, checksum); 298 } 299 } 300 } 301}