001package org.apache.archiva.consumers.core.repository; 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 org.apache.archiva.common.utils.VersionUtil; 023import org.apache.archiva.metadata.model.ArtifactMetadata; 024import org.apache.archiva.metadata.model.facets.AuditEvent; 025import org.apache.archiva.metadata.model.maven2.MavenArtifactFacet; 026import org.apache.archiva.metadata.repository.*; 027import org.apache.archiva.model.ArtifactReference; 028import org.apache.archiva.repository.ContentNotFoundException; 029import org.apache.archiva.repository.ManagedRepositoryContent; 030import org.apache.archiva.metadata.audit.RepositoryListener; 031import org.apache.archiva.repository.storage.StorageAsset; 032import org.apache.archiva.repository.storage.StorageUtil; 033import org.apache.commons.lang3.StringUtils; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037import java.io.IOException; 038import java.util.Collection; 039import java.util.HashMap; 040import java.util.HashSet; 041import java.util.List; 042import java.util.Map; 043import java.util.Set; 044 045/** 046 * Base class for all repository purge tasks. 047 */ 048public abstract class AbstractRepositoryPurge 049 implements RepositoryPurge 050{ 051 protected Logger log = LoggerFactory.getLogger( getClass( ) ); 052 053 protected final ManagedRepositoryContent repository; 054 055 protected final RepositorySession repositorySession; 056 057 protected final List<RepositoryListener> listeners; 058 059 private Logger logger = LoggerFactory.getLogger( "org.apache.archiva.AuditLog" ); 060 061 private static final char DELIM = ' '; 062 063 public AbstractRepositoryPurge( ManagedRepositoryContent repository, RepositorySession repositorySession, 064 List<RepositoryListener> listeners ) 065 { 066 this.repository = repository; 067 this.repositorySession = repositorySession; 068 this.listeners = listeners; 069 } 070 071 /* 072 * We have to track namespace, project, project version, artifact version and classifier 073 * There is no metadata class that contains all these properties. 074 */ 075 class ArtifactInfo 076 { 077 final String namespace; 078 final String name; 079 final String projectVersion; 080 String version; 081 String classifier; 082 083 ArtifactInfo( String namespace, String name, String projectVersion, String version ) 084 { 085 this.namespace = namespace; 086 this.name = name; 087 this.projectVersion = projectVersion; 088 this.version = version; 089 } 090 091 ArtifactInfo( String namespace, String name, String projectVersion ) 092 { 093 this.namespace = namespace; 094 this.name = name; 095 this.projectVersion = projectVersion; 096 } 097 098 /* 099 * Creates a info object without version and classifier 100 */ 101 ArtifactInfo projectVersionLevel( ) 102 { 103 return new ArtifactInfo( this.namespace, this.name, this.projectVersion ); 104 } 105 106 public void setClassifier( String classifier ) 107 { 108 this.classifier = classifier; 109 } 110 111 public String getNamespace( ) 112 { 113 return namespace; 114 } 115 116 public String getName( ) 117 { 118 return name; 119 } 120 121 public String getProjectVersion( ) 122 { 123 return projectVersion; 124 } 125 126 public String getVersion( ) 127 { 128 return version; 129 } 130 131 public String getClassifier( ) 132 { 133 return classifier; 134 } 135 136 public boolean hasClassifier( ) 137 { 138 return classifier != null && !"".equals( classifier ); 139 } 140 141 @Override 142 public boolean equals( Object o ) 143 { 144 if ( this == o ) return true; 145 if ( o == null || getClass( ) != o.getClass( ) ) return false; 146 147 ArtifactInfo that = (ArtifactInfo) o; 148 149 if ( !namespace.equals( that.namespace ) ) return false; 150 if ( !name.equals( that.name ) ) return false; 151 if ( !projectVersion.equals( that.projectVersion ) ) return false; 152 if ( !( version != null ? version.equals( that.version ) : that.version == null ) ) return false; 153 return classifier != null ? classifier.equals( that.classifier ) : that.classifier == null; 154 } 155 156 @Override 157 public int hashCode( ) 158 { 159 int result = namespace.hashCode( ); 160 result = 31 * result + name.hashCode( ); 161 result = 31 * result + projectVersion.hashCode( ); 162 result = 31 * result + ( version != null ? version.hashCode( ) : 0 ); 163 result = 31 * result + ( classifier != null ? classifier.hashCode( ) : 0 ); 164 return result; 165 } 166 167 @Override 168 public String toString( ) 169 { 170 final StringBuilder sb = new StringBuilder( "ArtifactInfo{" ); 171 sb.append( "namespace='" ).append( namespace ).append( '\'' ); 172 sb.append( ", name='" ).append( name ).append( '\'' ); 173 sb.append( ", projectVersion='" ).append( projectVersion ).append( '\'' ); 174 sb.append( ", version='" ).append( version ).append( '\'' ); 175 sb.append( ", classifier='" ).append( classifier ).append( '\'' ); 176 sb.append( '}' ); 177 return sb.toString( ); 178 } 179 } 180 181 /** 182 * Purge the repo. Update db and index of removed artifacts. 183 * 184 * @param references 185 */ 186 protected void purge( Set<ArtifactReference> references ) 187 { 188 if ( references != null && !references.isEmpty( ) ) 189 { 190 MetadataRepository metadataRepository = repositorySession.getRepository( ); 191 Map<ArtifactInfo, ArtifactMetadata> metaRemovalList = new HashMap<>( ); 192 Map<String, Collection<ArtifactMetadata>> metaResolved = new HashMap<>( ); 193 for ( ArtifactReference reference : references ) 194 { 195 String baseVersion = VersionUtil.getBaseVersion( reference.getVersion( ) ); 196 // Needed for tracking in the hashmap 197 String metaBaseId = reference.getGroupId( ) + "/" + reference.getArtifactId( ) + "/" + baseVersion; 198 199 if ( !metaResolved.containsKey( metaBaseId ) ) 200 { 201 try 202 { 203 metaResolved.put( metaBaseId, metadataRepository.getArtifacts(repositorySession, repository.getId( ), 204 reference.getGroupId( ), reference.getArtifactId( ), baseVersion ) ); 205 } 206 catch ( MetadataResolutionException e ) 207 { 208 log.error( "Error during metadata retrieval {}: {}", metaBaseId, e.getMessage( ) ); 209 } 210 } 211 StorageAsset artifactFile = repository.toFile( reference ); 212 213 for ( RepositoryListener listener : listeners ) 214 { 215 listener.deleteArtifact( metadataRepository, repository.getId( ), reference.getGroupId( ), 216 reference.getArtifactId( ), reference.getVersion( ), 217 artifactFile.getName( )); 218 } 219 try 220 { 221 artifactFile.getStorage().removeAsset(artifactFile); 222 log.debug( "File deleted: {}", artifactFile ); 223 } 224 catch ( IOException e ) 225 { 226 log.error( "Could not delete file {}: {}", artifactFile.toString(), e.getMessage( ), e ); 227 continue; 228 } 229 try 230 { 231 repository.deleteArtifact( reference ); 232 } 233 catch ( ContentNotFoundException e ) 234 { 235 log.warn( "skip error deleting artifact {}: {}", reference, e.getMessage( ) ); 236 } 237 238 boolean snapshotVersion = VersionUtil.isSnapshot( reference.getVersion( ) ); 239 240 241 // If this is a snapshot we have to search for artifacts with the same version. And remove all of them. 242 if ( snapshotVersion ) 243 { 244 Collection<ArtifactMetadata> artifacts = 245 metaResolved.get( metaBaseId ); 246 if ( artifacts != null ) 247 { 248 // cleanup snapshots metadata 249 for ( ArtifactMetadata artifactMetadata : artifacts ) 250 { 251 // Artifact metadata and reference version should match. 252 if ( artifactMetadata.getVersion( ).equals( reference.getVersion( ) ) ) 253 { 254 ArtifactInfo info = new ArtifactInfo( artifactMetadata.getNamespace( ), artifactMetadata.getProject( ), artifactMetadata.getProjectVersion( ), artifactMetadata.getVersion( ) ); 255 if ( StringUtils.isNotBlank( reference.getClassifier( ) ) ) 256 { 257 info.setClassifier( reference.getClassifier( ) ); 258 metaRemovalList.put( info, artifactMetadata ); 259 } 260 else 261 { 262 // metadataRepository.removeTimestampedArtifact( artifactMetadata, baseVersion ); 263 metaRemovalList.put( info, artifactMetadata ); 264 } 265 } 266 } 267 } 268 } 269 else // otherwise we delete the artifact version 270 { 271 ArtifactInfo info = new ArtifactInfo( reference.getGroupId( ), reference.getArtifactId( ), baseVersion, reference.getVersion( ) ); 272 for ( ArtifactMetadata metadata : metaResolved.get( metaBaseId ) ) 273 { 274 metaRemovalList.put( info, metadata ); 275 } 276 } 277 triggerAuditEvent( repository.getRepository( ).getId( ), ArtifactReference.toKey( reference ), 278 AuditEvent.PURGE_ARTIFACT ); 279 purgeSupportFiles( artifactFile ); 280 } 281 purgeMetadata( metadataRepository, metaRemovalList ); 282 try 283 { 284 repositorySession.save( ); 285 } 286 catch ( org.apache.archiva.metadata.repository.MetadataSessionException e ) 287 { 288 e.printStackTrace( ); 289 } 290 291 } 292 } 293 294 /* 295 * Purges the metadata. First removes the artifacts. After that empty versions will be removed. 296 */ 297 private void purgeMetadata( MetadataRepository metadataRepository, Map<ArtifactInfo, ArtifactMetadata> dataList ) 298 { 299 Set<ArtifactInfo> projectLevelMetadata = new HashSet<>( ); 300 for ( Map.Entry<ArtifactInfo, ArtifactMetadata> infoEntry : dataList.entrySet( ) ) 301 { 302 ArtifactInfo info = infoEntry.getKey( ); 303 try 304 { 305 removeArtifact( metadataRepository, info, infoEntry.getValue( ) ); 306 log.debug( "Removed artifact from MetadataRepository {}", info ); 307 } 308 catch ( MetadataRepositoryException e ) 309 { 310 log.error( "Could not remove artifact from MetadataRepository {}: {}", info, e.getMessage( ), e ); 311 } 312 projectLevelMetadata.add( info.projectVersionLevel( ) ); 313 } 314 try { 315 repositorySession.save( ); 316 } catch (MetadataSessionException e) { 317 log.error("Could not save sesion {}", e.getMessage()); 318 } 319 Collection<ArtifactMetadata> artifacts = null; 320 // Get remaining artifacts and remove project if empty 321 for ( ArtifactInfo info : projectLevelMetadata ) 322 { 323 try 324 { 325 artifacts = metadataRepository.getArtifacts(repositorySession , repository.getId( ), info.getNamespace( ), 326 info.getName( ), info.getProjectVersion( ) ); 327 if ( artifacts.size( ) == 0 ) 328 { 329 metadataRepository.removeProjectVersion(repositorySession , repository.getId( ), 330 info.getNamespace( ), info.getName( ), info.getProjectVersion( ) ); 331 log.debug( "Removed project version from MetadataRepository {}", info ); 332 } 333 } 334 catch ( MetadataResolutionException | MetadataRepositoryException e ) 335 { 336 log.error( "Could not remove project version from MetadataRepository {}: {}", info, e.getMessage( ), e ); 337 } 338 } 339 try { 340 repositorySession.save( ); 341 } catch (MetadataSessionException e) { 342 log.error("Could not save sesion {}", e.getMessage()); 343 344 } 345 346 } 347 348 /* 349 * Removes the artifact from the metadataRepository. If a classifier is set, the facet will be removed. 350 */ 351 private void removeArtifact( MetadataRepository metadataRepository, ArtifactInfo artifactInfo, ArtifactMetadata artifactMetadata ) throws MetadataRepositoryException 352 { 353 if ( artifactInfo.hasClassifier( ) ) 354 { 355 // cleanup facet which contains classifier information 356 MavenArtifactFacet mavenArtifactFacet = 357 (MavenArtifactFacet) artifactMetadata.getFacet( 358 MavenArtifactFacet.FACET_ID ); 359 360 if ( StringUtils.equals( artifactInfo.classifier, 361 mavenArtifactFacet.getClassifier( ) ) ) 362 { 363 artifactMetadata.removeFacet( MavenArtifactFacet.FACET_ID ); 364 String groupId = artifactInfo.getNamespace( ), artifactId = 365 artifactInfo.getName( ), 366 version = artifactInfo.getProjectVersion( ); 367 MavenArtifactFacet mavenArtifactFacetToCompare = new MavenArtifactFacet( ); 368 mavenArtifactFacetToCompare.setClassifier( artifactInfo.getClassifier( ) ); 369 metadataRepository.removeFacetFromArtifact(repositorySession , repository.getId( ), groupId, 370 artifactId, version, mavenArtifactFacetToCompare ); 371 try { 372 repositorySession.save( ); 373 } catch (MetadataSessionException e) { 374 log.error("Could not save session {}", e.getMessage()); 375 } 376 } 377 } 378 else 379 { 380 metadataRepository.removeTimestampedArtifact(repositorySession , artifactMetadata, artifactInfo.getProjectVersion( ) ); 381 } 382 } 383 384 private void deleteSilently( StorageAsset path ) 385 { 386 try 387 { 388 path.getStorage().removeAsset(path); 389 triggerAuditEvent( repository.getRepository( ).getId( ), path.toString( ), AuditEvent.PURGE_FILE ); 390 } 391 catch ( IOException e ) 392 { 393 log.error( "Error occured during file deletion {}: {} ", path, e.getMessage( ), e ); 394 } 395 } 396 397 /** 398 * <p> 399 * This find support files for the artifactFile and deletes them. 400 * </p> 401 * <p> 402 * Support Files are things like ".sha1", ".md5", ".asc", etc. 403 * </p> 404 * 405 * @param artifactFile the file to base off of. 406 */ 407 private void purgeSupportFiles( StorageAsset artifactFile ) 408 { 409 StorageAsset parentDir = artifactFile.getParent( ); 410 411 if ( !parentDir.exists() ) 412 { 413 return; 414 } 415 416 final String artifactName = artifactFile.getName( ); 417 418 try 419 { 420 421 StorageUtil.recurse(parentDir, a -> { 422 if (!a.isContainer() && a.getName().startsWith(artifactName)) deleteSilently(a); 423 }, true, 3 ); 424 } 425 catch ( IOException e ) 426 { 427 log.error( "Purge of support files failed {}: {}", artifactFile, e.getMessage( ), e ); 428 } 429 430 } 431 432 private void triggerAuditEvent( String repoId, String resource, String action ) 433 { 434 String msg = 435 repoId + DELIM + "<system-purge>" + DELIM + "<system>" + DELIM + '\"' + resource + '\"' + DELIM + '\"' + 436 action + '\"'; 437 438 logger.info( msg ); 439 } 440}