001    package org.apache.archiva.repository.metadata;
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    
022    import org.apache.archiva.checksum.ChecksumAlgorithm;
023    import org.apache.archiva.checksum.ChecksummedFile;
024    import org.apache.archiva.common.utils.PathUtil;
025    import org.apache.archiva.common.utils.VersionComparator;
026    import org.apache.archiva.common.utils.VersionUtil;
027    import org.apache.archiva.configuration.ArchivaConfiguration;
028    import org.apache.archiva.configuration.ConfigurationNames;
029    import org.apache.archiva.configuration.FileTypes;
030    import org.apache.archiva.configuration.ProxyConnectorConfiguration;
031    import org.apache.archiva.maven2.metadata.MavenMetadataReader;
032    import org.apache.archiva.model.ArchivaRepositoryMetadata;
033    import org.apache.archiva.model.ArtifactReference;
034    import org.apache.archiva.model.Plugin;
035    import org.apache.archiva.model.ProjectReference;
036    import org.apache.archiva.model.SnapshotVersion;
037    import org.apache.archiva.model.VersionedReference;
038    import org.apache.archiva.redback.components.registry.Registry;
039    import org.apache.archiva.redback.components.registry.RegistryListener;
040    import org.apache.archiva.repository.ContentNotFoundException;
041    import org.apache.archiva.repository.ManagedRepositoryContent;
042    import org.apache.archiva.repository.RemoteRepositoryContent;
043    import org.apache.archiva.repository.layout.LayoutException;
044    import org.apache.archiva.xml.XMLException;
045    import org.apache.commons.collections.CollectionUtils;
046    import org.apache.commons.io.FileUtils;
047    import org.apache.commons.lang.StringUtils;
048    import org.apache.commons.lang.math.NumberUtils;
049    import org.apache.commons.lang.time.DateUtils;
050    import org.slf4j.Logger;
051    import org.slf4j.LoggerFactory;
052    import org.springframework.stereotype.Service;
053    
054    import javax.annotation.PostConstruct;
055    import javax.inject.Inject;
056    import javax.inject.Named;
057    import java.io.File;
058    import java.io.IOException;
059    import java.text.ParseException;
060    import java.text.SimpleDateFormat;
061    import java.util.ArrayList;
062    import java.util.Calendar;
063    import java.util.Collection;
064    import java.util.Collections;
065    import java.util.Date;
066    import java.util.HashMap;
067    import java.util.HashSet;
068    import java.util.Iterator;
069    import java.util.LinkedHashSet;
070    import java.util.List;
071    import java.util.Map;
072    import java.util.Set;
073    import java.util.regex.Matcher;
074    
075    /**
076     * MetadataTools
077     *
078     *
079     */
080    @Service( "metadataTools#default" )
081    public class MetadataTools
082        implements RegistryListener
083    {
084        private Logger log = LoggerFactory.getLogger( getClass() );
085    
086        public static final String MAVEN_METADATA = "maven-metadata.xml";
087    
088        public static final String MAVEN_ARCHETYPE_CATALOG ="archetype-catalog.xml";
089    
090        private static final char PATH_SEPARATOR = '/';
091    
092        private static final char GROUP_SEPARATOR = '.';
093    
094        /**
095         *
096         */
097        @Inject
098        @Named( value = "archivaConfiguration#default" )
099        private ArchivaConfiguration configuration;
100    
101        /**
102         *
103         */
104        @Inject
105        @Named( value = "fileTypes" )
106        private FileTypes filetypes;
107    
108        private ChecksumAlgorithm[] algorithms = new ChecksumAlgorithm[]{ ChecksumAlgorithm.SHA1, ChecksumAlgorithm.MD5 };
109    
110        private List<String> artifactPatterns;
111    
112        private Map<String, Set<String>> proxies;
113    
114        private static final char NUMS[] = new char[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
115    
116        private SimpleDateFormat lastUpdatedFormat;
117    
118        public MetadataTools()
119        {
120            lastUpdatedFormat = new SimpleDateFormat( "yyyyMMddHHmmss" );
121            lastUpdatedFormat.setTimeZone( DateUtils.UTC_TIME_ZONE );
122        }
123    
124        public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
125        {
126            if ( ConfigurationNames.isProxyConnector( propertyName ) )
127            {
128                initConfigVariables();
129            }
130        }
131    
132        public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
133        {
134            /* nothing to do */
135        }
136    
137        /**
138         * Gather the set of snapshot versions found in a particular versioned reference.
139         *
140         * @return the Set of snapshot artifact versions found.
141         * @throws LayoutException
142         * @throws ContentNotFoundException
143         */
144        public Set<String> gatherSnapshotVersions( ManagedRepositoryContent managedRepository,
145                                                   VersionedReference reference )
146            throws LayoutException, IOException, ContentNotFoundException
147        {
148            Set<String> foundVersions = managedRepository.getVersions( reference );
149    
150            // Next gather up the referenced 'latest' versions found in any proxied repositories
151            // maven-metadata-${proxyId}.xml files that may be present.
152    
153            // Does this repository have a set of remote proxied repositories?
154            Set<String> proxiedRepoIds = this.proxies.get( managedRepository.getId() );
155    
156            if ( CollectionUtils.isNotEmpty( proxiedRepoIds ) )
157            {
158                String baseVersion = VersionUtil.getBaseVersion( reference.getVersion() );
159                baseVersion = baseVersion.substring( 0, baseVersion.indexOf( VersionUtil.SNAPSHOT ) - 1 );
160    
161                // Add in the proxied repo version ids too.
162                Iterator<String> it = proxiedRepoIds.iterator();
163                while ( it.hasNext() )
164                {
165                    String proxyId = it.next();
166    
167                    ArchivaRepositoryMetadata proxyMetadata = readProxyMetadata( managedRepository, reference, proxyId );
168                    if ( proxyMetadata == null )
169                    {
170                        // There is no proxy metadata, skip it.
171                        continue;
172                    }
173    
174                    // Is there some snapshot info?
175                    SnapshotVersion snapshot = proxyMetadata.getSnapshotVersion();
176                    if ( snapshot != null )
177                    {
178                        String timestamp = snapshot.getTimestamp();
179                        int buildNumber = snapshot.getBuildNumber();
180    
181                        // Only interested in the timestamp + buildnumber.
182                        if ( StringUtils.isNotBlank( timestamp ) && ( buildNumber > 0 ) )
183                        {
184                            foundVersions.add( baseVersion + "-" + timestamp + "-" + buildNumber );
185                        }
186                    }
187                }
188            }
189    
190            return foundVersions;
191        }
192    
193        /**
194         * Take a path to a maven-metadata.xml, and attempt to translate it to a VersionedReference.
195         *
196         * @param path
197         * @return
198         */
199        public VersionedReference toVersionedReference( String path )
200            throws RepositoryMetadataException
201        {
202            if ( !path.endsWith( "/" + MAVEN_METADATA ) )
203            {
204                throw new RepositoryMetadataException( "Cannot convert to versioned reference, not a metadata file. " );
205            }
206    
207            VersionedReference reference = new VersionedReference();
208    
209            String normalizedPath = StringUtils.replace( path, "\\", "/" );
210            String pathParts[] = StringUtils.split( normalizedPath, '/' );
211    
212            int versionOffset = pathParts.length - 2;
213            int artifactIdOffset = versionOffset - 1;
214            int groupIdEnd = artifactIdOffset - 1;
215    
216            reference.setVersion( pathParts[versionOffset] );
217    
218            if ( !hasNumberAnywhere( reference.getVersion() ) )
219            {
220                // Scary check, but without it, all paths are version references;
221                throw new RepositoryMetadataException(
222                    "Not a versioned reference, as version id on path has no number in it." );
223            }
224    
225            reference.setArtifactId( pathParts[artifactIdOffset] );
226    
227            StringBuilder gid = new StringBuilder();
228            for ( int i = 0; i <= groupIdEnd; i++ )
229            {
230                if ( i > 0 )
231                {
232                    gid.append( "." );
233                }
234                gid.append( pathParts[i] );
235            }
236    
237            reference.setGroupId( gid.toString() );
238    
239            return reference;
240        }
241    
242        private boolean hasNumberAnywhere( String version )
243        {
244            return StringUtils.indexOfAny( version, NUMS ) != ( -1 );
245        }
246    
247        public ProjectReference toProjectReference( String path )
248            throws RepositoryMetadataException
249        {
250            if ( !path.endsWith( "/" + MAVEN_METADATA ) )
251            {
252                throw new RepositoryMetadataException( "Cannot convert to versioned reference, not a metadata file. " );
253            }
254    
255            ProjectReference reference = new ProjectReference();
256    
257            String normalizedPath = StringUtils.replace( path, "\\", "/" );
258            String pathParts[] = StringUtils.split( normalizedPath, '/' );
259    
260            // Assume last part of the path is the version.
261    
262            int artifactIdOffset = pathParts.length - 2;
263            int groupIdEnd = artifactIdOffset - 1;
264    
265            reference.setArtifactId( pathParts[artifactIdOffset] );
266    
267            StringBuilder gid = new StringBuilder();
268            for ( int i = 0; i <= groupIdEnd; i++ )
269            {
270                if ( i > 0 )
271                {
272                    gid.append( "." );
273                }
274                gid.append( pathParts[i] );
275            }
276    
277            reference.setGroupId( gid.toString() );
278    
279            return reference;
280        }
281    
282        public String toPath( ProjectReference reference )
283        {
284            StringBuilder path = new StringBuilder();
285    
286            path.append( formatAsDirectory( reference.getGroupId() ) ).append( PATH_SEPARATOR );
287            path.append( reference.getArtifactId() ).append( PATH_SEPARATOR );
288            path.append( MAVEN_METADATA );
289    
290            return path.toString();
291        }
292    
293        public String toPath( VersionedReference reference )
294        {
295            StringBuilder path = new StringBuilder();
296    
297            path.append( formatAsDirectory( reference.getGroupId() ) ).append( PATH_SEPARATOR );
298            path.append( reference.getArtifactId() ).append( PATH_SEPARATOR );
299            if ( reference.getVersion() != null )
300            {
301                // add the version only if it is present
302                path.append( VersionUtil.getBaseVersion( reference.getVersion() ) ).append( PATH_SEPARATOR );
303            }
304            path.append( MAVEN_METADATA );
305    
306            return path.toString();
307        }
308    
309        private String formatAsDirectory( String directory )
310        {
311            return directory.replace( GROUP_SEPARATOR, PATH_SEPARATOR );
312        }
313    
314        /**
315         * Adjusts a path for a metadata.xml file to its repository specific path.
316         *
317         * @param repository the repository to base new path off of.
318         * @param path       the path to the metadata.xml file to adjust the name of.
319         * @return the newly adjusted path reference to the repository specific metadata path.
320         */
321        public String getRepositorySpecificName( RemoteRepositoryContent repository, String path )
322        {
323            return getRepositorySpecificName( repository.getId(), path );
324        }
325    
326        /**
327         * Adjusts a path for a metadata.xml file to its repository specific path.
328         *
329         * @param proxyId the repository id to base new path off of.
330         * @param path    the path to the metadata.xml file to adjust the name of.
331         * @return the newly adjusted path reference to the repository specific metadata path.
332         */
333        public String getRepositorySpecificName( String proxyId, String path )
334        {
335            StringBuilder ret = new StringBuilder();
336    
337            int idx = path.lastIndexOf( '/' );
338            if ( idx > 0 )
339            {
340                ret.append( path.substring( 0, idx + 1 ) );
341            }
342    
343            // TODO: need to filter out 'bad' characters from the proxy id.
344            ret.append( "maven-metadata-" ).append( proxyId ).append( ".xml" );
345    
346            return ret.toString();
347        }
348    
349        @PostConstruct
350        public void initialize()
351        {
352            this.artifactPatterns = new ArrayList<String>();
353            this.proxies = new HashMap<String, Set<String>>();
354            initConfigVariables();
355    
356            configuration.addChangeListener( this );
357        }
358    
359        public ArchivaRepositoryMetadata readProxyMetadata( ManagedRepositoryContent managedRepository,
360                                                            ProjectReference reference, String proxyId )
361        {
362            String metadataPath = getRepositorySpecificName( proxyId, toPath( reference ) );
363            File metadataFile = new File( managedRepository.getRepoRoot(), metadataPath );
364    
365            if ( !metadataFile.exists() || !metadataFile.isFile() )
366            {
367                // Nothing to do. return null.
368                return null;
369            }
370    
371            try
372            {
373                return MavenMetadataReader.read( metadataFile );
374            }
375            catch ( XMLException e )
376            {
377                // TODO: [monitor] consider a monitor for this event.
378                // TODO: consider a read-redo on monitor return code?
379                log.warn( "Unable to read metadata: " + metadataFile.getAbsolutePath(), e );
380                return null;
381            }
382        }
383    
384        public ArchivaRepositoryMetadata readProxyMetadata( ManagedRepositoryContent managedRepository,
385                                                            String logicalResource, String proxyId )
386        {
387            String metadataPath = getRepositorySpecificName( proxyId, logicalResource );
388            File metadataFile = new File( managedRepository.getRepoRoot(), metadataPath );
389    
390            if ( !metadataFile.exists() || !metadataFile.isFile() )
391            {
392                // Nothing to do. return null.
393                return null;
394            }
395    
396            try
397            {
398                return MavenMetadataReader.read( metadataFile );
399            }
400            catch ( XMLException e )
401            {
402                // TODO: [monitor] consider a monitor for this event.
403                // TODO: consider a read-redo on monitor return code?
404                log.warn( "Unable to read metadata: " + metadataFile.getAbsolutePath(), e );
405                return null;
406            }
407        }
408    
409        public ArchivaRepositoryMetadata readProxyMetadata( ManagedRepositoryContent managedRepository,
410                                                            VersionedReference reference, String proxyId )
411        {
412            String metadataPath = getRepositorySpecificName( proxyId, toPath( reference ) );
413            File metadataFile = new File( managedRepository.getRepoRoot(), metadataPath );
414    
415            if ( !metadataFile.exists() || !metadataFile.isFile() )
416            {
417                // Nothing to do. return null.
418                return null;
419            }
420    
421            try
422            {
423                return MavenMetadataReader.read( metadataFile );
424            }
425            catch ( XMLException e )
426            {
427                // TODO: [monitor] consider a monitor for this event.
428                // TODO: consider a read-redo on monitor return code?
429                log.warn( "Unable to read metadata: " + metadataFile.getAbsolutePath(), e );
430                return null;
431            }
432        }
433    
434        public void updateMetadata( ManagedRepositoryContent managedRepository, String logicalResource )
435            throws RepositoryMetadataException
436        {
437            final File metadataFile = new File( managedRepository.getRepoRoot(), logicalResource );
438            ArchivaRepositoryMetadata metadata = null;
439    
440            //Gather and merge all metadata available
441            List<ArchivaRepositoryMetadata> metadatas =
442                getMetadatasForManagedRepository( managedRepository, logicalResource );
443            for ( ArchivaRepositoryMetadata proxiedMetadata : metadatas )
444            {
445                if ( metadata == null )
446                {
447                    metadata = proxiedMetadata;
448                    continue;
449                }
450                metadata = RepositoryMetadataMerge.merge( metadata, proxiedMetadata );
451            }
452    
453            if ( metadata == null )
454            {
455                log.debug( "No metadata to update for {}", logicalResource );
456                return;
457            }
458    
459            Set<String> availableVersions = new HashSet<String>();
460            List<String> metadataAvailableVersions = metadata.getAvailableVersions();
461            if ( metadataAvailableVersions != null )
462            {
463                availableVersions.addAll( metadataAvailableVersions );
464            }
465            availableVersions = findPossibleVersions( availableVersions, metadataFile.getParentFile() );
466    
467            if ( availableVersions.size() > 0 )
468            {
469                updateMetadataVersions( availableVersions, metadata );
470            }
471    
472            RepositoryMetadataWriter.write( metadata, metadataFile );
473    
474            ChecksummedFile checksum = new ChecksummedFile( metadataFile );
475            checksum.fixChecksums( algorithms );
476        }
477    
478        /**
479         * Skims the parent directory of a metadata in vain hope of finding
480         * subdirectories that contain poms.
481         *
482         * @param metadataParentDirectory
483         * @return origional set plus newley found versions
484         */
485        private Set<String> findPossibleVersions( Set<String> versions, File metadataParentDirectory )
486        {
487            Set<String> result = new HashSet<String>( versions );
488            for ( File directory : metadataParentDirectory.listFiles() )
489            {
490                if ( directory.isDirectory() )
491                {
492                    for ( File possiblePom : directory.listFiles() )
493                    {
494                        if ( possiblePom.getName().endsWith( ".pom" ) )
495                        {
496                            result.add( directory.getName() );
497                        }
498                    }
499                }
500            }
501            return result;
502        }
503    
504        private List<ArchivaRepositoryMetadata> getMetadatasForManagedRepository(
505            ManagedRepositoryContent managedRepository, String logicalResource )
506        {
507            List<ArchivaRepositoryMetadata> metadatas = new ArrayList<ArchivaRepositoryMetadata>();
508            File file = new File( managedRepository.getRepoRoot(), logicalResource );
509            if ( file.exists() )
510            {
511                try
512                {
513                    ArchivaRepositoryMetadata existingMetadata = MavenMetadataReader.read( file );
514                    if ( existingMetadata != null )
515                    {
516                        metadatas.add( existingMetadata );
517                    }
518                }
519                catch ( XMLException e )
520                {
521                    log.debug( "Could not read metadata at {}. Metadata will be removed.", file.getAbsolutePath() );
522                    FileUtils.deleteQuietly( file );
523                }
524            }
525    
526            Set<String> proxyIds = proxies.get( managedRepository.getId() );
527            if ( proxyIds != null )
528            {
529                for ( String proxyId : proxyIds )
530                {
531                    ArchivaRepositoryMetadata proxyMetadata =
532                        readProxyMetadata( managedRepository, logicalResource, proxyId );
533                    if ( proxyMetadata != null )
534                    {
535                        metadatas.add( proxyMetadata );
536                    }
537                }
538            }
539    
540            return metadatas;
541        }
542    
543    
544        /**
545         * Update the metadata to represent the all versions/plugins of
546         * the provided groupId:artifactId project or group reference,
547         * based off of information present in the repository,
548         * the maven-metadata.xml files, and the proxy/repository specific
549         * metadata file contents.
550         * <p/>
551         * We must treat this as a group or a project metadata file as there is no way to know in advance
552         *
553         * @param managedRepository the managed repository where the metadata is kept.
554         * @param reference         the reference to update.
555         * @throws LayoutException
556         * @throws RepositoryMetadataException
557         * @throws IOException
558         * @throws ContentNotFoundException
559         * @deprecated
560         */
561        public void updateMetadata( ManagedRepositoryContent managedRepository, ProjectReference reference )
562            throws LayoutException, RepositoryMetadataException, IOException, ContentNotFoundException
563        {
564            File metadataFile = new File( managedRepository.getRepoRoot(), toPath( reference ) );
565    
566            long lastUpdated = getExistingLastUpdated( metadataFile );
567    
568            ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
569            metadata.setGroupId( reference.getGroupId() );
570            metadata.setArtifactId( reference.getArtifactId() );
571    
572            // Gather up all versions found in the managed repository.
573            Set<String> allVersions = managedRepository.getVersions( reference );
574    
575            // Gather up all plugins found in the managed repository.
576            // TODO: do we know this information instead?
577    //        Set<Plugin> allPlugins = managedRepository.getPlugins( reference );
578            Set<Plugin> allPlugins;
579            if ( metadataFile.exists() )
580            {
581                try
582                {
583                    allPlugins = new LinkedHashSet<Plugin>( MavenMetadataReader.read( metadataFile ).getPlugins() );
584                }
585                catch ( XMLException e )
586                {
587                    throw new RepositoryMetadataException( e.getMessage(), e );
588                }
589            }
590            else
591            {
592                allPlugins = new LinkedHashSet<Plugin>();
593            }
594    
595            // Does this repository have a set of remote proxied repositories?
596            Set<String> proxiedRepoIds = this.proxies.get( managedRepository.getId() );
597    
598            if ( CollectionUtils.isNotEmpty( proxiedRepoIds ) )
599            {
600                // Add in the proxied repo version ids too.
601                Iterator<String> it = proxiedRepoIds.iterator();
602                while ( it.hasNext() )
603                {
604                    String proxyId = it.next();
605    
606                    ArchivaRepositoryMetadata proxyMetadata = readProxyMetadata( managedRepository, reference, proxyId );
607                    if ( proxyMetadata != null )
608                    {
609                        allVersions.addAll( proxyMetadata.getAvailableVersions() );
610                        allPlugins.addAll( proxyMetadata.getPlugins() );
611                        long proxyLastUpdated = getLastUpdated( proxyMetadata );
612    
613                        lastUpdated = Math.max( lastUpdated, proxyLastUpdated );
614                    }
615                }
616            }
617    
618            if ( !allVersions.isEmpty() )
619            {
620                updateMetadataVersions( allVersions, metadata );
621            }
622            else
623            {
624                // Add the plugins to the metadata model.
625                metadata.setPlugins( new ArrayList<Plugin>( allPlugins ) );
626    
627                // artifact ID was actually the last part of the group
628                metadata.setGroupId( metadata.getGroupId() + "." + metadata.getArtifactId() );
629                metadata.setArtifactId( null );
630            }
631    
632            if ( lastUpdated > 0 )
633            {
634                metadata.setLastUpdatedTimestamp( toLastUpdatedDate( lastUpdated ) );
635            }
636    
637            // Save the metadata model to disk.
638            RepositoryMetadataWriter.write( metadata, metadataFile );
639            ChecksummedFile checksum = new ChecksummedFile( metadataFile );
640            checksum.fixChecksums( algorithms );
641        }
642    
643        private void updateMetadataVersions( Collection<String> allVersions, ArchivaRepositoryMetadata metadata )
644        {
645            // Sort the versions
646            List<String> sortedVersions = new ArrayList<String>( allVersions );
647            Collections.sort( sortedVersions, VersionComparator.getInstance() );
648    
649            // Split the versions into released and snapshots.
650            List<String> releasedVersions = new ArrayList<String>();
651            List<String> snapshotVersions = new ArrayList<String>();
652    
653            for ( String version : sortedVersions )
654            {
655                if ( VersionUtil.isSnapshot( version ) )
656                {
657                    snapshotVersions.add( version );
658                }
659                else
660                {
661                    releasedVersions.add( version );
662                }
663            }
664    
665            Collections.sort( releasedVersions, VersionComparator.getInstance() );
666            Collections.sort( snapshotVersions, VersionComparator.getInstance() );
667    
668            String latestVersion = sortedVersions.get( sortedVersions.size() - 1 );
669            String releaseVersion = null;
670    
671            if ( CollectionUtils.isNotEmpty( releasedVersions ) )
672            {
673                releaseVersion = releasedVersions.get( releasedVersions.size() - 1 );
674            }
675    
676            // Add the versions to the metadata model.
677            metadata.setAvailableVersions( sortedVersions );
678    
679            metadata.setLatestVersion( latestVersion );
680            metadata.setReleasedVersion( releaseVersion );
681        }
682    
683        private Date toLastUpdatedDate( long lastUpdated )
684        {
685            Calendar cal = Calendar.getInstance( DateUtils.UTC_TIME_ZONE );
686            cal.setTimeInMillis( lastUpdated );
687    
688            return cal.getTime();
689        }
690    
691        private long toLastUpdatedLong( String timestampString )
692        {
693            try
694            {
695                Date date = lastUpdatedFormat.parse( timestampString );
696                Calendar cal = Calendar.getInstance( DateUtils.UTC_TIME_ZONE );
697                cal.setTime( date );
698    
699                return cal.getTimeInMillis();
700            }
701            catch ( ParseException e )
702            {
703                return 0;
704            }
705        }
706    
707        private long getLastUpdated( ArchivaRepositoryMetadata metadata )
708        {
709            if ( metadata == null )
710            {
711                // Doesn't exist.
712                return 0;
713            }
714    
715            try
716            {
717                String lastUpdated = metadata.getLastUpdated();
718                if ( StringUtils.isBlank( lastUpdated ) )
719                {
720                    // Not set.
721                    return 0;
722                }
723    
724                Date lastUpdatedDate = lastUpdatedFormat.parse( lastUpdated );
725                return lastUpdatedDate.getTime();
726            }
727            catch ( ParseException e )
728            {
729                // Bad format on the last updated string.
730                return 0;
731            }
732        }
733    
734        private long getExistingLastUpdated( File metadataFile )
735        {
736            if ( !metadataFile.exists() )
737            {
738                // Doesn't exist.
739                return 0;
740            }
741    
742            try
743            {
744                ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( metadataFile );
745    
746                return getLastUpdated( metadata );
747            }
748            catch ( XMLException e )
749            {
750                // Error.
751                return 0;
752            }
753        }
754    
755        /**
756         * Update the metadata based on the following rules.
757         * <p/>
758         * 1) If this is a SNAPSHOT reference, then utilize the proxy/repository specific
759         * metadata files to represent the current / latest SNAPSHOT available.
760         * 2) If this is a RELEASE reference, and the metadata file does not exist, then
761         * create the metadata file with contents required of the VersionedReference
762         *
763         * @param managedRepository the managed repository where the metadata is kept.
764         * @param reference         the versioned reference to update
765         * @throws LayoutException
766         * @throws RepositoryMetadataException
767         * @throws IOException
768         * @throws ContentNotFoundException
769         * @deprecated
770         */
771        public void updateMetadata( ManagedRepositoryContent managedRepository, VersionedReference reference )
772            throws LayoutException, RepositoryMetadataException, IOException, ContentNotFoundException
773        {
774            File metadataFile = new File( managedRepository.getRepoRoot(), toPath( reference ) );
775    
776            long lastUpdated = getExistingLastUpdated( metadataFile );
777    
778            ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
779            metadata.setGroupId( reference.getGroupId() );
780            metadata.setArtifactId( reference.getArtifactId() );
781    
782            if ( VersionUtil.isSnapshot( reference.getVersion() ) )
783            {
784                // Do SNAPSHOT handling.
785                metadata.setVersion( VersionUtil.getBaseVersion( reference.getVersion() ) );
786    
787                // Gather up all of the versions found in the reference dir, and any
788                // proxied maven-metadata.xml files.
789                Set<String> snapshotVersions = gatherSnapshotVersions( managedRepository, reference );
790    
791                if ( snapshotVersions.isEmpty() )
792                {
793                    throw new ContentNotFoundException(
794                        "No snapshot versions found on reference [" + VersionedReference.toKey( reference ) + "]." );
795                }
796    
797                // sort the list to determine to aide in determining the Latest version.
798                List<String> sortedVersions = new ArrayList<String>();
799                sortedVersions.addAll( snapshotVersions );
800                Collections.sort( sortedVersions, new VersionComparator() );
801    
802                String latestVersion = sortedVersions.get( sortedVersions.size() - 1 );
803    
804                if ( VersionUtil.isUniqueSnapshot( latestVersion ) )
805                {
806                    // The latestVersion will contain the full version string "1.0-alpha-5-20070821.213044-8"
807                    // This needs to be broken down into ${base}-${timestamp}-${build_number}
808    
809                    Matcher m = VersionUtil.UNIQUE_SNAPSHOT_PATTERN.matcher( latestVersion );
810                    if ( m.matches() )
811                    {
812                        metadata.setSnapshotVersion( new SnapshotVersion() );
813                        int buildNumber = NumberUtils.toInt( m.group( 3 ), -1 );
814                        metadata.getSnapshotVersion().setBuildNumber( buildNumber );
815    
816                        Matcher mtimestamp = VersionUtil.TIMESTAMP_PATTERN.matcher( m.group( 2 ) );
817                        if ( mtimestamp.matches() )
818                        {
819                            String tsDate = mtimestamp.group( 1 );
820                            String tsTime = mtimestamp.group( 2 );
821    
822                            long snapshotLastUpdated = toLastUpdatedLong( tsDate + tsTime );
823    
824                            lastUpdated = Math.max( lastUpdated, snapshotLastUpdated );
825    
826                            metadata.getSnapshotVersion().setTimestamp( m.group( 2 ) );
827                        }
828                    }
829                }
830                else if ( VersionUtil.isGenericSnapshot( latestVersion ) )
831                {
832                    // The latestVersion ends with the generic version string.
833                    // Example: 1.0-alpha-5-SNAPSHOT
834    
835                    metadata.setSnapshotVersion( new SnapshotVersion() );
836    
837                    /* Disabled due to decision in [MRM-535].
838                     * Do not set metadata.lastUpdated to file.lastModified.
839                     * 
840                     * Should this be the last updated timestamp of the file, or in the case of an 
841                     * archive, the most recent timestamp in the archive?
842                     * 
843                    ArtifactReference artifact = getFirstArtifact( managedRepository, reference );
844    
845                    if ( artifact == null )
846                    {
847                        throw new IOException( "Not snapshot artifact found to reference in " + reference );
848                    }
849    
850                    File artifactFile = managedRepository.toFile( artifact );
851    
852                    if ( artifactFile.exists() )
853                    {
854                        Date lastModified = new Date( artifactFile.lastModified() );
855                        metadata.setLastUpdatedTimestamp( lastModified );
856                    }
857                    */
858                }
859                else
860                {
861                    throw new RepositoryMetadataException(
862                        "Unable to process snapshot version <" + latestVersion + "> reference <" + reference + ">" );
863                }
864            }
865            else
866            {
867                // Do RELEASE handling.
868                metadata.setVersion( reference.getVersion() );
869            }
870    
871            // Set last updated
872            if ( lastUpdated > 0 )
873            {
874                metadata.setLastUpdatedTimestamp( toLastUpdatedDate( lastUpdated ) );
875            }
876    
877            // Save the metadata model to disk.
878            RepositoryMetadataWriter.write( metadata, metadataFile );
879            ChecksummedFile checksum = new ChecksummedFile( metadataFile );
880            checksum.fixChecksums( algorithms );
881        }
882    
883        private void initConfigVariables()
884        {
885            synchronized ( this.artifactPatterns )
886            {
887                this.artifactPatterns.clear();
888    
889                this.artifactPatterns.addAll( filetypes.getFileTypePatterns( FileTypes.ARTIFACTS ) );
890            }
891    
892            synchronized ( proxies )
893            {
894                this.proxies.clear();
895    
896                List<ProxyConnectorConfiguration> proxyConfigs = configuration.getConfiguration().getProxyConnectors();
897                for ( ProxyConnectorConfiguration proxyConfig : proxyConfigs )
898                {
899                    String key = proxyConfig.getSourceRepoId();
900    
901                    Set<String> remoteRepoIds = this.proxies.get( key );
902    
903                    if ( remoteRepoIds == null )
904                    {
905                        remoteRepoIds = new HashSet<String>();
906                    }
907    
908                    remoteRepoIds.add( proxyConfig.getTargetRepoId() );
909    
910                    this.proxies.put( key, remoteRepoIds );
911                }
912            }
913        }
914    
915        /**
916         * Get the first Artifact found in the provided VersionedReference location.
917         *
918         * @param managedRepository the repository to search within.
919         * @param reference         the reference to the versioned reference to search within
920         * @return the ArtifactReference to the first artifact located within the versioned reference. or null if
921         *         no artifact was found within the versioned reference.
922         * @throws IOException     if the versioned reference is invalid (example: doesn't exist, or isn't a directory)
923         * @throws LayoutException
924         */
925        public ArtifactReference getFirstArtifact( ManagedRepositoryContent managedRepository,
926                                                   VersionedReference reference )
927            throws LayoutException, IOException
928        {
929            String path = toPath( reference );
930    
931            int idx = path.lastIndexOf( '/' );
932            if ( idx > 0 )
933            {
934                path = path.substring( 0, idx );
935            }
936    
937            File repoDir = new File( managedRepository.getRepoRoot(), path );
938    
939            if ( !repoDir.exists() )
940            {
941                throw new IOException( "Unable to gather the list of snapshot versions on a non-existant directory: "
942                                           + repoDir.getAbsolutePath() );
943            }
944    
945            if ( !repoDir.isDirectory() )
946            {
947                throw new IOException(
948                    "Unable to gather the list of snapshot versions on a non-directory: " + repoDir.getAbsolutePath() );
949            }
950    
951            File repoFiles[] = repoDir.listFiles();
952            for ( int i = 0; i < repoFiles.length; i++ )
953            {
954                if ( repoFiles[i].isDirectory() )
955                {
956                    // Skip it. it's a directory.
957                    continue;
958                }
959    
960                String relativePath = PathUtil.getRelative( managedRepository.getRepoRoot(), repoFiles[i] );
961    
962                if ( filetypes.matchesArtifactPattern( relativePath ) )
963                {
964                    ArtifactReference artifact = managedRepository.toArtifactReference( relativePath );
965    
966                    return artifact;
967                }
968            }
969    
970            // No artifact was found.
971            return null;
972        }
973    
974        public ArchivaConfiguration getConfiguration()
975        {
976            return configuration;
977        }
978    
979        public void setConfiguration( ArchivaConfiguration configuration )
980        {
981            this.configuration = configuration;
982        }
983    
984        public FileTypes getFiletypes()
985        {
986            return filetypes;
987        }
988    
989        public void setFiletypes( FileTypes filetypes )
990        {
991            this.filetypes = filetypes;
992        }
993    }