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