001 package org.apache.maven.repository.legacy; 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 022 import java.io.ByteArrayInputStream; 023 import java.io.ByteArrayOutputStream; 024 import java.io.File; 025 import java.io.FileInputStream; 026 import java.io.IOException; 027 import java.io.RandomAccessFile; 028 import java.nio.ByteBuffer; 029 import java.nio.channels.FileChannel; 030 import java.nio.channels.FileLock; 031 import java.util.Date; 032 import java.util.Properties; 033 034 import org.apache.maven.artifact.Artifact; 035 import org.apache.maven.artifact.repository.ArtifactRepository; 036 import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy; 037 import org.apache.maven.artifact.repository.Authentication; 038 import org.apache.maven.artifact.repository.metadata.RepositoryMetadata; 039 import org.apache.maven.repository.Proxy; 040 import org.codehaus.plexus.component.annotations.Component; 041 import org.codehaus.plexus.logging.AbstractLogEnabled; 042 import org.codehaus.plexus.logging.Logger; 043 044 @Component( role = UpdateCheckManager.class ) 045 public class DefaultUpdateCheckManager 046 extends AbstractLogEnabled 047 implements UpdateCheckManager 048 { 049 050 private static final String ERROR_KEY_SUFFIX = ".error"; 051 052 public DefaultUpdateCheckManager() 053 { 054 055 } 056 057 public DefaultUpdateCheckManager( Logger logger ) 058 { 059 enableLogging( logger ); 060 } 061 062 public static final String LAST_UPDATE_TAG = ".lastUpdated"; 063 064 private static final String TOUCHFILE_NAME = "resolver-status.properties"; 065 066 public boolean isUpdateRequired( Artifact artifact, ArtifactRepository repository ) 067 { 068 File file = artifact.getFile(); 069 070 ArtifactRepositoryPolicy policy = artifact.isSnapshot() ? repository.getSnapshots() : repository.getReleases(); 071 072 if ( !policy.isEnabled() ) 073 { 074 if ( getLogger().isDebugEnabled() ) 075 { 076 getLogger().debug( 077 "Skipping update check for " + artifact + " (" + file + ") from " 078 + repository.getId() + " (" + repository.getUrl() + ")" ); 079 } 080 081 return false; 082 } 083 084 if ( getLogger().isDebugEnabled() ) 085 { 086 getLogger().debug( 087 "Determining update check for " + artifact + " (" + file + ") from " 088 + repository.getId() + " (" + repository.getUrl() + ")" ); 089 } 090 091 if ( file == null ) 092 { 093 // TODO throw something instead? 094 return true; 095 } 096 097 Date lastCheckDate; 098 099 if ( file.exists() ) 100 { 101 lastCheckDate = new Date ( file.lastModified() ); 102 } 103 else 104 { 105 File touchfile = getTouchfile( artifact ); 106 lastCheckDate = readLastUpdated( touchfile, getRepositoryKey( repository ) ); 107 } 108 109 return ( lastCheckDate == null ) || policy.checkOutOfDate( lastCheckDate ); 110 } 111 112 public boolean isUpdateRequired( RepositoryMetadata metadata, ArtifactRepository repository, File file ) 113 { 114 // Here, we need to determine which policy to use. Release updateInterval will be used when 115 // the metadata refers to a release artifact or meta-version, and snapshot updateInterval will be used when 116 // it refers to a snapshot artifact or meta-version. 117 // NOTE: Release metadata includes version information about artifacts that have been released, to allow 118 // meta-versions like RELEASE and LATEST to resolve, and also to allow retrieval of the range of valid, released 119 // artifacts available. 120 ArtifactRepositoryPolicy policy = metadata.getPolicy( repository ); 121 122 if ( !policy.isEnabled() ) 123 { 124 if ( getLogger().isDebugEnabled() ) 125 { 126 getLogger().debug( 127 "Skipping update check for " + metadata.getKey() + " (" + file + ") from " 128 + repository.getId() + " (" + repository.getUrl() + ")" ); 129 } 130 131 return false; 132 } 133 134 if ( getLogger().isDebugEnabled() ) 135 { 136 getLogger().debug( 137 "Determining update check for " + metadata.getKey() + " (" + file + ") from " 138 + repository.getId() + " (" + repository.getUrl() + ")" ); 139 } 140 141 if ( file == null ) 142 { 143 // TODO throw something instead? 144 return true; 145 } 146 147 Date lastCheckDate = readLastUpdated( metadata, repository, file ); 148 149 return ( lastCheckDate == null ) || policy.checkOutOfDate( lastCheckDate ); 150 } 151 152 private Date readLastUpdated( RepositoryMetadata metadata, ArtifactRepository repository, File file ) 153 { 154 File touchfile = getTouchfile( metadata, file ); 155 156 String key = getMetadataKey( repository, file ); 157 158 return readLastUpdated( touchfile, key ); 159 } 160 161 public String getError( Artifact artifact, ArtifactRepository repository ) 162 { 163 File touchFile = getTouchfile( artifact ); 164 return getError( touchFile, getRepositoryKey( repository ) ); 165 } 166 167 public void touch( Artifact artifact, ArtifactRepository repository, String error ) 168 { 169 File file = artifact.getFile(); 170 171 File touchfile = getTouchfile( artifact ); 172 173 if ( file.exists() ) 174 { 175 touchfile.delete(); 176 } 177 else 178 { 179 writeLastUpdated( touchfile, getRepositoryKey( repository ), error ); 180 } 181 } 182 183 public void touch( RepositoryMetadata metadata, ArtifactRepository repository, File file ) 184 { 185 File touchfile = getTouchfile( metadata, file ); 186 187 String key = getMetadataKey( repository, file ); 188 189 writeLastUpdated( touchfile, key, null ); 190 } 191 192 String getMetadataKey( ArtifactRepository repository, File file ) 193 { 194 return repository.getId() + '.' + file.getName() + LAST_UPDATE_TAG; 195 } 196 197 String getRepositoryKey( ArtifactRepository repository ) 198 { 199 StringBuilder buffer = new StringBuilder( 256 ); 200 201 Proxy proxy = repository.getProxy(); 202 if ( proxy != null ) 203 { 204 if ( proxy.getUserName() != null ) 205 { 206 int hash = ( proxy.getUserName() + proxy.getPassword() ).hashCode(); 207 buffer.append( hash ).append( '@' ); 208 } 209 buffer.append( proxy.getHost() ).append( ':' ).append( proxy.getPort() ).append( '>' ); 210 } 211 212 // consider the username&password because a repo manager might block artifacts depending on authorization 213 Authentication auth = repository.getAuthentication(); 214 if ( auth != null ) 215 { 216 int hash = ( auth.getUsername() + auth.getPassword() ).hashCode(); 217 buffer.append( hash ).append( '@' ); 218 } 219 220 // consider the URL (instead of the id) as this most closely relates to the contents in the repo 221 buffer.append( repository.getUrl() ); 222 223 return buffer.toString(); 224 } 225 226 private void writeLastUpdated( File touchfile, String key, String error ) 227 { 228 synchronized ( touchfile.getAbsolutePath().intern() ) 229 { 230 if ( !touchfile.getParentFile().exists() && !touchfile.getParentFile().mkdirs() ) 231 { 232 getLogger().debug( "Failed to create directory: " + touchfile.getParent() 233 + " for tracking artifact metadata resolution." ); 234 return; 235 } 236 237 FileChannel channel = null; 238 FileLock lock = null; 239 try 240 { 241 Properties props = new Properties(); 242 243 channel = new RandomAccessFile( touchfile, "rw" ).getChannel(); 244 lock = channel.lock( 0, channel.size(), false ); 245 246 if ( touchfile.canRead() ) 247 { 248 getLogger().debug( "Reading resolution-state from: " + touchfile ); 249 ByteBuffer buffer = ByteBuffer.allocate( (int) channel.size() ); 250 251 channel.read( buffer ); 252 buffer.flip(); 253 254 ByteArrayInputStream stream = new ByteArrayInputStream( buffer.array() ); 255 props.load( stream ); 256 } 257 258 props.setProperty( key, Long.toString( System.currentTimeMillis() ) ); 259 260 if ( error != null ) 261 { 262 props.setProperty( key + ERROR_KEY_SUFFIX, error ); 263 } 264 else 265 { 266 props.remove( key + ERROR_KEY_SUFFIX ); 267 } 268 269 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 270 271 getLogger().debug( "Writing resolution-state to: " + touchfile ); 272 props.store( stream, "Last modified on: " + new Date() ); 273 274 byte[] data = stream.toByteArray(); 275 ByteBuffer buffer = ByteBuffer.allocate( data.length ); 276 buffer.put( data ); 277 buffer.flip(); 278 279 channel.position( 0 ); 280 channel.write( buffer ); 281 } 282 catch ( IOException e ) 283 { 284 getLogger().debug( "Failed to record lastUpdated information for resolution.\nFile: " 285 + touchfile.toString() + "; key: " + key, e ); 286 } 287 finally 288 { 289 if ( lock != null ) 290 { 291 try 292 { 293 lock.release(); 294 } 295 catch ( IOException e ) 296 { 297 getLogger().debug( "Error releasing exclusive lock for resolution tracking file: " 298 + touchfile, e ); 299 } 300 } 301 302 if ( channel != null ) 303 { 304 try 305 { 306 channel.close(); 307 } 308 catch ( IOException e ) 309 { 310 getLogger().debug( "Error closing FileChannel for resolution tracking file: " 311 + touchfile, e ); 312 } 313 } 314 } 315 } 316 } 317 318 Date readLastUpdated( File touchfile, String key ) 319 { 320 getLogger().debug( "Searching for " + key + " in resolution tracking file." ); 321 322 Properties props = read( touchfile ); 323 if ( props != null ) 324 { 325 String rawVal = props.getProperty( key ); 326 if ( rawVal != null ) 327 { 328 try 329 { 330 return new Date( Long.parseLong( rawVal ) ); 331 } 332 catch ( NumberFormatException e ) 333 { 334 getLogger().debug( "Cannot parse lastUpdated date: \'" + rawVal + "\'. Ignoring.", e ); 335 } 336 } 337 } 338 return null; 339 } 340 341 private String getError( File touchFile, String key ) 342 { 343 Properties props = read( touchFile ); 344 if ( props != null ) 345 { 346 return props.getProperty( key + ERROR_KEY_SUFFIX ); 347 } 348 return null; 349 } 350 351 private Properties read( File touchfile ) 352 { 353 if ( !touchfile.canRead() ) 354 { 355 getLogger().debug( "Skipped unreadable resolution tracking file " + touchfile ); 356 return null; 357 } 358 359 synchronized ( touchfile.getAbsolutePath().intern() ) 360 { 361 FileInputStream stream = null; 362 FileLock lock = null; 363 FileChannel channel = null; 364 try 365 { 366 Properties props = new Properties(); 367 368 stream = new FileInputStream( touchfile ); 369 channel = stream.getChannel(); 370 lock = channel.lock( 0, channel.size(), true ); 371 372 getLogger().debug( "Reading resolution-state from: " + touchfile ); 373 props.load( stream ); 374 375 return props; 376 } 377 catch ( IOException e ) 378 { 379 getLogger().debug( "Failed to read resolution tracking file " + touchfile, e ); 380 381 return null; 382 } 383 finally 384 { 385 if ( lock != null ) 386 { 387 try 388 { 389 lock.release(); 390 } 391 catch ( IOException e ) 392 { 393 getLogger().debug( "Error releasing shared lock for resolution tracking file: " + touchfile, 394 e ); 395 } 396 } 397 398 if ( channel != null ) 399 { 400 try 401 { 402 channel.close(); 403 } 404 catch ( IOException e ) 405 { 406 getLogger().debug( "Error closing FileChannel for resolution tracking file: " + touchfile, e ); 407 } 408 } 409 } 410 } 411 } 412 413 File getTouchfile( Artifact artifact ) 414 { 415 StringBuilder sb = new StringBuilder( 128 ); 416 sb.append( artifact.getArtifactId() ); 417 sb.append( '-' ).append( artifact.getBaseVersion() ); 418 if ( artifact.getClassifier() != null ) 419 { 420 sb.append( '-' ).append( artifact.getClassifier() ); 421 } 422 sb.append( '.' ).append( artifact.getType() ).append( LAST_UPDATE_TAG ); 423 return new File( artifact.getFile().getParentFile(), sb.toString() ); 424 } 425 426 File getTouchfile( RepositoryMetadata metadata, File file ) 427 { 428 return new File( file.getParent(), TOUCHFILE_NAME ); 429 } 430 431 }