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.metadata.repository.RepositorySession;
023import org.apache.archiva.repository.events.RepositoryListener;
024import org.apache.commons.lang.time.DateUtils;
025import org.apache.archiva.common.utils.VersionComparator;
026import org.apache.archiva.common.utils.VersionUtil;
027import org.apache.archiva.model.ArtifactReference;
028import org.apache.archiva.model.VersionedReference;
029import org.apache.archiva.repository.ContentNotFoundException;
030import org.apache.archiva.repository.ManagedRepositoryContent;
031import org.apache.archiva.repository.layout.LayoutException;
032
033import java.io.File;
034import java.text.ParseException;
035import java.text.SimpleDateFormat;
036import java.util.ArrayList;
037import java.util.Calendar;
038import java.util.Collections;
039import java.util.Date;
040import java.util.List;
041import java.util.Set;
042import java.util.regex.Matcher;
043
044/**
045 * Purge from repository all snapshots older than the specified days in the repository configuration.
046 */
047public class DaysOldRepositoryPurge
048    extends AbstractRepositoryPurge
049{
050    private SimpleDateFormat timestampParser;
051
052    private int daysOlder;
053
054    private int retentionCount;
055
056    public DaysOldRepositoryPurge( ManagedRepositoryContent repository, int daysOlder, int retentionCount,
057                                   RepositorySession repositorySession, List<RepositoryListener> listeners )
058    {
059        super( repository, repositorySession, listeners );
060        this.daysOlder = daysOlder;
061        this.retentionCount = retentionCount;
062        timestampParser = new SimpleDateFormat( "yyyyMMdd.HHmmss" );
063        timestampParser.setTimeZone( DateUtils.UTC_TIME_ZONE );
064    }
065
066    @Override
067    public void process( String path )
068        throws RepositoryPurgeException
069    {
070        try
071        {
072            File artifactFile = new File( repository.getRepoRoot(), path );
073
074            if ( !artifactFile.exists() )
075            {
076                return;
077            }
078
079            ArtifactReference artifact = repository.toArtifactReference( path );
080
081            Calendar olderThanThisDate = Calendar.getInstance( DateUtils.UTC_TIME_ZONE );
082            olderThanThisDate.add( Calendar.DATE, -daysOlder );
083
084            // respect retention count
085            VersionedReference reference = new VersionedReference();
086            reference.setGroupId( artifact.getGroupId() );
087            reference.setArtifactId( artifact.getArtifactId() );
088            reference.setVersion( artifact.getVersion() );
089
090            List<String> versions = new ArrayList<>( repository.getVersions( reference ) );
091
092            Collections.sort( versions, VersionComparator.getInstance() );
093
094            if ( retentionCount > versions.size() )
095            {
096                // Done. nothing to do here. skip it.
097                return;
098            }
099
100            int countToPurge = versions.size() - retentionCount;
101
102            for ( String version : versions )
103            {
104                if ( countToPurge-- <= 0 )
105                {
106                    break;
107                }
108
109                ArtifactReference newArtifactReference = repository.toArtifactReference(
110                    artifactFile.getAbsolutePath() );
111                newArtifactReference.setVersion( version );
112
113                File newArtifactFile = repository.toFile( newArtifactReference );
114
115                // Is this a generic snapshot "1.0-SNAPSHOT" ?
116                if ( VersionUtil.isGenericSnapshot( newArtifactReference.getVersion() ) )
117                {
118                    if ( newArtifactFile.lastModified() < olderThanThisDate.getTimeInMillis() )
119                    {
120                        doPurgeAllRelated( newArtifactReference );
121                    }
122                }
123                // Is this a timestamp snapshot "1.0-20070822.123456-42" ?
124                else if ( VersionUtil.isUniqueSnapshot( newArtifactReference.getVersion() ) )
125                {
126                    Calendar timestampCal = uniqueSnapshotToCalendar( newArtifactReference.getVersion() );
127
128                    if ( timestampCal.getTimeInMillis() < olderThanThisDate.getTimeInMillis() )
129                    {
130                        doPurgeAllRelated( newArtifactReference );
131                    }
132                }
133            }
134        }
135        catch ( ContentNotFoundException e )
136        {
137            throw new RepositoryPurgeException( e.getMessage(), e );
138        }
139        catch ( LayoutException e )
140        {
141            log.debug( "Not processing file that is not an artifact: {}", e.getMessage() );
142        }
143    }
144
145    private Calendar uniqueSnapshotToCalendar( String version )
146    {
147        // The latestVersion will contain the full version string "1.0-alpha-5-20070821.213044-8"
148        // This needs to be broken down into ${base}-${timestamp}-${build_number}
149
150        Matcher m = VersionUtil.UNIQUE_SNAPSHOT_PATTERN.matcher( version );
151        if ( m.matches() )
152        {
153            Matcher mtimestamp = VersionUtil.TIMESTAMP_PATTERN.matcher( m.group( 2 ) );
154            if ( mtimestamp.matches() )
155            {
156                String tsDate = mtimestamp.group( 1 );
157                String tsTime = mtimestamp.group( 2 );
158
159                Date versionDate;
160                try
161                {
162                    versionDate = timestampParser.parse( tsDate + "." + tsTime );
163                    Calendar cal = Calendar.getInstance( DateUtils.UTC_TIME_ZONE );
164                    cal.setTime( versionDate );
165
166                    return cal;
167                }
168                catch ( ParseException e )
169                {
170                    // Invalid Date/Time
171                    return null;
172                }
173            }
174        }
175        return null;
176    }
177
178    private void doPurgeAllRelated( ArtifactReference reference )
179    {
180        try
181        {
182            Set<ArtifactReference> related = repository.getRelatedArtifacts( reference );
183            purge( related );
184        }
185        catch ( ContentNotFoundException e )
186        {
187            // Nothing to do here - it means the repository would have been constructed incorrectly
188            log.debug( e.getMessage(), e );
189        }
190    }
191}