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