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