1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.internal.impl.checksum;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.BufferedReader;
26 import java.io.IOException;
27 import java.io.UncheckedIOException;
28 import java.nio.charset.StandardCharsets;
29 import java.nio.file.Files;
30 import java.nio.file.Path;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Objects;
36 import java.util.concurrent.ConcurrentHashMap;
37 import java.util.concurrent.atomic.AtomicBoolean;
38 import java.util.function.Function;
39 import java.util.stream.Collectors;
40
41 import org.eclipse.aether.MultiRuntimeException;
42 import org.eclipse.aether.RepositorySystemSession;
43 import org.eclipse.aether.artifact.Artifact;
44 import org.eclipse.aether.impl.RepositorySystemLifecycle;
45 import org.eclipse.aether.internal.impl.LocalPathComposer;
46 import org.eclipse.aether.repository.ArtifactRepository;
47 import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
48 import org.eclipse.aether.spi.io.PathProcessor;
49 import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory;
50 import org.eclipse.aether.util.ConfigUtils;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 import static java.util.Objects.requireNonNull;
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 @Singleton
91 @Named(SummaryFileTrustedChecksumsSource.NAME)
92 public final class SummaryFileTrustedChecksumsSource extends FileTrustedChecksumsSourceSupport {
93 public static final String NAME = "summaryFile";
94
95 private static final String CONFIG_PROPS_PREFIX =
96 FileTrustedChecksumsSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".";
97
98
99
100
101
102
103
104
105 public static final String CONFIG_PROP_ENABLED = FileTrustedChecksumsSourceSupport.CONFIG_PROPS_PREFIX + NAME;
106
107
108
109
110
111
112
113
114 public static final String CONFIG_PROP_BASEDIR = CONFIG_PROPS_PREFIX + "basedir";
115
116 public static final String LOCAL_REPO_PREFIX_DIR = ".checksums";
117
118
119
120
121
122
123
124
125 public static final String CONFIG_PROP_ORIGIN_AWARE = CONFIG_PROPS_PREFIX + "originAware";
126
127 public static final String CHECKSUMS_FILE_PREFIX = "checksums";
128
129 private static final Logger LOGGER = LoggerFactory.getLogger(SummaryFileTrustedChecksumsSource.class);
130
131 private final LocalPathComposer localPathComposer;
132
133 private final RepositorySystemLifecycle repositorySystemLifecycle;
134
135 private final PathProcessor pathProcessor;
136
137 private final ConcurrentHashMap<Path, ConcurrentHashMap<String, String>> checksums;
138
139 private final ConcurrentHashMap<Path, Boolean> changedChecksums;
140
141 private final AtomicBoolean onShutdownHandlerRegistered;
142
143 @Inject
144 public SummaryFileTrustedChecksumsSource(
145 RepositoryKeyFunctionFactory repoKeyFunctionFactory,
146 LocalPathComposer localPathComposer,
147 RepositorySystemLifecycle repositorySystemLifecycle,
148 PathProcessor pathProcessor) {
149 super(repoKeyFunctionFactory);
150 this.localPathComposer = requireNonNull(localPathComposer);
151 this.repositorySystemLifecycle = requireNonNull(repositorySystemLifecycle);
152 this.pathProcessor = requireNonNull(pathProcessor);
153 this.checksums = new ConcurrentHashMap<>();
154 this.changedChecksums = new ConcurrentHashMap<>();
155 this.onShutdownHandlerRegistered = new AtomicBoolean(false);
156 }
157
158 @Override
159 protected boolean isEnabled(RepositorySystemSession session) {
160 return ConfigUtils.getBoolean(session, false, CONFIG_PROP_ENABLED);
161 }
162
163 private boolean isOriginAware(RepositorySystemSession session) {
164 return ConfigUtils.getBoolean(session, true, CONFIG_PROP_ORIGIN_AWARE);
165 }
166
167 @Override
168 protected Map<String, String> doGetTrustedArtifactChecksums(
169 RepositorySystemSession session,
170 Artifact artifact,
171 ArtifactRepository artifactRepository,
172 List<ChecksumAlgorithmFactory> checksumAlgorithmFactories) {
173 final HashMap<String, String> result = new HashMap<>();
174 final Path basedir = getBasedir(session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, false);
175 if (Files.isDirectory(basedir)) {
176 final String artifactPath = localPathComposer.getPathForArtifact(artifact, false);
177 final boolean originAware = isOriginAware(session);
178 for (ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories) {
179 Path summaryFile = summaryFile(
180 basedir,
181 originAware,
182 repositoryKey(session, artifactRepository),
183 checksumAlgorithmFactory.getFileExtension());
184 ConcurrentHashMap<String, String> algorithmChecksums =
185 checksums.computeIfAbsent(summaryFile, f -> loadProvidedChecksums(summaryFile));
186 String checksum = algorithmChecksums.get(artifactPath);
187 if (checksum != null) {
188 result.put(checksumAlgorithmFactory.getName(), checksum);
189 }
190 }
191 }
192 return result;
193 }
194
195 @Override
196 protected Writer doGetTrustedArtifactChecksumsWriter(RepositorySystemSession session) {
197 if (onShutdownHandlerRegistered.compareAndSet(false, true)) {
198 repositorySystemLifecycle.addOnSystemEndedHandler(this::saveRecordedLines);
199 }
200 return new SummaryFileWriter(
201 checksums,
202 getBasedir(session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, true),
203 isOriginAware(session),
204 r -> repositoryKey(session, r));
205 }
206
207
208
209
210
211 private Path summaryFile(Path basedir, boolean originAware, String safeRepositoryId, String checksumExtension) {
212 String fileName = CHECKSUMS_FILE_PREFIX;
213 if (originAware) {
214 fileName += "-" + safeRepositoryId;
215 }
216 return basedir.resolve(fileName + "." + checksumExtension);
217 }
218
219 private ConcurrentHashMap<String, String> loadProvidedChecksums(Path summaryFile) {
220 ConcurrentHashMap<String, String> result = new ConcurrentHashMap<>();
221 if (Files.isRegularFile(summaryFile)) {
222 try (BufferedReader reader = Files.newBufferedReader(summaryFile, StandardCharsets.UTF_8)) {
223 String line;
224 while ((line = reader.readLine()) != null) {
225 if (!line.startsWith("#") && !line.isEmpty()) {
226 String[] parts = line.split(" ", 2);
227 if (parts.length == 2) {
228 String newChecksum = parts[0];
229 String artifactPath = parts[1];
230 String oldChecksum = result.put(artifactPath, newChecksum);
231 if (oldChecksum != null) {
232 if (Objects.equals(oldChecksum, newChecksum)) {
233 LOGGER.warn(
234 "Checksums file '{}' contains duplicate checksums for artifact {}: {}",
235 summaryFile,
236 artifactPath,
237 oldChecksum);
238 } else {
239 LOGGER.warn(
240 "Checksums file '{}' contains different checksums for artifact {}: "
241 + "old '{}' replaced by new '{}'",
242 summaryFile,
243 artifactPath,
244 oldChecksum,
245 newChecksum);
246 }
247 }
248 } else {
249 LOGGER.warn("Checksums file '{}' ignored malformed line '{}'", summaryFile, line);
250 }
251 }
252 }
253 } catch (IOException e) {
254 throw new UncheckedIOException(e);
255 }
256 LOGGER.info("Loaded {} trusted checksums from {}", result.size(), summaryFile);
257 }
258 return result;
259 }
260
261 private class SummaryFileWriter implements Writer {
262 private final ConcurrentHashMap<Path, ConcurrentHashMap<String, String>> cache;
263
264 private final Path basedir;
265
266 private final boolean originAware;
267
268 private final Function<ArtifactRepository, String> repositoryKeyFunction;
269
270 private SummaryFileWriter(
271 ConcurrentHashMap<Path, ConcurrentHashMap<String, String>> cache,
272 Path basedir,
273 boolean originAware,
274 Function<ArtifactRepository, String> repositoryKeyFunction) {
275 this.cache = cache;
276 this.basedir = basedir;
277 this.originAware = originAware;
278 this.repositoryKeyFunction = repositoryKeyFunction;
279 }
280
281 @Override
282 public void addTrustedArtifactChecksums(
283 Artifact artifact,
284 ArtifactRepository artifactRepository,
285 List<ChecksumAlgorithmFactory> checksumAlgorithmFactories,
286 Map<String, String> trustedArtifactChecksums) {
287 String artifactPath = localPathComposer.getPathForArtifact(artifact, false);
288 for (ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories) {
289 Path summaryFile = summaryFile(
290 basedir,
291 originAware,
292 repositoryKeyFunction.apply(artifactRepository),
293 checksumAlgorithmFactory.getFileExtension());
294 String checksum = requireNonNull(trustedArtifactChecksums.get(checksumAlgorithmFactory.getName()));
295
296 String oldChecksum = cache.computeIfAbsent(summaryFile, k -> loadProvidedChecksums(summaryFile))
297 .put(artifactPath, checksum);
298
299 if (oldChecksum == null) {
300 changedChecksums.put(summaryFile, Boolean.TRUE);
301 } else if (!Objects.equals(oldChecksum, checksum)) {
302 changedChecksums.put(summaryFile, Boolean.TRUE);
303 LOGGER.info(
304 "Trusted checksum for artifact {} replaced: old {}, new {}",
305 artifact,
306 oldChecksum,
307 checksum);
308 }
309 }
310 }
311 }
312
313
314
315
316 private void saveRecordedLines() {
317 if (changedChecksums.isEmpty()) {
318 return;
319 }
320
321 ArrayList<Exception> exceptions = new ArrayList<>();
322 for (Map.Entry<Path, ConcurrentHashMap<String, String>> entry : checksums.entrySet()) {
323 Path summaryFile = entry.getKey();
324 if (changedChecksums.get(summaryFile) != Boolean.TRUE) {
325 continue;
326 }
327 ConcurrentHashMap<String, String> recordedLines = entry.getValue();
328 if (!recordedLines.isEmpty()) {
329 try {
330 ConcurrentHashMap<String, String> result = new ConcurrentHashMap<>();
331 result.putAll(loadProvidedChecksums(summaryFile));
332 result.putAll(recordedLines);
333
334 LOGGER.info("Saving {} checksums to '{}'", result.size(), summaryFile);
335 pathProcessor.writeWithBackup(
336 summaryFile,
337 result.entrySet().stream()
338 .sorted(Map.Entry.comparingByKey())
339 .map(e -> e.getValue() + " " + e.getKey())
340 .collect(Collectors.joining(System.lineSeparator())));
341 } catch (IOException e) {
342 exceptions.add(e);
343 }
344 }
345 }
346 MultiRuntimeException.mayThrow("session save checksums failure", exceptions);
347 }
348 }