This project has retired. For details please refer to its Attic page.
Source code
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}