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