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