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