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 17KB

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