blob: 302e37da445071bda86626524e0af71cf120d99f [file] [log] [blame]
package org.apache.archiva.consumers.core.repository;
/*
* 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 org.apache.archiva.common.utils.VersionUtil;
import org.apache.archiva.metadata.model.ArtifactMetadata;
import org.apache.archiva.metadata.model.facets.AuditEvent;
import org.apache.archiva.metadata.model.maven2.MavenArtifactFacet;
import org.apache.archiva.metadata.repository.MetadataRepository;
import org.apache.archiva.metadata.repository.MetadataRepositoryException;
import org.apache.archiva.metadata.repository.MetadataResolutionException;
import org.apache.archiva.metadata.repository.RepositorySession;
import org.apache.archiva.model.ArtifactReference;
import org.apache.archiva.repository.ContentNotFoundException;
import org.apache.archiva.repository.ManagedRepositoryContent;
import org.apache.archiva.repository.events.RepositoryListener;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Base class for all repository purge tasks.
*/
public abstract class AbstractRepositoryPurge
implements RepositoryPurge
{
protected Logger log = LoggerFactory.getLogger( getClass( ) );
protected final ManagedRepositoryContent repository;
protected final RepositorySession repositorySession;
protected final List<RepositoryListener> listeners;
private Logger logger = LoggerFactory.getLogger( "org.apache.archiva.AuditLog" );
private static final char DELIM = ' ';
public AbstractRepositoryPurge( ManagedRepositoryContent repository, RepositorySession repositorySession,
List<RepositoryListener> listeners )
{
this.repository = repository;
this.repositorySession = repositorySession;
this.listeners = listeners;
}
/*
* We have to track namespace, project, project version, artifact version and classifier
* There is no metadata class that contains all these properties.
*/
class ArtifactInfo
{
final String namespace;
final String name;
final String projectVersion;
String version;
String classifier;
ArtifactInfo( String namespace, String name, String projectVersion, String version )
{
this.namespace = namespace;
this.name = name;
this.projectVersion = projectVersion;
this.version = version;
}
ArtifactInfo( String namespace, String name, String projectVersion )
{
this.namespace = namespace;
this.name = name;
this.projectVersion = projectVersion;
}
/*
* Creates a info object without version and classifier
*/
ArtifactInfo projectVersionLevel( )
{
return new ArtifactInfo( this.namespace, this.name, this.projectVersion );
}
public void setClassifier( String classifier )
{
this.classifier = classifier;
}
public String getNamespace( )
{
return namespace;
}
public String getName( )
{
return name;
}
public String getProjectVersion( )
{
return projectVersion;
}
public String getVersion( )
{
return version;
}
public String getClassifier( )
{
return classifier;
}
public boolean hasClassifier( )
{
return classifier != null && !"".equals( classifier );
}
@Override
public boolean equals( Object o )
{
if ( this == o ) return true;
if ( o == null || getClass( ) != o.getClass( ) ) return false;
ArtifactInfo that = (ArtifactInfo) o;
if ( !namespace.equals( that.namespace ) ) return false;
if ( !name.equals( that.name ) ) return false;
if ( !projectVersion.equals( that.projectVersion ) ) return false;
if ( !( version != null ? version.equals( that.version ) : that.version == null ) ) return false;
return classifier != null ? classifier.equals( that.classifier ) : that.classifier == null;
}
@Override
public int hashCode( )
{
int result = namespace.hashCode( );
result = 31 * result + name.hashCode( );
result = 31 * result + projectVersion.hashCode( );
result = 31 * result + ( version != null ? version.hashCode( ) : 0 );
result = 31 * result + ( classifier != null ? classifier.hashCode( ) : 0 );
return result;
}
@Override
public String toString( )
{
final StringBuilder sb = new StringBuilder( "ArtifactInfo{" );
sb.append( "namespace='" ).append( namespace ).append( '\'' );
sb.append( ", name='" ).append( name ).append( '\'' );
sb.append( ", projectVersion='" ).append( projectVersion ).append( '\'' );
sb.append( ", version='" ).append( version ).append( '\'' );
sb.append( ", classifier='" ).append( classifier ).append( '\'' );
sb.append( '}' );
return sb.toString( );
}
}
/**
* Purge the repo. Update db and index of removed artifacts.
*
* @param references
*/
protected void purge( Set<ArtifactReference> references )
{
if ( references != null && !references.isEmpty( ) )
{
MetadataRepository metadataRepository = repositorySession.getRepository( );
Map<ArtifactInfo, ArtifactMetadata> metaRemovalList = new HashMap<>( );
Map<String, Collection<ArtifactMetadata>> metaResolved = new HashMap<>( );
for ( ArtifactReference reference : references )
{
String baseVersion = VersionUtil.getBaseVersion( reference.getVersion( ) );
// Needed for tracking in the hashmap
String metaBaseId = reference.getGroupId( ) + "/" + reference.getArtifactId( ) + "/" + baseVersion;
if ( !metaResolved.containsKey( metaBaseId ) )
{
try
{
metaResolved.put( metaBaseId, metadataRepository.getArtifacts( repository.getId( ), reference.getGroupId( ),
reference.getArtifactId( ), baseVersion ) );
}
catch ( MetadataResolutionException e )
{
log.error( "Error during metadata retrieval {}: {}", metaBaseId, e.getMessage( ) );
}
}
Path artifactFile = repository.toFile( reference ).toPath( );
for ( RepositoryListener listener : listeners )
{
listener.deleteArtifact( metadataRepository, repository.getId( ), reference.getGroupId( ),
reference.getArtifactId( ), reference.getVersion( ),
artifactFile.getFileName( ).toString( ) );
}
try
{
Files.delete( artifactFile );
log.debug( "File deleted: {}", artifactFile.toAbsolutePath( ) );
}
catch ( IOException e )
{
log.error( "Could not delete file {}: {}", artifactFile.toAbsolutePath( ), e.getMessage( ), e );
continue;
}
try
{
repository.deleteArtifact( reference );
}
catch ( ContentNotFoundException e )
{
log.warn( "skip error deleting artifact {}: {}", reference, e.getMessage( ) );
}
boolean snapshotVersion = VersionUtil.isSnapshot( reference.getVersion( ) );
// If this is a snapshot we have to search for artifacts with the same version. And remove all of them.
if ( snapshotVersion )
{
Collection<ArtifactMetadata> artifacts =
metaResolved.get( metaBaseId );
if ( artifacts != null )
{
// cleanup snapshots metadata
for ( ArtifactMetadata artifactMetadata : artifacts )
{
// Artifact metadata and reference version should match.
if ( artifactMetadata.getVersion( ).equals( reference.getVersion( ) ) )
{
ArtifactInfo info = new ArtifactInfo( artifactMetadata.getNamespace( ), artifactMetadata.getProject( ), artifactMetadata.getProjectVersion( ), artifactMetadata.getVersion( ) );
if ( StringUtils.isNotBlank( reference.getClassifier( ) ) )
{
info.setClassifier( reference.getClassifier( ) );
metaRemovalList.put( info, artifactMetadata );
}
else
{
// metadataRepository.removeArtifact( artifactMetadata, baseVersion );
metaRemovalList.put( info, artifactMetadata );
}
}
}
}
}
else // otherwise we delete the artifact version
{
ArtifactInfo info = new ArtifactInfo( reference.getGroupId( ), reference.getArtifactId( ), baseVersion, reference.getVersion( ) );
for ( ArtifactMetadata metadata : metaResolved.get( metaBaseId ) )
{
metaRemovalList.put( info, metadata );
}
}
triggerAuditEvent( repository.getRepository( ).getId( ), ArtifactReference.toKey( reference ),
AuditEvent.PURGE_ARTIFACT );
purgeSupportFiles( artifactFile );
}
purgeMetadata( metadataRepository, metaRemovalList );
repositorySession.save( );
}
}
/*
* Purges the metadata. First removes the artifacts. After that empty versions will be removed.
*/
private void purgeMetadata( MetadataRepository metadataRepository, Map<ArtifactInfo, ArtifactMetadata> dataList )
{
Set<ArtifactInfo> projectLevelMetadata = new HashSet<>( );
for ( Map.Entry<ArtifactInfo, ArtifactMetadata> infoEntry : dataList.entrySet( ) )
{
ArtifactInfo info = infoEntry.getKey( );
try
{
removeArtifact( metadataRepository, info, infoEntry.getValue( ) );
log.debug( "Removed artifact from MetadataRepository {}", info );
}
catch ( MetadataRepositoryException e )
{
log.error( "Could not remove artifact from MetadataRepository {}: {}", info, e.getMessage( ), e );
}
projectLevelMetadata.add( info.projectVersionLevel( ) );
}
metadataRepository.save( );
Collection<ArtifactMetadata> artifacts = null;
// Get remaining artifacts and remove project if empty
for ( ArtifactInfo info : projectLevelMetadata )
{
try
{
artifacts = metadataRepository.getArtifacts( repository.getId( ), info.getNamespace( ), info.getName( ),
info.getProjectVersion( ) );
if ( artifacts.size( ) == 0 )
{
metadataRepository.removeProjectVersion( repository.getId( ), info.getNamespace( ),
info.getName( ), info.getProjectVersion( ) );
log.debug( "Removed project version from MetadataRepository {}", info );
}
}
catch ( MetadataResolutionException | MetadataRepositoryException e )
{
log.error( "Could not remove project version from MetadataRepository {}: {}", info, e.getMessage( ), e );
}
}
metadataRepository.save( );
}
/*
* Removes the artifact from the metadataRepository. If a classifier is set, the facet will be removed.
*/
private void removeArtifact( MetadataRepository metadataRepository, ArtifactInfo artifactInfo, ArtifactMetadata artifactMetadata ) throws MetadataRepositoryException
{
if ( artifactInfo.hasClassifier( ) )
{
// cleanup facet which contains classifier information
MavenArtifactFacet mavenArtifactFacet =
(MavenArtifactFacet) artifactMetadata.getFacet(
MavenArtifactFacet.FACET_ID );
if ( StringUtils.equals( artifactInfo.classifier,
mavenArtifactFacet.getClassifier( ) ) )
{
artifactMetadata.removeFacet( MavenArtifactFacet.FACET_ID );
String groupId = artifactInfo.getNamespace( ), artifactId =
artifactInfo.getName( ),
version = artifactInfo.getProjectVersion( );
MavenArtifactFacet mavenArtifactFacetToCompare = new MavenArtifactFacet( );
mavenArtifactFacetToCompare.setClassifier( artifactInfo.getClassifier( ) );
metadataRepository.removeArtifact( repository.getId( ), groupId, artifactId,
version, mavenArtifactFacetToCompare );
metadataRepository.save( );
}
}
else
{
metadataRepository.removeArtifact( artifactMetadata, artifactInfo.getProjectVersion( ) );
}
}
private void deleteSilently(Path path) {
try
{
Files.deleteIfExists( path );
triggerAuditEvent( repository.getRepository( ).getId( ), path.toString(), AuditEvent.PURGE_FILE );
}
catch ( IOException e )
{
log.error("Error occured during file deletion {}: {} ",path,e.getMessage(), e);
}
}
/**
* <p>
* This find support files for the artifactFile and deletes them.
* </p>
* <p>
* Support Files are things like ".sha1", ".md5", ".asc", etc.
* </p>
*
* @param artifactFile the file to base off of.
*/
private void purgeSupportFiles( Path artifactFile )
{
Path parentDir = artifactFile.getParent();
if ( !Files.exists(parentDir) )
{
return;
}
final String artifactName = artifactFile.getFileName().toString();
try
{
Files.find(parentDir, 3,
( path, basicFileAttributes ) -> path.getFileName().toString().startsWith(artifactName)
&& Files.isRegularFile( path ) ).forEach( this::deleteSilently );
}
catch ( IOException e )
{
log.error("Purge of support files failed {}: {}", artifactFile, e.getMessage(), e);
}
}
private void triggerAuditEvent( String repoId, String resource, String action )
{
String msg =
repoId + DELIM + "<system-purge>" + DELIM + "<system>" + DELIM + '\"' + resource + '\"' + DELIM + '\"' +
action + '\"';
logger.info( msg );
}
}