You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AbstractRepositoryPurge.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. package org.apache.archiva.consumers.core.repository;
  2. /*
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing,
  14. * software distributed under the License is distributed on an
  15. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16. * KIND, either express or implied. See the License for the
  17. * specific language governing permissions and limitations
  18. * under the License.
  19. */
  20. import org.apache.archiva.common.utils.VersionUtil;
  21. import org.apache.archiva.metadata.model.ArtifactMetadata;
  22. import org.apache.archiva.metadata.model.facets.AuditEvent;
  23. import org.apache.archiva.metadata.model.maven2.MavenArtifactFacet;
  24. import org.apache.archiva.metadata.repository.MetadataRepository;
  25. import org.apache.archiva.metadata.repository.MetadataRepositoryException;
  26. import org.apache.archiva.metadata.repository.MetadataResolutionException;
  27. import org.apache.archiva.metadata.repository.RepositorySession;
  28. import org.apache.archiva.model.ArtifactReference;
  29. import org.apache.archiva.repository.ContentNotFoundException;
  30. import org.apache.archiva.repository.ManagedRepositoryContent;
  31. import org.apache.archiva.repository.events.RepositoryListener;
  32. import org.apache.commons.lang.StringUtils;
  33. import org.slf4j.Logger;
  34. import org.slf4j.LoggerFactory;
  35. import java.io.IOException;
  36. import java.nio.file.Files;
  37. import java.nio.file.Path;
  38. import java.util.Collection;
  39. import java.util.HashMap;
  40. import java.util.HashSet;
  41. import java.util.List;
  42. import java.util.Map;
  43. import java.util.Set;
  44. /**
  45. * Base class for all repository purge tasks.
  46. */
  47. public abstract class AbstractRepositoryPurge
  48. implements RepositoryPurge
  49. {
  50. protected Logger log = LoggerFactory.getLogger( getClass( ) );
  51. protected final ManagedRepositoryContent repository;
  52. protected final RepositorySession repositorySession;
  53. protected final List<RepositoryListener> listeners;
  54. private Logger logger = LoggerFactory.getLogger( "org.apache.archiva.AuditLog" );
  55. private static final char DELIM = ' ';
  56. public AbstractRepositoryPurge( ManagedRepositoryContent repository, RepositorySession repositorySession,
  57. List<RepositoryListener> listeners )
  58. {
  59. this.repository = repository;
  60. this.repositorySession = repositorySession;
  61. this.listeners = listeners;
  62. }
  63. /*
  64. * We have to track namespace, project, project version, artifact version and classifier
  65. * There is no metadata class that contains all these properties.
  66. */
  67. class ArtifactInfo
  68. {
  69. final String namespace;
  70. final String name;
  71. final String projectVersion;
  72. String version;
  73. String classifier;
  74. ArtifactInfo( String namespace, String name, String projectVersion, String version )
  75. {
  76. this.namespace = namespace;
  77. this.name = name;
  78. this.projectVersion = projectVersion;
  79. this.version = version;
  80. }
  81. ArtifactInfo( String namespace, String name, String projectVersion )
  82. {
  83. this.namespace = namespace;
  84. this.name = name;
  85. this.projectVersion = projectVersion;
  86. }
  87. /*
  88. * Creates a info object without version and classifier
  89. */
  90. ArtifactInfo projectVersionLevel( )
  91. {
  92. return new ArtifactInfo( this.namespace, this.name, this.projectVersion );
  93. }
  94. public void setClassifier( String classifier )
  95. {
  96. this.classifier = classifier;
  97. }
  98. public String getNamespace( )
  99. {
  100. return namespace;
  101. }
  102. public String getName( )
  103. {
  104. return name;
  105. }
  106. public String getProjectVersion( )
  107. {
  108. return projectVersion;
  109. }
  110. public String getVersion( )
  111. {
  112. return version;
  113. }
  114. public String getClassifier( )
  115. {
  116. return classifier;
  117. }
  118. public boolean hasClassifier( )
  119. {
  120. return classifier != null && !"".equals( classifier );
  121. }
  122. @Override
  123. public boolean equals( Object o )
  124. {
  125. if ( this == o ) return true;
  126. if ( o == null || getClass( ) != o.getClass( ) ) return false;
  127. ArtifactInfo that = (ArtifactInfo) o;
  128. if ( !namespace.equals( that.namespace ) ) return false;
  129. if ( !name.equals( that.name ) ) return false;
  130. if ( !projectVersion.equals( that.projectVersion ) ) return false;
  131. if ( !( version != null ? version.equals( that.version ) : that.version == null ) ) return false;
  132. return classifier != null ? classifier.equals( that.classifier ) : that.classifier == null;
  133. }
  134. @Override
  135. public int hashCode( )
  136. {
  137. int result = namespace.hashCode( );
  138. result = 31 * result + name.hashCode( );
  139. result = 31 * result + projectVersion.hashCode( );
  140. result = 31 * result + ( version != null ? version.hashCode( ) : 0 );
  141. result = 31 * result + ( classifier != null ? classifier.hashCode( ) : 0 );
  142. return result;
  143. }
  144. @Override
  145. public String toString( )
  146. {
  147. final StringBuilder sb = new StringBuilder( "ArtifactInfo{" );
  148. sb.append( "namespace='" ).append( namespace ).append( '\'' );
  149. sb.append( ", name='" ).append( name ).append( '\'' );
  150. sb.append( ", projectVersion='" ).append( projectVersion ).append( '\'' );
  151. sb.append( ", version='" ).append( version ).append( '\'' );
  152. sb.append( ", classifier='" ).append( classifier ).append( '\'' );
  153. sb.append( '}' );
  154. return sb.toString( );
  155. }
  156. }
  157. /**
  158. * Purge the repo. Update db and index of removed artifacts.
  159. *
  160. * @param references
  161. */
  162. protected void purge( Set<ArtifactReference> references )
  163. {
  164. if ( references != null && !references.isEmpty( ) )
  165. {
  166. MetadataRepository metadataRepository = repositorySession.getRepository( );
  167. Map<ArtifactInfo, ArtifactMetadata> metaRemovalList = new HashMap<>( );
  168. Map<String, Collection<ArtifactMetadata>> metaResolved = new HashMap<>( );
  169. for ( ArtifactReference reference : references )
  170. {
  171. String baseVersion = VersionUtil.getBaseVersion( reference.getVersion( ) );
  172. // Needed for tracking in the hashmap
  173. String metaBaseId = reference.getGroupId( ) + "/" + reference.getArtifactId( ) + "/" + baseVersion;
  174. if ( !metaResolved.containsKey( metaBaseId ) )
  175. {
  176. try
  177. {
  178. metaResolved.put( metaBaseId, metadataRepository.getArtifacts( repository.getId( ), reference.getGroupId( ),
  179. reference.getArtifactId( ), baseVersion ) );
  180. }
  181. catch ( MetadataResolutionException e )
  182. {
  183. log.error( "Error during metadata retrieval {}: {}", metaBaseId, e.getMessage( ) );
  184. }
  185. }
  186. Path artifactFile = repository.toFile( reference ).toPath( );
  187. for ( RepositoryListener listener : listeners )
  188. {
  189. listener.deleteArtifact( metadataRepository, repository.getId( ), reference.getGroupId( ),
  190. reference.getArtifactId( ), reference.getVersion( ),
  191. artifactFile.getFileName( ).toString( ) );
  192. }
  193. try
  194. {
  195. Files.delete( artifactFile );
  196. log.debug( "File deleted: {}", artifactFile.toAbsolutePath( ) );
  197. }
  198. catch ( IOException e )
  199. {
  200. log.error( "Could not delete file {}: {}", artifactFile.toAbsolutePath( ), e.getMessage( ), e );
  201. continue;
  202. }
  203. try
  204. {
  205. repository.deleteArtifact( reference );
  206. }
  207. catch ( ContentNotFoundException e )
  208. {
  209. log.warn( "skip error deleting artifact {}: {}", reference, e.getMessage( ) );
  210. }
  211. boolean snapshotVersion = VersionUtil.isSnapshot( reference.getVersion( ) );
  212. // If this is a snapshot we have to search for artifacts with the same version. And remove all of them.
  213. if ( snapshotVersion )
  214. {
  215. Collection<ArtifactMetadata> artifacts =
  216. metaResolved.get( metaBaseId );
  217. if ( artifacts != null )
  218. {
  219. // cleanup snapshots metadata
  220. for ( ArtifactMetadata artifactMetadata : artifacts )
  221. {
  222. // Artifact metadata and reference version should match.
  223. if ( artifactMetadata.getVersion( ).equals( reference.getVersion( ) ) )
  224. {
  225. ArtifactInfo info = new ArtifactInfo( artifactMetadata.getNamespace( ), artifactMetadata.getProject( ), artifactMetadata.getProjectVersion( ), artifactMetadata.getVersion( ) );
  226. if ( StringUtils.isNotBlank( reference.getClassifier( ) ) )
  227. {
  228. info.setClassifier( reference.getClassifier( ) );
  229. metaRemovalList.put( info, artifactMetadata );
  230. }
  231. else
  232. {
  233. // metadataRepository.removeArtifact( artifactMetadata, baseVersion );
  234. metaRemovalList.put( info, artifactMetadata );
  235. }
  236. }
  237. }
  238. }
  239. }
  240. else // otherwise we delete the artifact version
  241. {
  242. ArtifactInfo info = new ArtifactInfo( reference.getGroupId( ), reference.getArtifactId( ), baseVersion, reference.getVersion( ) );
  243. for ( ArtifactMetadata metadata : metaResolved.get( metaBaseId ) )
  244. {
  245. metaRemovalList.put( info, metadata );
  246. }
  247. }
  248. triggerAuditEvent( repository.getRepository( ).getId( ), ArtifactReference.toKey( reference ),
  249. AuditEvent.PURGE_ARTIFACT );
  250. purgeSupportFiles( artifactFile );
  251. }
  252. purgeMetadata( metadataRepository, metaRemovalList );
  253. repositorySession.save( );
  254. }
  255. }
  256. /*
  257. * Purges the metadata. First removes the artifacts. After that empty versions will be removed.
  258. */
  259. private void purgeMetadata( MetadataRepository metadataRepository, Map<ArtifactInfo, ArtifactMetadata> dataList )
  260. {
  261. Set<ArtifactInfo> projectLevelMetadata = new HashSet<>( );
  262. for ( Map.Entry<ArtifactInfo, ArtifactMetadata> infoEntry : dataList.entrySet( ) )
  263. {
  264. ArtifactInfo info = infoEntry.getKey( );
  265. try
  266. {
  267. removeArtifact( metadataRepository, info, infoEntry.getValue( ) );
  268. log.debug( "Removed artifact from MetadataRepository {}", info );
  269. }
  270. catch ( MetadataRepositoryException e )
  271. {
  272. log.error( "Could not remove artifact from MetadataRepository {}: {}", info, e.getMessage( ), e );
  273. }
  274. projectLevelMetadata.add( info.projectVersionLevel( ) );
  275. }
  276. metadataRepository.save( );
  277. Collection<ArtifactMetadata> artifacts = null;
  278. // Get remaining artifacts and remove project if empty
  279. for ( ArtifactInfo info : projectLevelMetadata )
  280. {
  281. try
  282. {
  283. artifacts = metadataRepository.getArtifacts( repository.getId( ), info.getNamespace( ), info.getName( ),
  284. info.getProjectVersion( ) );
  285. if ( artifacts.size( ) == 0 )
  286. {
  287. metadataRepository.removeProjectVersion( repository.getId( ), info.getNamespace( ),
  288. info.getName( ), info.getProjectVersion( ) );
  289. log.debug( "Removed project version from MetadataRepository {}", info );
  290. }
  291. }
  292. catch ( MetadataResolutionException | MetadataRepositoryException e )
  293. {
  294. log.error( "Could not remove project version from MetadataRepository {}: {}", info, e.getMessage( ), e );
  295. }
  296. }
  297. metadataRepository.save( );
  298. }
  299. /*
  300. * Removes the artifact from the metadataRepository. If a classifier is set, the facet will be removed.
  301. */
  302. private void removeArtifact( MetadataRepository metadataRepository, ArtifactInfo artifactInfo, ArtifactMetadata artifactMetadata ) throws MetadataRepositoryException
  303. {
  304. if ( artifactInfo.hasClassifier( ) )
  305. {
  306. // cleanup facet which contains classifier information
  307. MavenArtifactFacet mavenArtifactFacet =
  308. (MavenArtifactFacet) artifactMetadata.getFacet(
  309. MavenArtifactFacet.FACET_ID );
  310. if ( StringUtils.equals( artifactInfo.classifier,
  311. mavenArtifactFacet.getClassifier( ) ) )
  312. {
  313. artifactMetadata.removeFacet( MavenArtifactFacet.FACET_ID );
  314. String groupId = artifactInfo.getNamespace( ), artifactId =
  315. artifactInfo.getName( ),
  316. version = artifactInfo.getProjectVersion( );
  317. MavenArtifactFacet mavenArtifactFacetToCompare = new MavenArtifactFacet( );
  318. mavenArtifactFacetToCompare.setClassifier( artifactInfo.getClassifier( ) );
  319. metadataRepository.removeArtifact( repository.getId( ), groupId, artifactId,
  320. version, mavenArtifactFacetToCompare );
  321. metadataRepository.save( );
  322. }
  323. }
  324. else
  325. {
  326. metadataRepository.removeArtifact( artifactMetadata, artifactInfo.getProjectVersion( ) );
  327. }
  328. }
  329. private void deleteSilently( Path path )
  330. {
  331. try
  332. {
  333. Files.deleteIfExists( path );
  334. triggerAuditEvent( repository.getRepository( ).getId( ), path.toString( ), AuditEvent.PURGE_FILE );
  335. }
  336. catch ( IOException e )
  337. {
  338. log.error( "Error occured during file deletion {}: {} ", path, e.getMessage( ), e );
  339. }
  340. }
  341. /**
  342. * <p>
  343. * This find support files for the artifactFile and deletes them.
  344. * </p>
  345. * <p>
  346. * Support Files are things like ".sha1", ".md5", ".asc", etc.
  347. * </p>
  348. *
  349. * @param artifactFile the file to base off of.
  350. */
  351. private void purgeSupportFiles( Path artifactFile )
  352. {
  353. Path parentDir = artifactFile.getParent( );
  354. if ( !Files.exists( parentDir ) )
  355. {
  356. return;
  357. }
  358. final String artifactName = artifactFile.getFileName( ).toString( );
  359. try
  360. {
  361. Files.find( parentDir, 3,
  362. ( path, basicFileAttributes ) -> path.getFileName( ).toString( ).startsWith( artifactName )
  363. && Files.isRegularFile( path ) ).forEach( this::deleteSilently );
  364. }
  365. catch ( IOException e )
  366. {
  367. log.error( "Purge of support files failed {}: {}", artifactFile, e.getMessage( ), e );
  368. }
  369. }
  370. private void triggerAuditEvent( String repoId, String resource, String action )
  371. {
  372. String msg =
  373. repoId + DELIM + "<system-purge>" + DELIM + "<system>" + DELIM + '\"' + resource + '\"' + DELIM + '\"' +
  374. action + '\"';
  375. logger.info( msg );
  376. }
  377. }