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.MetadataRepository;
027import org.apache.archiva.metadata.repository.MetadataRepositoryException;
028import org.apache.archiva.metadata.repository.MetadataResolutionException;
029import org.apache.archiva.metadata.repository.RepositorySession;
030import org.apache.archiva.model.ArtifactReference;
031import org.apache.archiva.repository.ContentNotFoundException;
032import org.apache.archiva.repository.ManagedRepositoryContent;
033import org.apache.archiva.repository.events.RepositoryListener;
034import org.apache.commons.lang.StringUtils;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038import java.io.IOException;
039import java.nio.file.DirectoryStream;
040import java.nio.file.FileVisitOption;
041import java.nio.file.FileVisitResult;
042import java.nio.file.Files;
043import java.nio.file.Path;
044import java.nio.file.SimpleFileVisitor;
045import java.nio.file.attribute.BasicFileAttributes;
046import java.util.Collection;
047import java.util.HashMap;
048import java.util.HashSet;
049import java.util.List;
050import java.util.Map;
051import java.util.Set;
052
053/**
054 * Base class for all repository purge tasks.
055 */
056public abstract class AbstractRepositoryPurge
057    implements RepositoryPurge
058{
059    protected Logger log = LoggerFactory.getLogger( getClass( ) );
060
061    protected final ManagedRepositoryContent repository;
062
063    protected final RepositorySession repositorySession;
064
065    protected final List<RepositoryListener> listeners;
066
067    private Logger logger = LoggerFactory.getLogger( "org.apache.archiva.AuditLog" );
068
069    private static final char DELIM = ' ';
070
071    public AbstractRepositoryPurge( ManagedRepositoryContent repository, RepositorySession repositorySession,
072                                    List<RepositoryListener> listeners )
073    {
074        this.repository = repository;
075        this.repositorySession = repositorySession;
076        this.listeners = listeners;
077    }
078
079    /*
080     * We have to track namespace, project, project version, artifact version and classifier
081     * There is no metadata class that contains all these properties.
082     */
083    class ArtifactInfo
084    {
085        final String namespace;
086        final String name;
087        final String projectVersion;
088        String version;
089        String classifier;
090
091        ArtifactInfo( String namespace, String name, String projectVersion, String version )
092        {
093            this.namespace = namespace;
094            this.name = name;
095            this.projectVersion = projectVersion;
096            this.version = version;
097        }
098
099        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}