001package org.eclipse.aether.internal.impl; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.io.File; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.Map; 026import static java.util.Objects.requireNonNull; 027import java.util.Properties; 028import java.util.Set; 029import java.util.TreeSet; 030import java.util.concurrent.ConcurrentHashMap; 031 032import javax.inject.Inject; 033import javax.inject.Named; 034 035import org.eclipse.aether.RepositorySystemSession; 036import org.eclipse.aether.SessionData; 037import org.eclipse.aether.artifact.Artifact; 038import org.eclipse.aether.impl.UpdateCheck; 039import org.eclipse.aether.impl.UpdateCheckManager; 040import org.eclipse.aether.impl.UpdatePolicyAnalyzer; 041import org.eclipse.aether.metadata.Metadata; 042import org.eclipse.aether.repository.AuthenticationDigest; 043import org.eclipse.aether.repository.Proxy; 044import org.eclipse.aether.repository.RemoteRepository; 045import org.eclipse.aether.resolution.ResolutionErrorPolicy; 046import org.eclipse.aether.spi.locator.Service; 047import org.eclipse.aether.spi.locator.ServiceLocator; 048import org.eclipse.aether.transfer.ArtifactNotFoundException; 049import org.eclipse.aether.transfer.ArtifactTransferException; 050import org.eclipse.aether.transfer.MetadataNotFoundException; 051import org.eclipse.aether.transfer.MetadataTransferException; 052import org.eclipse.aether.util.ConfigUtils; 053import org.slf4j.Logger; 054import org.slf4j.LoggerFactory; 055 056/** 057 */ 058@Named 059public class DefaultUpdateCheckManager 060 implements UpdateCheckManager, Service 061{ 062 063 private static final Logger LOGGER = LoggerFactory.getLogger( DefaultUpdatePolicyAnalyzer.class ); 064 065 private UpdatePolicyAnalyzer updatePolicyAnalyzer; 066 067 private static final String UPDATED_KEY_SUFFIX = ".lastUpdated"; 068 069 private static final String ERROR_KEY_SUFFIX = ".error"; 070 071 private static final String NOT_FOUND = ""; 072 073 private static final String SESSION_CHECKS = "updateCheckManager.checks"; 074 075 static final String CONFIG_PROP_SESSION_STATE = "aether.updateCheckManager.sessionState"; 076 077 private static final int STATE_ENABLED = 0; 078 079 private static final int STATE_BYPASS = 1; 080 081 private static final int STATE_DISABLED = 2; 082 083 public DefaultUpdateCheckManager() 084 { 085 // enables default constructor 086 } 087 088 @Inject 089 DefaultUpdateCheckManager( UpdatePolicyAnalyzer updatePolicyAnalyzer ) 090 { 091 setUpdatePolicyAnalyzer( updatePolicyAnalyzer ); 092 } 093 094 public void initService( ServiceLocator locator ) 095 { 096 setUpdatePolicyAnalyzer( locator.getService( UpdatePolicyAnalyzer.class ) ); 097 } 098 099 public DefaultUpdateCheckManager setUpdatePolicyAnalyzer( UpdatePolicyAnalyzer updatePolicyAnalyzer ) 100 { 101 this.updatePolicyAnalyzer = requireNonNull( updatePolicyAnalyzer, "update policy analyzer cannot be null" ); 102 return this; 103 } 104 105 public void checkArtifact( RepositorySystemSession session, UpdateCheck<Artifact, ArtifactTransferException> check ) 106 { 107 if ( check.getLocalLastUpdated() != 0 108 && !isUpdatedRequired( session, check.getLocalLastUpdated(), check.getPolicy() ) ) 109 { 110 LOGGER.debug( "Skipped remote request for {}, locally installed artifact up-to-date.", check.getItem() ); 111 112 check.setRequired( false ); 113 return; 114 } 115 116 Artifact artifact = check.getItem(); 117 RemoteRepository repository = check.getRepository(); 118 119 File artifactFile = requireNonNull( check.getFile(), String.format( "The artifact '%s' has no file attached", artifact ) ); 120 121 boolean fileExists = check.isFileValid() && artifactFile.exists(); 122 123 File touchFile = getTouchFile( artifact, artifactFile ); 124 Properties props = read( touchFile ); 125 126 String updateKey = getUpdateKey( session, artifactFile, repository ); 127 String dataKey = getDataKey( artifact, artifactFile, repository ); 128 129 String error = getError( props, dataKey ); 130 131 long lastUpdated; 132 if ( error == null ) 133 { 134 if ( fileExists ) 135 { 136 // last update was successful 137 lastUpdated = artifactFile.lastModified(); 138 } 139 else 140 { 141 // this is the first attempt ever 142 lastUpdated = 0L; 143 } 144 } 145 else if ( error.length() <= 0 ) 146 { 147 // artifact did not exist 148 lastUpdated = getLastUpdated( props, dataKey ); 149 } 150 else 151 { 152 // artifact could not be transferred 153 String transferKey = getTransferKey( session, artifact, artifactFile, repository ); 154 lastUpdated = getLastUpdated( props, transferKey ); 155 } 156 157 if ( lastUpdated == 0L ) 158 { 159 check.setRequired( true ); 160 } 161 else if ( isAlreadyUpdated( session, updateKey ) ) 162 { 163 if ( LOGGER.isDebugEnabled() ) 164 { 165 LOGGER.debug( "Skipped remote request for " + check.getItem() 166 + ", already updated during this session." ); 167 } 168 169 check.setRequired( false ); 170 if ( error != null ) 171 { 172 check.setException( newException( error, artifact, repository ) ); 173 } 174 } 175 else if ( isUpdatedRequired( session, lastUpdated, check.getPolicy() ) ) 176 { 177 check.setRequired( true ); 178 } 179 else if ( fileExists ) 180 { 181 LOGGER.debug( "Skipped remote request for {}, locally cached artifact up-to-date.", check.getItem() ); 182 183 check.setRequired( false ); 184 } 185 else 186 { 187 int errorPolicy = Utils.getPolicy( session, artifact, repository ); 188 int cacheFlag = getCacheFlag( error ); 189 if ( ( errorPolicy & cacheFlag ) != 0 ) 190 { 191 check.setRequired( false ); 192 check.setException( newException( error, artifact, repository ) ); 193 } 194 else 195 { 196 check.setRequired( true ); 197 } 198 } 199 } 200 201 private static int getCacheFlag( String error ) 202 { 203 if ( error == null || error.length() <= 0 ) 204 { 205 return ResolutionErrorPolicy.CACHE_NOT_FOUND; 206 } 207 else 208 { 209 return ResolutionErrorPolicy.CACHE_TRANSFER_ERROR; 210 } 211 } 212 213 private ArtifactTransferException newException( String error, Artifact artifact, RemoteRepository repository ) 214 { 215 if ( error == null || error.length() <= 0 ) 216 { 217 return new ArtifactNotFoundException( artifact, repository, "Failure to find " + artifact + " in " 218 + repository.getUrl() + " was cached in the local repository, " 219 + "resolution will not be reattempted until the update interval of " + repository.getId() 220 + " has elapsed or updates are forced", true ); 221 } 222 else 223 { 224 return new ArtifactTransferException( artifact, repository, "Failure to transfer " + artifact + " from " 225 + repository.getUrl() + " was cached in the local repository, " 226 + "resolution will not be reattempted until the update interval of " + repository.getId() 227 + " has elapsed or updates are forced. Original error: " + error, true ); 228 } 229 } 230 231 public void checkMetadata( RepositorySystemSession session, UpdateCheck<Metadata, MetadataTransferException> check ) 232 { 233 if ( check.getLocalLastUpdated() != 0 234 && !isUpdatedRequired( session, check.getLocalLastUpdated(), check.getPolicy() ) ) 235 { 236 LOGGER.debug( "Skipped remote request for {} locally installed metadata up-to-date.", check.getItem() ); 237 238 check.setRequired( false ); 239 return; 240 } 241 242 Metadata metadata = check.getItem(); 243 RemoteRepository repository = check.getRepository(); 244 245 File metadataFile = requireNonNull( check.getFile(), String.format( "The metadata '%s' has no file attached", metadata ) ); 246 247 boolean fileExists = check.isFileValid() && metadataFile.exists(); 248 249 File touchFile = getTouchFile( metadata, metadataFile ); 250 Properties props = read( touchFile ); 251 252 String updateKey = getUpdateKey( session, metadataFile, repository ); 253 String dataKey = getDataKey( metadata, metadataFile, check.getAuthoritativeRepository() ); 254 255 String error = getError( props, dataKey ); 256 257 long lastUpdated; 258 if ( error == null ) 259 { 260 if ( fileExists ) 261 { 262 // last update was successful 263 lastUpdated = getLastUpdated( props, dataKey ); 264 } 265 else 266 { 267 // this is the first attempt ever 268 lastUpdated = 0L; 269 } 270 } 271 else if ( error.length() <= 0 ) 272 { 273 // metadata did not exist 274 lastUpdated = getLastUpdated( props, dataKey ); 275 } 276 else 277 { 278 // metadata could not be transferred 279 String transferKey = getTransferKey( session, metadata, metadataFile, repository ); 280 lastUpdated = getLastUpdated( props, transferKey ); 281 } 282 283 if ( lastUpdated == 0L ) 284 { 285 check.setRequired( true ); 286 } 287 else if ( isAlreadyUpdated( session, updateKey ) ) 288 { 289 LOGGER.debug( "Skipped remote request for {}, already updated during this session.", check.getItem() ); 290 291 check.setRequired( false ); 292 if ( error != null ) 293 { 294 check.setException( newException( error, metadata, repository ) ); 295 } 296 } 297 else if ( isUpdatedRequired( session, lastUpdated, check.getPolicy() ) ) 298 { 299 check.setRequired( true ); 300 } 301 else if ( fileExists ) 302 { 303 LOGGER.debug( "Skipped remote request for {}, locally cached metadata up-to-date.", check.getItem() ); 304 305 check.setRequired( false ); 306 } 307 else 308 { 309 int errorPolicy = Utils.getPolicy( session, metadata, repository ); 310 int cacheFlag = getCacheFlag( error ); 311 if ( ( errorPolicy & cacheFlag ) != 0 ) 312 { 313 check.setRequired( false ); 314 check.setException( newException( error, metadata, repository ) ); 315 } 316 else 317 { 318 check.setRequired( true ); 319 } 320 } 321 } 322 323 private MetadataTransferException newException( String error, Metadata metadata, RemoteRepository repository ) 324 { 325 if ( error == null || error.length() <= 0 ) 326 { 327 return new MetadataNotFoundException( metadata, repository, "Failure to find " + metadata + " in " 328 + repository.getUrl() + " was cached in the local repository, " 329 + "resolution will not be reattempted until the update interval of " + repository.getId() 330 + " has elapsed or updates are forced", true ); 331 } 332 else 333 { 334 return new MetadataTransferException( metadata, repository, "Failure to transfer " + metadata + " from " 335 + repository.getUrl() + " was cached in the local repository, " 336 + "resolution will not be reattempted until the update interval of " + repository.getId() 337 + " has elapsed or updates are forced. Original error: " + error, true ); 338 } 339 } 340 341 private long getLastUpdated( Properties props, String key ) 342 { 343 String value = props.getProperty( key + UPDATED_KEY_SUFFIX, "" ); 344 try 345 { 346 return ( value.length() > 0 ) ? Long.parseLong( value ) : 1; 347 } 348 catch ( NumberFormatException e ) 349 { 350 LOGGER.debug( "Cannot parse lastUpdated date: \'{}\'. Ignoring.", value, e ); 351 return 1; 352 } 353 } 354 355 private String getError( Properties props, String key ) 356 { 357 return props.getProperty( key + ERROR_KEY_SUFFIX ); 358 } 359 360 private File getTouchFile( Artifact artifact, File artifactFile ) 361 { 362 return new File( artifactFile.getPath() + UPDATED_KEY_SUFFIX ); 363 } 364 365 private File getTouchFile( Metadata metadata, File metadataFile ) 366 { 367 return new File( metadataFile.getParent(), "resolver-status.properties" ); 368 } 369 370 private String getDataKey( Artifact artifact, File artifactFile, RemoteRepository repository ) 371 { 372 Set<String> mirroredUrls = Collections.emptySet(); 373 if ( repository.isRepositoryManager() ) 374 { 375 mirroredUrls = new TreeSet<String>(); 376 for ( RemoteRepository mirroredRepository : repository.getMirroredRepositories() ) 377 { 378 mirroredUrls.add( normalizeRepoUrl( mirroredRepository.getUrl() ) ); 379 } 380 } 381 382 StringBuilder buffer = new StringBuilder( 1024 ); 383 384 buffer.append( normalizeRepoUrl( repository.getUrl() ) ); 385 for ( String mirroredUrl : mirroredUrls ) 386 { 387 buffer.append( '+' ).append( mirroredUrl ); 388 } 389 390 return buffer.toString(); 391 } 392 393 private String getTransferKey( RepositorySystemSession session, Artifact artifact, File artifactFile, 394 RemoteRepository repository ) 395 { 396 return getRepoKey( session, repository ); 397 } 398 399 private String getDataKey( Metadata metadata, File metadataFile, RemoteRepository repository ) 400 { 401 return metadataFile.getName(); 402 } 403 404 private String getTransferKey( RepositorySystemSession session, Metadata metadata, File metadataFile, 405 RemoteRepository repository ) 406 { 407 return metadataFile.getName() + '/' + getRepoKey( session, repository ); 408 } 409 410 private String getRepoKey( RepositorySystemSession session, RemoteRepository repository ) 411 { 412 StringBuilder buffer = new StringBuilder( 128 ); 413 414 Proxy proxy = repository.getProxy(); 415 if ( proxy != null ) 416 { 417 buffer.append( AuthenticationDigest.forProxy( session, repository ) ).append( '@' ); 418 buffer.append( proxy.getHost() ).append( ':' ).append( proxy.getPort() ).append( '>' ); 419 } 420 421 buffer.append( AuthenticationDigest.forRepository( session, repository ) ).append( '@' ); 422 423 buffer.append( repository.getContentType() ).append( '-' ); 424 buffer.append( repository.getId() ).append( '-' ); 425 buffer.append( normalizeRepoUrl( repository.getUrl() ) ); 426 427 return buffer.toString(); 428 } 429 430 private String normalizeRepoUrl( String url ) 431 { 432 String result = url; 433 if ( url != null && url.length() > 0 && !url.endsWith( "/" ) ) 434 { 435 result = url + '/'; 436 } 437 return result; 438 } 439 440 private String getUpdateKey( RepositorySystemSession session, File file, RemoteRepository repository ) 441 { 442 return file.getAbsolutePath() + '|' + getRepoKey( session, repository ); 443 } 444 445 private int getSessionState( RepositorySystemSession session ) 446 { 447 String mode = ConfigUtils.getString( session, "true", CONFIG_PROP_SESSION_STATE ); 448 if ( Boolean.parseBoolean( mode ) ) 449 { 450 // perform update check at most once per session, regardless of update policy 451 return STATE_ENABLED; 452 } 453 else if ( "bypass".equalsIgnoreCase( mode ) ) 454 { 455 // evaluate update policy but record update in session to prevent potential future checks 456 return STATE_BYPASS; 457 } 458 else 459 { 460 // no session state at all, always evaluate update policy 461 return STATE_DISABLED; 462 } 463 } 464 465 private boolean isAlreadyUpdated( RepositorySystemSession session, Object updateKey ) 466 { 467 if ( getSessionState( session ) >= STATE_BYPASS ) 468 { 469 return false; 470 } 471 SessionData data = session.getData(); 472 Object checkedFiles = data.get( SESSION_CHECKS ); 473 if ( !( checkedFiles instanceof Map ) ) 474 { 475 return false; 476 } 477 return ( (Map<?, ?>) checkedFiles ).containsKey( updateKey ); 478 } 479 480 @SuppressWarnings( "unchecked" ) 481 private void setUpdated( RepositorySystemSession session, Object updateKey ) 482 { 483 if ( getSessionState( session ) >= STATE_DISABLED ) 484 { 485 return; 486 } 487 SessionData data = session.getData(); 488 Object checkedFiles = data.get( SESSION_CHECKS ); 489 while ( !( checkedFiles instanceof Map ) ) 490 { 491 Object old = checkedFiles; 492 checkedFiles = new ConcurrentHashMap<Object, Object>( 256 ); 493 if ( data.set( SESSION_CHECKS, old, checkedFiles ) ) 494 { 495 break; 496 } 497 checkedFiles = data.get( SESSION_CHECKS ); 498 } 499 ( (Map<Object, Boolean>) checkedFiles ).put( updateKey, Boolean.TRUE ); 500 } 501 502 private boolean isUpdatedRequired( RepositorySystemSession session, long lastModified, String policy ) 503 { 504 return updatePolicyAnalyzer.isUpdatedRequired( session, lastModified, policy ); 505 } 506 507 private Properties read( File touchFile ) 508 { 509 Properties props = new TrackingFileManager().read( touchFile ); 510 return ( props != null ) ? props : new Properties(); 511 } 512 513 public void touchArtifact( RepositorySystemSession session, UpdateCheck<Artifact, ArtifactTransferException> check ) 514 { 515 Artifact artifact = check.getItem(); 516 File artifactFile = check.getFile(); 517 File touchFile = getTouchFile( artifact, artifactFile ); 518 519 String updateKey = getUpdateKey( session, artifactFile, check.getRepository() ); 520 String dataKey = getDataKey( artifact, artifactFile, check.getAuthoritativeRepository() ); 521 String transferKey = getTransferKey( session, artifact, artifactFile, check.getRepository() ); 522 523 setUpdated( session, updateKey ); 524 Properties props = write( touchFile, dataKey, transferKey, check.getException() ); 525 526 if ( artifactFile.exists() && !hasErrors( props ) ) 527 { 528 touchFile.delete(); 529 } 530 } 531 532 private boolean hasErrors( Properties props ) 533 { 534 for ( Object key : props.keySet() ) 535 { 536 if ( key.toString().endsWith( ERROR_KEY_SUFFIX ) ) 537 { 538 return true; 539 } 540 } 541 return false; 542 } 543 544 public void touchMetadata( RepositorySystemSession session, UpdateCheck<Metadata, MetadataTransferException> check ) 545 { 546 Metadata metadata = check.getItem(); 547 File metadataFile = check.getFile(); 548 File touchFile = getTouchFile( metadata, metadataFile ); 549 550 String updateKey = getUpdateKey( session, metadataFile, check.getRepository() ); 551 String dataKey = getDataKey( metadata, metadataFile, check.getAuthoritativeRepository() ); 552 String transferKey = getTransferKey( session, metadata, metadataFile, check.getRepository() ); 553 554 setUpdated( session, updateKey ); 555 write( touchFile, dataKey, transferKey, check.getException() ); 556 } 557 558 private Properties write( File touchFile, String dataKey, String transferKey, Exception error ) 559 { 560 Map<String, String> updates = new HashMap<String, String>(); 561 562 String timestamp = Long.toString( System.currentTimeMillis() ); 563 564 if ( error == null ) 565 { 566 updates.put( dataKey + ERROR_KEY_SUFFIX, null ); 567 updates.put( dataKey + UPDATED_KEY_SUFFIX, timestamp ); 568 updates.put( transferKey + UPDATED_KEY_SUFFIX, null ); 569 } 570 else if ( error instanceof ArtifactNotFoundException || error instanceof MetadataNotFoundException ) 571 { 572 updates.put( dataKey + ERROR_KEY_SUFFIX, NOT_FOUND ); 573 updates.put( dataKey + UPDATED_KEY_SUFFIX, timestamp ); 574 updates.put( transferKey + UPDATED_KEY_SUFFIX, null ); 575 } 576 else 577 { 578 String msg = error.getMessage(); 579 if ( msg == null || msg.length() <= 0 ) 580 { 581 msg = error.getClass().getSimpleName(); 582 } 583 updates.put( dataKey + ERROR_KEY_SUFFIX, msg ); 584 updates.put( dataKey + UPDATED_KEY_SUFFIX, null ); 585 updates.put( transferKey + UPDATED_KEY_SUFFIX, timestamp ); 586 } 587 588 return new TrackingFileManager().update( touchFile, updates ); 589 } 590 591}