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;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.File;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.Properties;
30 import java.util.Set;
31 import java.util.TreeSet;
32 import java.util.concurrent.ConcurrentHashMap;
33
34 import org.eclipse.aether.RepositorySystemSession;
35 import org.eclipse.aether.SessionData;
36 import org.eclipse.aether.artifact.Artifact;
37 import org.eclipse.aether.impl.UpdateCheck;
38 import org.eclipse.aether.impl.UpdateCheckManager;
39 import org.eclipse.aether.impl.UpdatePolicyAnalyzer;
40 import org.eclipse.aether.metadata.Metadata;
41 import org.eclipse.aether.repository.AuthenticationDigest;
42 import org.eclipse.aether.repository.Proxy;
43 import org.eclipse.aether.repository.RemoteRepository;
44 import org.eclipse.aether.resolution.ResolutionErrorPolicy;
45 import org.eclipse.aether.transfer.ArtifactNotFoundException;
46 import org.eclipse.aether.transfer.ArtifactTransferException;
47 import org.eclipse.aether.transfer.MetadataNotFoundException;
48 import org.eclipse.aether.transfer.MetadataTransferException;
49 import org.eclipse.aether.util.ConfigUtils;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 import static java.util.Objects.requireNonNull;
54
55
56
57 @Singleton
58 @Named
59 public class DefaultUpdateCheckManager implements UpdateCheckManager {
60
61 private static final Logger LOGGER = LoggerFactory.getLogger(DefaultUpdatePolicyAnalyzer.class);
62
63 private static final String UPDATED_KEY_SUFFIX = ".lastUpdated";
64
65 private static final String ERROR_KEY_SUFFIX = ".error";
66
67 private static final String NOT_FOUND = "";
68
69 static final Object SESSION_CHECKS = new Object() {
70 @Override
71 public String toString() {
72 return "updateCheckManager.checks";
73 }
74 };
75
76 static final String CONFIG_PROP_SESSION_STATE = "aether.updateCheckManager.sessionState";
77
78 private static final int STATE_ENABLED = 0;
79
80 private static final int STATE_BYPASS = 1;
81
82 private static final int STATE_DISABLED = 2;
83
84
85
86
87
88
89
90 private static final long TS_NEVER = 0L;
91
92
93
94
95
96
97
98
99
100
101 private static final long TS_UNKNOWN = 1L;
102
103 private final TrackingFileManager trackingFileManager;
104
105 private final UpdatePolicyAnalyzer updatePolicyAnalyzer;
106
107 @Inject
108 public DefaultUpdateCheckManager(
109 TrackingFileManager trackingFileManager, UpdatePolicyAnalyzer updatePolicyAnalyzer) {
110 this.trackingFileManager = requireNonNull(trackingFileManager, "tracking file manager cannot be null");
111 this.updatePolicyAnalyzer = requireNonNull(updatePolicyAnalyzer, "update policy analyzer cannot be null");
112 }
113
114 @Override
115 public void checkArtifact(RepositorySystemSession session, UpdateCheck<Artifact, ArtifactTransferException> check) {
116 requireNonNull(session, "session cannot be null");
117 requireNonNull(check, "check cannot be null");
118 final String updatePolicy = check.getArtifactPolicy();
119 if (check.getLocalLastUpdated() != 0
120 && !isUpdatedRequired(session, check.getLocalLastUpdated(), updatePolicy)) {
121 LOGGER.debug("Skipped remote request for {}, locally installed artifact up-to-date", check.getItem());
122
123 check.setRequired(false);
124 return;
125 }
126
127 Artifact artifact = check.getItem();
128 RemoteRepository repository = check.getRepository();
129
130 File artifactFile =
131 requireNonNull(check.getFile(), String.format("The artifact '%s' has no file attached", artifact));
132
133 boolean fileExists = check.isFileValid() && artifactFile.exists();
134
135 File touchFile = getArtifactTouchFile(artifactFile);
136 Properties props = read(touchFile);
137
138 String updateKey = getUpdateKey(session, artifactFile, repository);
139 String dataKey = getDataKey(repository);
140
141 String error = getError(props, dataKey);
142
143 long lastUpdated;
144 if (error == null) {
145 if (fileExists) {
146
147 lastUpdated = artifactFile.lastModified();
148 } else {
149
150 lastUpdated = TS_NEVER;
151 }
152 } else if (error.isEmpty()) {
153
154 lastUpdated = getLastUpdated(props, dataKey);
155 } else {
156
157 String transferKey = getTransferKey(session, repository);
158 lastUpdated = getLastUpdated(props, transferKey);
159 }
160
161 if (lastUpdated == TS_NEVER) {
162 check.setRequired(true);
163 } else if (isAlreadyUpdated(session, updateKey)) {
164 LOGGER.debug("Skipped remote request for {}, already updated during this session", check.getItem());
165
166 check.setRequired(false);
167 if (error != null) {
168 check.setException(newException(error, artifact, repository));
169 }
170 } else if (isUpdatedRequired(session, lastUpdated, updatePolicy)) {
171 check.setRequired(true);
172 } else if (fileExists) {
173 LOGGER.debug("Skipped remote request for {}, locally cached artifact up-to-date", check.getItem());
174
175 check.setRequired(false);
176 } else {
177 int errorPolicy = Utils.getPolicy(session, artifact, repository);
178 int cacheFlag = getCacheFlag(error);
179 if ((errorPolicy & cacheFlag) != 0) {
180 check.setRequired(false);
181 check.setException(newException(error, artifact, repository));
182 } else {
183 check.setRequired(true);
184 }
185 }
186 }
187
188 private static int getCacheFlag(String error) {
189 if (error == null || error.isEmpty()) {
190 return ResolutionErrorPolicy.CACHE_NOT_FOUND;
191 } else {
192 return ResolutionErrorPolicy.CACHE_TRANSFER_ERROR;
193 }
194 }
195
196 private ArtifactTransferException newException(String error, Artifact artifact, RemoteRepository repository) {
197 if (error == null || error.isEmpty()) {
198 return new ArtifactNotFoundException(
199 artifact,
200 repository,
201 artifact
202 + " was not found in " + repository.getUrl()
203 + " during a previous attempt. This failure was"
204 + " cached in the local repository and"
205 + " resolution is not reattempted until the update interval of " + repository.getId()
206 + " has elapsed or updates are forced",
207 true);
208 } else {
209 return new ArtifactTransferException(
210 artifact,
211 repository,
212 artifact + " failed to transfer from "
213 + repository.getUrl() + " during a previous attempt. This failure"
214 + " was cached in the local repository and"
215 + " resolution is not reattempted until the update interval of " + repository.getId()
216 + " has elapsed or updates are forced. Original error: " + error,
217 true);
218 }
219 }
220
221 @Override
222 public void checkMetadata(RepositorySystemSession session, UpdateCheck<Metadata, MetadataTransferException> check) {
223 requireNonNull(session, "session cannot be null");
224 requireNonNull(check, "check cannot be null");
225 final String updatePolicy = check.getMetadataPolicy();
226 if (check.getLocalLastUpdated() != 0
227 && !isUpdatedRequired(session, check.getLocalLastUpdated(), updatePolicy)) {
228 LOGGER.debug("Skipped remote request for {} locally installed metadata up-to-date", check.getItem());
229
230 check.setRequired(false);
231 return;
232 }
233
234 Metadata metadata = check.getItem();
235 RemoteRepository repository = check.getRepository();
236
237 File metadataFile =
238 requireNonNull(check.getFile(), String.format("The metadata '%s' has no file attached", metadata));
239
240 boolean fileExists = check.isFileValid() && metadataFile.exists();
241
242 File touchFile = getMetadataTouchFile(metadataFile);
243 Properties props = read(touchFile);
244
245 String updateKey = getUpdateKey(session, metadataFile, repository);
246 String dataKey = getDataKey(metadataFile);
247
248 String error = getError(props, dataKey);
249
250 long lastUpdated;
251 if (error == null) {
252 if (fileExists) {
253
254 lastUpdated = getLastUpdated(props, dataKey);
255 } else {
256
257 lastUpdated = TS_NEVER;
258 }
259 } else if (error.isEmpty()) {
260
261 lastUpdated = getLastUpdated(props, dataKey);
262 } else {
263
264 String transferKey = getTransferKey(session, metadataFile, repository);
265 lastUpdated = getLastUpdated(props, transferKey);
266 }
267
268 if (lastUpdated == TS_NEVER) {
269 check.setRequired(true);
270 } else if (isAlreadyUpdated(session, updateKey)) {
271 LOGGER.debug("Skipped remote request for {}, already updated during this session", check.getItem());
272
273 check.setRequired(false);
274 if (error != null) {
275 check.setException(newException(error, metadata, repository));
276 }
277 } else if (isUpdatedRequired(session, lastUpdated, updatePolicy)) {
278 check.setRequired(true);
279 } else if (fileExists) {
280 LOGGER.debug("Skipped remote request for {}, locally cached metadata up-to-date", check.getItem());
281
282 check.setRequired(false);
283 } else {
284 int errorPolicy = Utils.getPolicy(session, metadata, repository);
285 int cacheFlag = getCacheFlag(error);
286 if ((errorPolicy & cacheFlag) != 0) {
287 check.setRequired(false);
288 check.setException(newException(error, metadata, repository));
289 } else {
290 check.setRequired(true);
291 }
292 }
293 }
294
295 private MetadataTransferException newException(String error, Metadata metadata, RemoteRepository repository) {
296 if (error == null || error.isEmpty()) {
297 return new MetadataNotFoundException(
298 metadata,
299 repository,
300 metadata + " was not found in "
301 + repository.getUrl() + " during a previous attempt."
302 + " This failure was cached in the local repository and"
303 + " resolution is not be reattempted until the update interval of " + repository.getId()
304 + " has elapsed or updates are forced",
305 true);
306 } else {
307 return new MetadataTransferException(
308 metadata,
309 repository,
310 metadata + " failed to transfer from "
311 + repository.getUrl() + " during a previous attempt."
312 + " This failure was cached in the local repository and"
313 + " resolution will not be reattempted until the update interval of " + repository.getId()
314 + " has elapsed or updates are forced. Original error: " + error,
315 true);
316 }
317 }
318
319 private long getLastUpdated(Properties props, String key) {
320 String value = props.getProperty(key + UPDATED_KEY_SUFFIX, "");
321 try {
322 return (!value.isEmpty()) ? Long.parseLong(value) : TS_UNKNOWN;
323 } catch (NumberFormatException e) {
324 LOGGER.debug("Cannot parse last updated date {}, ignoring it", value, e);
325 return TS_UNKNOWN;
326 }
327 }
328
329 private String getError(Properties props, String key) {
330 return props.getProperty(key + ERROR_KEY_SUFFIX);
331 }
332
333 private File getArtifactTouchFile(File artifactFile) {
334 return new File(artifactFile.getPath() + UPDATED_KEY_SUFFIX);
335 }
336
337 private File getMetadataTouchFile(File metadataFile) {
338 return new File(metadataFile.getParent(), "resolver-status.properties");
339 }
340
341 private String getDataKey(RemoteRepository repository) {
342 Set<String> mirroredUrls = Collections.emptySet();
343 if (repository.isRepositoryManager()) {
344 mirroredUrls = new TreeSet<>();
345 for (RemoteRepository mirroredRepository : repository.getMirroredRepositories()) {
346 mirroredUrls.add(normalizeRepoUrl(mirroredRepository.getUrl()));
347 }
348 }
349
350 StringBuilder buffer = new StringBuilder(1024);
351
352 buffer.append(normalizeRepoUrl(repository.getUrl()));
353 for (String mirroredUrl : mirroredUrls) {
354 buffer.append('+').append(mirroredUrl);
355 }
356
357 return buffer.toString();
358 }
359
360 private String getTransferKey(RepositorySystemSession session, RemoteRepository repository) {
361 return getRepoKey(session, repository);
362 }
363
364 private String getDataKey(File metadataFile) {
365 return metadataFile.getName();
366 }
367
368 private String getTransferKey(RepositorySystemSession session, File metadataFile, RemoteRepository repository) {
369 return metadataFile.getName() + '/' + getRepoKey(session, repository);
370 }
371
372 private String getRepoKey(RepositorySystemSession session, RemoteRepository repository) {
373 StringBuilder buffer = new StringBuilder(128);
374
375 Proxy proxy = repository.getProxy();
376 if (proxy != null) {
377 buffer.append(AuthenticationDigest.forProxy(session, repository)).append('@');
378 buffer.append(proxy.getHost()).append(':').append(proxy.getPort()).append('>');
379 }
380
381 buffer.append(AuthenticationDigest.forRepository(session, repository)).append('@');
382
383 buffer.append(repository.getContentType()).append('-');
384 buffer.append(repository.getId()).append('-');
385 buffer.append(normalizeRepoUrl(repository.getUrl()));
386
387 return buffer.toString();
388 }
389
390 private String normalizeRepoUrl(String url) {
391 String result = url;
392 if (url != null && !url.isEmpty() && !url.endsWith("/")) {
393 result = url + '/';
394 }
395 return result;
396 }
397
398 private String getUpdateKey(RepositorySystemSession session, File file, RemoteRepository repository) {
399 return file.getAbsolutePath() + '|' + getRepoKey(session, repository);
400 }
401
402 private int getSessionState(RepositorySystemSession session) {
403 String mode = ConfigUtils.getString(session, "enabled", CONFIG_PROP_SESSION_STATE);
404 if (Boolean.parseBoolean(mode) || "enabled".equalsIgnoreCase(mode)) {
405
406 return STATE_ENABLED;
407 } else if ("bypass".equalsIgnoreCase(mode)) {
408
409 return STATE_BYPASS;
410 } else {
411
412 return STATE_DISABLED;
413 }
414 }
415
416 private boolean isAlreadyUpdated(RepositorySystemSession session, Object updateKey) {
417 if (getSessionState(session) >= STATE_BYPASS) {
418 return false;
419 }
420 SessionData data = session.getData();
421 Object checkedFiles = data.get(SESSION_CHECKS);
422 if (!(checkedFiles instanceof Map)) {
423 return false;
424 }
425 return ((Map<?, ?>) checkedFiles).containsKey(updateKey);
426 }
427
428 @SuppressWarnings("unchecked")
429 private void setUpdated(RepositorySystemSession session, Object updateKey) {
430 if (getSessionState(session) >= STATE_DISABLED) {
431 return;
432 }
433 SessionData data = session.getData();
434 Object checkedFiles = data.computeIfAbsent(SESSION_CHECKS, () -> new ConcurrentHashMap<>(256));
435 ((Map<Object, Boolean>) checkedFiles).put(updateKey, Boolean.TRUE);
436 }
437
438 private boolean isUpdatedRequired(RepositorySystemSession session, long lastModified, String policy) {
439 return updatePolicyAnalyzer.isUpdatedRequired(session, lastModified, policy);
440 }
441
442 private Properties read(File touchFile) {
443 Properties props = trackingFileManager.read(touchFile);
444 return (props != null) ? props : new Properties();
445 }
446
447 @Override
448 public void touchArtifact(RepositorySystemSession session, UpdateCheck<Artifact, ArtifactTransferException> check) {
449 requireNonNull(session, "session cannot be null");
450 requireNonNull(check, "check cannot be null");
451 File artifactFile = check.getFile();
452 File touchFile = getArtifactTouchFile(artifactFile);
453
454 String updateKey = getUpdateKey(session, artifactFile, check.getRepository());
455 String dataKey = getDataKey(check.getAuthoritativeRepository());
456 String transferKey = getTransferKey(session, check.getRepository());
457
458 setUpdated(session, updateKey);
459 Properties props = write(touchFile, dataKey, transferKey, check.getException());
460
461 if (artifactFile.exists() && !hasErrors(props)) {
462 touchFile.delete();
463 }
464 }
465
466 private boolean hasErrors(Properties props) {
467 for (Object key : props.keySet()) {
468 if (key.toString().endsWith(ERROR_KEY_SUFFIX)) {
469 return true;
470 }
471 }
472 return false;
473 }
474
475 @Override
476 public void touchMetadata(RepositorySystemSession session, UpdateCheck<Metadata, MetadataTransferException> check) {
477 requireNonNull(session, "session cannot be null");
478 requireNonNull(check, "check cannot be null");
479 File metadataFile = check.getFile();
480 File touchFile = getMetadataTouchFile(metadataFile);
481
482 String updateKey = getUpdateKey(session, metadataFile, check.getRepository());
483 String dataKey = getDataKey(metadataFile);
484 String transferKey = getTransferKey(session, metadataFile, check.getRepository());
485
486 setUpdated(session, updateKey);
487 write(touchFile, dataKey, transferKey, check.getException());
488 }
489
490 private Properties write(File touchFile, String dataKey, String transferKey, Exception error) {
491 Map<String, String> updates = new HashMap<>();
492
493 String timestamp = Long.toString(System.currentTimeMillis());
494
495 if (error == null) {
496 updates.put(dataKey + ERROR_KEY_SUFFIX, null);
497 updates.put(dataKey + UPDATED_KEY_SUFFIX, timestamp);
498 updates.put(transferKey + UPDATED_KEY_SUFFIX, null);
499 } else if (error instanceof ArtifactNotFoundException || error instanceof MetadataNotFoundException) {
500 updates.put(dataKey + ERROR_KEY_SUFFIX, NOT_FOUND);
501 updates.put(dataKey + UPDATED_KEY_SUFFIX, timestamp);
502 updates.put(transferKey + UPDATED_KEY_SUFFIX, null);
503 } else {
504 String msg = error.getMessage();
505 if (msg == null || msg.isEmpty()) {
506 msg = error.getClass().getSimpleName();
507 }
508 updates.put(dataKey + ERROR_KEY_SUFFIX, msg);
509 updates.put(dataKey + UPDATED_KEY_SUFFIX, null);
510 updates.put(transferKey + UPDATED_KEY_SUFFIX, timestamp);
511 }
512
513 return trackingFileManager.update(touchFile, updates);
514 }
515 }