package org.apache.maven.repository.legacy; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.Date; import java.util.Properties; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy; import org.apache.maven.artifact.repository.Authentication; import org.apache.maven.artifact.repository.metadata.RepositoryMetadata; import org.apache.maven.repository.Proxy; import org.codehaus.plexus.component.annotations.Component; import org.codehaus.plexus.logging.AbstractLogEnabled; import org.codehaus.plexus.logging.Logger; @Component( role = UpdateCheckManager.class ) public class DefaultUpdateCheckManager extends AbstractLogEnabled implements UpdateCheckManager { private static final String ERROR_KEY_SUFFIX = ".error"; public DefaultUpdateCheckManager() { } public DefaultUpdateCheckManager( Logger logger ) { enableLogging( logger ); } public static final String LAST_UPDATE_TAG = ".lastUpdated"; private static final String TOUCHFILE_NAME = "resolver-status.properties"; public boolean isUpdateRequired( Artifact artifact, ArtifactRepository repository ) { File file = artifact.getFile(); ArtifactRepositoryPolicy policy = artifact.isSnapshot() ? repository.getSnapshots() : repository.getReleases(); if ( !policy.isEnabled() ) { if ( getLogger().isDebugEnabled() ) { getLogger().debug( "Skipping update check for " + artifact + " (" + file + ") from " + repository.getId() + " (" + repository.getUrl() + ")" ); } return false; } if ( getLogger().isDebugEnabled() ) { getLogger().debug( "Determining update check for " + artifact + " (" + file + ") from " + repository.getId() + " (" + repository.getUrl() + ")" ); } if ( file == null ) { // TODO throw something instead? return true; } Date lastCheckDate; if ( file.exists() ) { lastCheckDate = new Date ( file.lastModified() ); } else { File touchfile = getTouchfile( artifact ); lastCheckDate = readLastUpdated( touchfile, getRepositoryKey( repository ) ); } return ( lastCheckDate == null ) || policy.checkOutOfDate( lastCheckDate ); } public boolean isUpdateRequired( RepositoryMetadata metadata, ArtifactRepository repository, File file ) { // Here, we need to determine which policy to use. Release updateInterval will be used when // the metadata refers to a release artifact or meta-version, and snapshot updateInterval will be used when // it refers to a snapshot artifact or meta-version. // NOTE: Release metadata includes version information about artifacts that have been released, to allow // meta-versions like RELEASE and LATEST to resolve, and also to allow retrieval of the range of valid, released // artifacts available. ArtifactRepositoryPolicy policy = metadata.getPolicy( repository ); if ( !policy.isEnabled() ) { if ( getLogger().isDebugEnabled() ) { getLogger().debug( "Skipping update check for " + metadata.getKey() + " (" + file + ") from " + repository.getId() + " (" + repository.getUrl() + ")" ); } return false; } if ( getLogger().isDebugEnabled() ) { getLogger().debug( "Determining update check for " + metadata.getKey() + " (" + file + ") from " + repository.getId() + " (" + repository.getUrl() + ")" ); } if ( file == null ) { // TODO throw something instead? return true; } Date lastCheckDate = readLastUpdated( metadata, repository, file ); return ( lastCheckDate == null ) || policy.checkOutOfDate( lastCheckDate ); } private Date readLastUpdated( RepositoryMetadata metadata, ArtifactRepository repository, File file ) { File touchfile = getTouchfile( metadata, file ); String key = getMetadataKey( repository, file ); return readLastUpdated( touchfile, key ); } public String getError( Artifact artifact, ArtifactRepository repository ) { File touchFile = getTouchfile( artifact ); return getError( touchFile, getRepositoryKey( repository ) ); } public void touch( Artifact artifact, ArtifactRepository repository, String error ) { File file = artifact.getFile(); File touchfile = getTouchfile( artifact ); if ( file.exists() ) { touchfile.delete(); } else { writeLastUpdated( touchfile, getRepositoryKey( repository ), error ); } } public void touch( RepositoryMetadata metadata, ArtifactRepository repository, File file ) { File touchfile = getTouchfile( metadata, file ); String key = getMetadataKey( repository, file ); writeLastUpdated( touchfile, key, null ); } String getMetadataKey( ArtifactRepository repository, File file ) { return repository.getId() + '.' + file.getName() + LAST_UPDATE_TAG; } String getRepositoryKey( ArtifactRepository repository ) { StringBuilder buffer = new StringBuilder( 256 ); Proxy proxy = repository.getProxy(); if ( proxy != null ) { if ( proxy.getUserName() != null ) { int hash = ( proxy.getUserName() + proxy.getPassword() ).hashCode(); buffer.append( hash ).append( '@' ); } buffer.append( proxy.getHost() ).append( ':' ).append( proxy.getPort() ).append( '>' ); } // consider the username&password because a repo manager might block artifacts depending on authorization Authentication auth = repository.getAuthentication(); if ( auth != null ) { int hash = ( auth.getUsername() + auth.getPassword() ).hashCode(); buffer.append( hash ).append( '@' ); } // consider the URL (instead of the id) as this most closely relates to the contents in the repo buffer.append( repository.getUrl() ); return buffer.toString(); } private void writeLastUpdated( File touchfile, String key, String error ) { synchronized ( touchfile.getAbsolutePath().intern() ) { if ( !touchfile.getParentFile().exists() && !touchfile.getParentFile().mkdirs() ) { getLogger().debug( "Failed to create directory: " + touchfile.getParent() + " for tracking artifact metadata resolution." ); return; } FileChannel channel = null; FileLock lock = null; try { Properties props = new Properties(); channel = new RandomAccessFile( touchfile, "rw" ).getChannel(); lock = channel.lock( 0, channel.size(), false ); if ( touchfile.canRead() ) { getLogger().debug( "Reading resolution-state from: " + touchfile ); ByteBuffer buffer = ByteBuffer.allocate( (int) channel.size() ); channel.read( buffer ); buffer.flip(); ByteArrayInputStream stream = new ByteArrayInputStream( buffer.array() ); props.load( stream ); } props.setProperty( key, Long.toString( System.currentTimeMillis() ) ); if ( error != null ) { props.setProperty( key + ERROR_KEY_SUFFIX, error ); } else { props.remove( key + ERROR_KEY_SUFFIX ); } ByteArrayOutputStream stream = new ByteArrayOutputStream(); getLogger().debug( "Writing resolution-state to: " + touchfile ); props.store( stream, "Last modified on: " + new Date() ); byte[] data = stream.toByteArray(); ByteBuffer buffer = ByteBuffer.allocate( data.length ); buffer.put( data ); buffer.flip(); channel.position( 0 ); channel.write( buffer ); } catch ( IOException e ) { getLogger().debug( "Failed to record lastUpdated information for resolution.\nFile: " + touchfile.toString() + "; key: " + key, e ); } finally { if ( lock != null ) { try { lock.release(); } catch ( IOException e ) { getLogger().debug( "Error releasing exclusive lock for resolution tracking file: " + touchfile, e ); } } if ( channel != null ) { try { channel.close(); } catch ( IOException e ) { getLogger().debug( "Error closing FileChannel for resolution tracking file: " + touchfile, e ); } } } } } Date readLastUpdated( File touchfile, String key ) { getLogger().debug( "Searching for " + key + " in resolution tracking file." ); Properties props = read( touchfile ); if ( props != null ) { String rawVal = props.getProperty( key ); if ( rawVal != null ) { try { return new Date( Long.parseLong( rawVal ) ); } catch ( NumberFormatException e ) { getLogger().debug( "Cannot parse lastUpdated date: \'" + rawVal + "\'. Ignoring.", e ); } } } return null; } private String getError( File touchFile, String key ) { Properties props = read( touchFile ); if ( props != null ) { return props.getProperty( key + ERROR_KEY_SUFFIX ); } return null; } private Properties read( File touchfile ) { if ( !touchfile.canRead() ) { getLogger().debug( "Skipped unreadable resolution tracking file " + touchfile ); return null; } synchronized ( touchfile.getAbsolutePath().intern() ) { FileInputStream stream = null; FileLock lock = null; FileChannel channel = null; try { Properties props = new Properties(); stream = new FileInputStream( touchfile ); channel = stream.getChannel(); lock = channel.lock( 0, channel.size(), true ); getLogger().debug( "Reading resolution-state from: " + touchfile ); props.load( stream ); return props; } catch ( IOException e ) { getLogger().debug( "Failed to read resolution tracking file " + touchfile, e ); return null; } finally { if ( lock != null ) { try { lock.release(); } catch ( IOException e ) { getLogger().debug( "Error releasing shared lock for resolution tracking file: " + touchfile, e ); } } if ( channel != null ) { try { channel.close(); } catch ( IOException e ) { getLogger().debug( "Error closing FileChannel for resolution tracking file: " + touchfile, e ); } } } } } File getTouchfile( Artifact artifact ) { StringBuilder sb = new StringBuilder( 128 ); sb.append( artifact.getArtifactId() ); sb.append( '-' ).append( artifact.getBaseVersion() ); if ( artifact.getClassifier() != null ) { sb.append( '-' ).append( artifact.getClassifier() ); } sb.append( '.' ).append( artifact.getType() ).append( LAST_UPDATE_TAG ); return new File( artifact.getFile().getParentFile(), sb.toString() ); } File getTouchfile( RepositoryMetadata metadata, File file ) { return new File( file.getParent(), TOUCHFILE_NAME ); } }