| package org.eclipse.aether.internal.impl; |
| |
| /* |
| * 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.File; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| import static java.util.Objects.requireNonNull; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| |
| import org.eclipse.aether.RepositorySystemSession; |
| import org.eclipse.aether.SessionData; |
| import org.eclipse.aether.artifact.Artifact; |
| import org.eclipse.aether.impl.UpdateCheck; |
| import org.eclipse.aether.impl.UpdateCheckManager; |
| import org.eclipse.aether.impl.UpdatePolicyAnalyzer; |
| import org.eclipse.aether.metadata.Metadata; |
| import org.eclipse.aether.repository.AuthenticationDigest; |
| import org.eclipse.aether.repository.Proxy; |
| import org.eclipse.aether.repository.RemoteRepository; |
| import org.eclipse.aether.resolution.ResolutionErrorPolicy; |
| import org.eclipse.aether.spi.locator.Service; |
| import org.eclipse.aether.spi.locator.ServiceLocator; |
| import org.eclipse.aether.transfer.ArtifactNotFoundException; |
| import org.eclipse.aether.transfer.ArtifactTransferException; |
| import org.eclipse.aether.transfer.MetadataNotFoundException; |
| import org.eclipse.aether.transfer.MetadataTransferException; |
| import org.eclipse.aether.util.ConfigUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| */ |
| @Named |
| public class DefaultUpdateCheckManager |
| implements UpdateCheckManager, Service |
| { |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger( DefaultUpdatePolicyAnalyzer.class ); |
| |
| private UpdatePolicyAnalyzer updatePolicyAnalyzer; |
| |
| private static final String UPDATED_KEY_SUFFIX = ".lastUpdated"; |
| |
| private static final String ERROR_KEY_SUFFIX = ".error"; |
| |
| private static final String NOT_FOUND = ""; |
| |
| private static final String SESSION_CHECKS = "updateCheckManager.checks"; |
| |
| static final String CONFIG_PROP_SESSION_STATE = "aether.updateCheckManager.sessionState"; |
| |
| private static final int STATE_ENABLED = 0; |
| |
| private static final int STATE_BYPASS = 1; |
| |
| private static final int STATE_DISABLED = 2; |
| |
| public DefaultUpdateCheckManager() |
| { |
| // enables default constructor |
| } |
| |
| @Inject |
| DefaultUpdateCheckManager( UpdatePolicyAnalyzer updatePolicyAnalyzer ) |
| { |
| setUpdatePolicyAnalyzer( updatePolicyAnalyzer ); |
| } |
| |
| public void initService( ServiceLocator locator ) |
| { |
| setUpdatePolicyAnalyzer( locator.getService( UpdatePolicyAnalyzer.class ) ); |
| } |
| |
| public DefaultUpdateCheckManager setUpdatePolicyAnalyzer( UpdatePolicyAnalyzer updatePolicyAnalyzer ) |
| { |
| this.updatePolicyAnalyzer = requireNonNull( updatePolicyAnalyzer, "update policy analyzer cannot be null" ); |
| return this; |
| } |
| |
| public void checkArtifact( RepositorySystemSession session, UpdateCheck<Artifact, ArtifactTransferException> check ) |
| { |
| if ( check.getLocalLastUpdated() != 0 |
| && !isUpdatedRequired( session, check.getLocalLastUpdated(), check.getPolicy() ) ) |
| { |
| LOGGER.debug( "Skipped remote request for {}, locally installed artifact up-to-date.", check.getItem() ); |
| |
| check.setRequired( false ); |
| return; |
| } |
| |
| Artifact artifact = check.getItem(); |
| RemoteRepository repository = check.getRepository(); |
| |
| File artifactFile = requireNonNull( check.getFile(), String.format( "The artifact '%s' has no file attached", artifact ) ); |
| |
| boolean fileExists = check.isFileValid() && artifactFile.exists(); |
| |
| File touchFile = getTouchFile( artifact, artifactFile ); |
| Properties props = read( touchFile ); |
| |
| String updateKey = getUpdateKey( session, artifactFile, repository ); |
| String dataKey = getDataKey( artifact, artifactFile, repository ); |
| |
| String error = getError( props, dataKey ); |
| |
| long lastUpdated; |
| if ( error == null ) |
| { |
| if ( fileExists ) |
| { |
| // last update was successful |
| lastUpdated = artifactFile.lastModified(); |
| } |
| else |
| { |
| // this is the first attempt ever |
| lastUpdated = 0L; |
| } |
| } |
| else if ( error.length() <= 0 ) |
| { |
| // artifact did not exist |
| lastUpdated = getLastUpdated( props, dataKey ); |
| } |
| else |
| { |
| // artifact could not be transferred |
| String transferKey = getTransferKey( session, artifact, artifactFile, repository ); |
| lastUpdated = getLastUpdated( props, transferKey ); |
| } |
| |
| if ( lastUpdated == 0L ) |
| { |
| check.setRequired( true ); |
| } |
| else if ( isAlreadyUpdated( session, updateKey ) ) |
| { |
| if ( LOGGER.isDebugEnabled() ) |
| { |
| LOGGER.debug( "Skipped remote request for " + check.getItem() |
| + ", already updated during this session." ); |
| } |
| |
| check.setRequired( false ); |
| if ( error != null ) |
| { |
| check.setException( newException( error, artifact, repository ) ); |
| } |
| } |
| else if ( isUpdatedRequired( session, lastUpdated, check.getPolicy() ) ) |
| { |
| check.setRequired( true ); |
| } |
| else if ( fileExists ) |
| { |
| LOGGER.debug( "Skipped remote request for {}, locally cached artifact up-to-date.", check.getItem() ); |
| |
| check.setRequired( false ); |
| } |
| else |
| { |
| int errorPolicy = Utils.getPolicy( session, artifact, repository ); |
| int cacheFlag = getCacheFlag( error ); |
| if ( ( errorPolicy & cacheFlag ) != 0 ) |
| { |
| check.setRequired( false ); |
| check.setException( newException( error, artifact, repository ) ); |
| } |
| else |
| { |
| check.setRequired( true ); |
| } |
| } |
| } |
| |
| private static int getCacheFlag( String error ) |
| { |
| if ( error == null || error.length() <= 0 ) |
| { |
| return ResolutionErrorPolicy.CACHE_NOT_FOUND; |
| } |
| else |
| { |
| return ResolutionErrorPolicy.CACHE_TRANSFER_ERROR; |
| } |
| } |
| |
| private ArtifactTransferException newException( String error, Artifact artifact, RemoteRepository repository ) |
| { |
| if ( error == null || error.length() <= 0 ) |
| { |
| return new ArtifactNotFoundException( artifact, repository, "Failure to find " + artifact + " in " |
| + repository.getUrl() + " was cached in the local repository, " |
| + "resolution will not be reattempted until the update interval of " + repository.getId() |
| + " has elapsed or updates are forced", true ); |
| } |
| else |
| { |
| return new ArtifactTransferException( artifact, repository, "Failure to transfer " + artifact + " from " |
| + repository.getUrl() + " was cached in the local repository, " |
| + "resolution will not be reattempted until the update interval of " + repository.getId() |
| + " has elapsed or updates are forced. Original error: " + error, true ); |
| } |
| } |
| |
| public void checkMetadata( RepositorySystemSession session, UpdateCheck<Metadata, MetadataTransferException> check ) |
| { |
| if ( check.getLocalLastUpdated() != 0 |
| && !isUpdatedRequired( session, check.getLocalLastUpdated(), check.getPolicy() ) ) |
| { |
| LOGGER.debug( "Skipped remote request for {} locally installed metadata up-to-date.", check.getItem() ); |
| |
| check.setRequired( false ); |
| return; |
| } |
| |
| Metadata metadata = check.getItem(); |
| RemoteRepository repository = check.getRepository(); |
| |
| File metadataFile = requireNonNull( check.getFile(), String.format( "The metadata '%s' has no file attached", metadata ) ); |
| |
| boolean fileExists = check.isFileValid() && metadataFile.exists(); |
| |
| File touchFile = getTouchFile( metadata, metadataFile ); |
| Properties props = read( touchFile ); |
| |
| String updateKey = getUpdateKey( session, metadataFile, repository ); |
| String dataKey = getDataKey( metadata, metadataFile, check.getAuthoritativeRepository() ); |
| |
| String error = getError( props, dataKey ); |
| |
| long lastUpdated; |
| if ( error == null ) |
| { |
| if ( fileExists ) |
| { |
| // last update was successful |
| lastUpdated = getLastUpdated( props, dataKey ); |
| } |
| else |
| { |
| // this is the first attempt ever |
| lastUpdated = 0L; |
| } |
| } |
| else if ( error.length() <= 0 ) |
| { |
| // metadata did not exist |
| lastUpdated = getLastUpdated( props, dataKey ); |
| } |
| else |
| { |
| // metadata could not be transferred |
| String transferKey = getTransferKey( session, metadata, metadataFile, repository ); |
| lastUpdated = getLastUpdated( props, transferKey ); |
| } |
| |
| if ( lastUpdated == 0L ) |
| { |
| check.setRequired( true ); |
| } |
| else if ( isAlreadyUpdated( session, updateKey ) ) |
| { |
| LOGGER.debug( "Skipped remote request for {}, already updated during this session.", check.getItem() ); |
| |
| check.setRequired( false ); |
| if ( error != null ) |
| { |
| check.setException( newException( error, metadata, repository ) ); |
| } |
| } |
| else if ( isUpdatedRequired( session, lastUpdated, check.getPolicy() ) ) |
| { |
| check.setRequired( true ); |
| } |
| else if ( fileExists ) |
| { |
| LOGGER.debug( "Skipped remote request for {}, locally cached metadata up-to-date.", check.getItem() ); |
| |
| check.setRequired( false ); |
| } |
| else |
| { |
| int errorPolicy = Utils.getPolicy( session, metadata, repository ); |
| int cacheFlag = getCacheFlag( error ); |
| if ( ( errorPolicy & cacheFlag ) != 0 ) |
| { |
| check.setRequired( false ); |
| check.setException( newException( error, metadata, repository ) ); |
| } |
| else |
| { |
| check.setRequired( true ); |
| } |
| } |
| } |
| |
| private MetadataTransferException newException( String error, Metadata metadata, RemoteRepository repository ) |
| { |
| if ( error == null || error.length() <= 0 ) |
| { |
| return new MetadataNotFoundException( metadata, repository, "Failure to find " + metadata + " in " |
| + repository.getUrl() + " was cached in the local repository, " |
| + "resolution will not be reattempted until the update interval of " + repository.getId() |
| + " has elapsed or updates are forced", true ); |
| } |
| else |
| { |
| return new MetadataTransferException( metadata, repository, "Failure to transfer " + metadata + " from " |
| + repository.getUrl() + " was cached in the local repository, " |
| + "resolution will not be reattempted until the update interval of " + repository.getId() |
| + " has elapsed or updates are forced. Original error: " + error, true ); |
| } |
| } |
| |
| private long getLastUpdated( Properties props, String key ) |
| { |
| String value = props.getProperty( key + UPDATED_KEY_SUFFIX, "" ); |
| try |
| { |
| return ( value.length() > 0 ) ? Long.parseLong( value ) : 1; |
| } |
| catch ( NumberFormatException e ) |
| { |
| LOGGER.debug( "Cannot parse lastUpdated date: \'{}\'. Ignoring.", value, e ); |
| return 1; |
| } |
| } |
| |
| private String getError( Properties props, String key ) |
| { |
| return props.getProperty( key + ERROR_KEY_SUFFIX ); |
| } |
| |
| private File getTouchFile( Artifact artifact, File artifactFile ) |
| { |
| return new File( artifactFile.getPath() + UPDATED_KEY_SUFFIX ); |
| } |
| |
| private File getTouchFile( Metadata metadata, File metadataFile ) |
| { |
| return new File( metadataFile.getParent(), "resolver-status.properties" ); |
| } |
| |
| private String getDataKey( Artifact artifact, File artifactFile, RemoteRepository repository ) |
| { |
| Set<String> mirroredUrls = Collections.emptySet(); |
| if ( repository.isRepositoryManager() ) |
| { |
| mirroredUrls = new TreeSet<String>(); |
| for ( RemoteRepository mirroredRepository : repository.getMirroredRepositories() ) |
| { |
| mirroredUrls.add( normalizeRepoUrl( mirroredRepository.getUrl() ) ); |
| } |
| } |
| |
| StringBuilder buffer = new StringBuilder( 1024 ); |
| |
| buffer.append( normalizeRepoUrl( repository.getUrl() ) ); |
| for ( String mirroredUrl : mirroredUrls ) |
| { |
| buffer.append( '+' ).append( mirroredUrl ); |
| } |
| |
| return buffer.toString(); |
| } |
| |
| private String getTransferKey( RepositorySystemSession session, Artifact artifact, File artifactFile, |
| RemoteRepository repository ) |
| { |
| return getRepoKey( session, repository ); |
| } |
| |
| private String getDataKey( Metadata metadata, File metadataFile, RemoteRepository repository ) |
| { |
| return metadataFile.getName(); |
| } |
| |
| private String getTransferKey( RepositorySystemSession session, Metadata metadata, File metadataFile, |
| RemoteRepository repository ) |
| { |
| return metadataFile.getName() + '/' + getRepoKey( session, repository ); |
| } |
| |
| private String getRepoKey( RepositorySystemSession session, RemoteRepository repository ) |
| { |
| StringBuilder buffer = new StringBuilder( 128 ); |
| |
| Proxy proxy = repository.getProxy(); |
| if ( proxy != null ) |
| { |
| buffer.append( AuthenticationDigest.forProxy( session, repository ) ).append( '@' ); |
| buffer.append( proxy.getHost() ).append( ':' ).append( proxy.getPort() ).append( '>' ); |
| } |
| |
| buffer.append( AuthenticationDigest.forRepository( session, repository ) ).append( '@' ); |
| |
| buffer.append( repository.getContentType() ).append( '-' ); |
| buffer.append( repository.getId() ).append( '-' ); |
| buffer.append( normalizeRepoUrl( repository.getUrl() ) ); |
| |
| return buffer.toString(); |
| } |
| |
| private String normalizeRepoUrl( String url ) |
| { |
| String result = url; |
| if ( url != null && url.length() > 0 && !url.endsWith( "/" ) ) |
| { |
| result = url + '/'; |
| } |
| return result; |
| } |
| |
| private String getUpdateKey( RepositorySystemSession session, File file, RemoteRepository repository ) |
| { |
| return file.getAbsolutePath() + '|' + getRepoKey( session, repository ); |
| } |
| |
| private int getSessionState( RepositorySystemSession session ) |
| { |
| String mode = ConfigUtils.getString( session, "true", CONFIG_PROP_SESSION_STATE ); |
| if ( Boolean.parseBoolean( mode ) ) |
| { |
| // perform update check at most once per session, regardless of update policy |
| return STATE_ENABLED; |
| } |
| else if ( "bypass".equalsIgnoreCase( mode ) ) |
| { |
| // evaluate update policy but record update in session to prevent potential future checks |
| return STATE_BYPASS; |
| } |
| else |
| { |
| // no session state at all, always evaluate update policy |
| return STATE_DISABLED; |
| } |
| } |
| |
| private boolean isAlreadyUpdated( RepositorySystemSession session, Object updateKey ) |
| { |
| if ( getSessionState( session ) >= STATE_BYPASS ) |
| { |
| return false; |
| } |
| SessionData data = session.getData(); |
| Object checkedFiles = data.get( SESSION_CHECKS ); |
| if ( !( checkedFiles instanceof Map ) ) |
| { |
| return false; |
| } |
| return ( (Map<?, ?>) checkedFiles ).containsKey( updateKey ); |
| } |
| |
| @SuppressWarnings( "unchecked" ) |
| private void setUpdated( RepositorySystemSession session, Object updateKey ) |
| { |
| if ( getSessionState( session ) >= STATE_DISABLED ) |
| { |
| return; |
| } |
| SessionData data = session.getData(); |
| Object checkedFiles = data.get( SESSION_CHECKS ); |
| while ( !( checkedFiles instanceof Map ) ) |
| { |
| Object old = checkedFiles; |
| checkedFiles = new ConcurrentHashMap<Object, Object>( 256 ); |
| if ( data.set( SESSION_CHECKS, old, checkedFiles ) ) |
| { |
| break; |
| } |
| checkedFiles = data.get( SESSION_CHECKS ); |
| } |
| ( (Map<Object, Boolean>) checkedFiles ).put( updateKey, Boolean.TRUE ); |
| } |
| |
| private boolean isUpdatedRequired( RepositorySystemSession session, long lastModified, String policy ) |
| { |
| return updatePolicyAnalyzer.isUpdatedRequired( session, lastModified, policy ); |
| } |
| |
| private Properties read( File touchFile ) |
| { |
| Properties props = new TrackingFileManager().read( touchFile ); |
| return ( props != null ) ? props : new Properties(); |
| } |
| |
| public void touchArtifact( RepositorySystemSession session, UpdateCheck<Artifact, ArtifactTransferException> check ) |
| { |
| Artifact artifact = check.getItem(); |
| File artifactFile = check.getFile(); |
| File touchFile = getTouchFile( artifact, artifactFile ); |
| |
| String updateKey = getUpdateKey( session, artifactFile, check.getRepository() ); |
| String dataKey = getDataKey( artifact, artifactFile, check.getAuthoritativeRepository() ); |
| String transferKey = getTransferKey( session, artifact, artifactFile, check.getRepository() ); |
| |
| setUpdated( session, updateKey ); |
| Properties props = write( touchFile, dataKey, transferKey, check.getException() ); |
| |
| if ( artifactFile.exists() && !hasErrors( props ) ) |
| { |
| touchFile.delete(); |
| } |
| } |
| |
| private boolean hasErrors( Properties props ) |
| { |
| for ( Object key : props.keySet() ) |
| { |
| if ( key.toString().endsWith( ERROR_KEY_SUFFIX ) ) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public void touchMetadata( RepositorySystemSession session, UpdateCheck<Metadata, MetadataTransferException> check ) |
| { |
| Metadata metadata = check.getItem(); |
| File metadataFile = check.getFile(); |
| File touchFile = getTouchFile( metadata, metadataFile ); |
| |
| String updateKey = getUpdateKey( session, metadataFile, check.getRepository() ); |
| String dataKey = getDataKey( metadata, metadataFile, check.getAuthoritativeRepository() ); |
| String transferKey = getTransferKey( session, metadata, metadataFile, check.getRepository() ); |
| |
| setUpdated( session, updateKey ); |
| write( touchFile, dataKey, transferKey, check.getException() ); |
| } |
| |
| private Properties write( File touchFile, String dataKey, String transferKey, Exception error ) |
| { |
| Map<String, String> updates = new HashMap<String, String>(); |
| |
| String timestamp = Long.toString( System.currentTimeMillis() ); |
| |
| if ( error == null ) |
| { |
| updates.put( dataKey + ERROR_KEY_SUFFIX, null ); |
| updates.put( dataKey + UPDATED_KEY_SUFFIX, timestamp ); |
| updates.put( transferKey + UPDATED_KEY_SUFFIX, null ); |
| } |
| else if ( error instanceof ArtifactNotFoundException || error instanceof MetadataNotFoundException ) |
| { |
| updates.put( dataKey + ERROR_KEY_SUFFIX, NOT_FOUND ); |
| updates.put( dataKey + UPDATED_KEY_SUFFIX, timestamp ); |
| updates.put( transferKey + UPDATED_KEY_SUFFIX, null ); |
| } |
| else |
| { |
| String msg = error.getMessage(); |
| if ( msg == null || msg.length() <= 0 ) |
| { |
| msg = error.getClass().getSimpleName(); |
| } |
| updates.put( dataKey + ERROR_KEY_SUFFIX, msg ); |
| updates.put( dataKey + UPDATED_KEY_SUFFIX, null ); |
| updates.put( transferKey + UPDATED_KEY_SUFFIX, timestamp ); |
| } |
| |
| return new TrackingFileManager().update( touchFile, updates ); |
| } |
| |
| } |