]> source.dussan.org Git - archiva.git/blob
38f871703bc9b7f75d423b04e27e0df9ae6e5366
[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         ArchivaRepositoryMetadata metadata = null;
428         final File metadataFile = new File(managedRepository.getRepoRoot(), logicalResource);
429 //        final long lastUpdated = getExistingLastUpdated( metadataFile );
430         
431         //Gather and merge all metadata available
432         List<ArchivaRepositoryMetadata> metadatas = getMetadatasForManagedRepository(managedRepository, logicalResource);
433         for (ArchivaRepositoryMetadata proxiedMetadata : metadatas)
434         {
435             if (metadata == null)
436             {
437                 metadata = proxiedMetadata;
438                 continue;
439             }
440             metadata = RepositoryMetadataMerge.merge(metadata, proxiedMetadata);
441         }
442         
443         Set<String> availableVersions = new HashSet<String>(metadata.getAvailableVersions());
444         availableVersions = findPossibleVersions(availableVersions, metadataFile.getParentFile());
445
446         if (availableVersions.size() > 0)
447         {
448             updateMetadataVersions(availableVersions, metadata);
449         }
450         
451 //        if ( lastUpdated > 0 )
452 //        {
453 //            metadata.setLastUpdatedTimestamp( toLastUpdatedDate( lastUpdated ) );
454 //        }
455         
456         RepositoryMetadataWriter.write(metadata, metadataFile);
457     }
458     
459     /**
460      * Skims the parent directory of a metadata in vain hope of finding 
461      * subdirectories that contain poms.
462      * 
463      * @param metadataParentDirectory
464      * @return origional set plus newley found versions
465      */
466     private Set<String> findPossibleVersions(Set<String> versions, File metadataParentDirectory)
467     {
468         Set<String> result = new HashSet<String>(versions);
469         for (File directory : metadataParentDirectory.listFiles())
470         {
471             if (directory.isDirectory())
472             {
473                 for (File possiblePom : directory.listFiles())
474                 {
475                     if (possiblePom.getName().endsWith(".pom"))
476                     {
477                         result.add(directory.getName());
478                     }
479                 }
480             }
481         }
482         return result;
483     }
484     
485     private List<ArchivaRepositoryMetadata> getMetadatasForManagedRepository( ManagedRepositoryContent managedRepository, String logicalResource )
486     {
487         List<ArchivaRepositoryMetadata> metadatas = new ArrayList<ArchivaRepositoryMetadata>();
488         File file = new File(managedRepository.getRepoRoot(), logicalResource);
489         if (file.exists())
490         {
491             try
492             {
493                ArchivaRepositoryMetadata existingMetadata = RepositoryMetadataReader.read(file);
494                if (existingMetadata != null)
495                {
496                     metadatas.add(existingMetadata);
497                }
498             }
499             catch (RepositoryMetadataException e)
500             {
501                 log.debug("Could not read metadata at " + file.getAbsolutePath() + ". Metadata will be removed.");
502                 FileUtils.deleteQuietly(file);
503             }
504         }
505         
506         for (String proxyId : proxies.get(managedRepository.getId()))
507         {
508             ArchivaRepositoryMetadata proxyMetadata = readProxyMetadata( managedRepository, logicalResource, proxyId );
509             if (proxyMetadata != null)
510             {
511                 metadatas.add(proxyMetadata);
512             }
513         }
514         
515         return metadatas;
516     }
517     
518
519     /**
520      * Update the metadata to represent the all versions/plugins of
521      * the provided groupId:artifactId project or group reference,
522      * based off of information present in the repository,
523      * the maven-metadata.xml files, and the proxy/repository specific
524      * metadata file contents.
525      *
526      * We must treat this as a group or a project metadata file as there is no way to know in advance
527      *
528      * @deprecated 
529      * @param managedRepository the managed repository where the metadata is kept.
530      * @param reference         the reference to update.
531      * @throws LayoutException
532      * @throws RepositoryMetadataException
533      * @throws IOException
534      * @throws ContentNotFoundException 
535      */
536     public void updateMetadata( ManagedRepositoryContent managedRepository, ProjectReference reference )
537         throws LayoutException, RepositoryMetadataException, IOException, ContentNotFoundException
538     {
539         File metadataFile = new File( managedRepository.getRepoRoot(), toPath( reference ) );
540
541         long lastUpdated = getExistingLastUpdated( metadataFile );
542
543         ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
544         metadata.setGroupId( reference.getGroupId() );
545         metadata.setArtifactId( reference.getArtifactId() );
546
547         // Gather up all versions found in the managed repository.
548         Set<String> allVersions = managedRepository.getVersions( reference );
549
550         // Gather up all plugins found in the managed repository.
551         // TODO: do we know this information instead?
552 //        Set<Plugin> allPlugins = managedRepository.getPlugins( reference );
553         Set<Plugin> allPlugins;
554         if ( metadataFile.exists() )
555         {
556             allPlugins = new LinkedHashSet<Plugin>( RepositoryMetadataReader.read( metadataFile ).getPlugins() );
557         }
558         else
559         {
560             allPlugins = new LinkedHashSet<Plugin>();
561         }
562
563         // Does this repository have a set of remote proxied repositories?
564         Set<String> proxiedRepoIds = this.proxies.get( managedRepository.getId() );
565
566         if ( CollectionUtils.isNotEmpty( proxiedRepoIds ) )
567         {
568             // Add in the proxied repo version ids too.
569             Iterator<String> it = proxiedRepoIds.iterator();
570             while ( it.hasNext() )
571             {
572                 String proxyId = it.next();
573
574                 ArchivaRepositoryMetadata proxyMetadata = readProxyMetadata( managedRepository, reference, proxyId );
575                 if ( proxyMetadata != null )
576                 {
577                     allVersions.addAll( proxyMetadata.getAvailableVersions() );
578                     allPlugins.addAll( proxyMetadata.getPlugins() );
579                     long proxyLastUpdated = getLastUpdated( proxyMetadata );
580
581                     lastUpdated = Math.max( lastUpdated, proxyLastUpdated );
582                 }
583             }
584         }
585
586         if ( !allVersions.isEmpty() )
587         {
588             updateMetadataVersions( allVersions ,metadata );
589         }
590         else
591         {
592             // Add the plugins to the metadata model.
593             metadata.setPlugins( new ArrayList<Plugin>( allPlugins ) );
594
595             // artifact ID was actually the last part of the group
596             metadata.setGroupId( metadata.getGroupId() + "." + metadata.getArtifactId() );
597             metadata.setArtifactId( null );
598         }
599
600         if ( lastUpdated > 0 )
601         {
602             metadata.setLastUpdatedTimestamp( toLastUpdatedDate( lastUpdated ) );
603         }
604
605         // Save the metadata model to disk.
606         RepositoryMetadataWriter.write( metadata, metadataFile );
607         ChecksummedFile checksum = new ChecksummedFile( metadataFile );
608         checksum.fixChecksums( algorithms );
609     }
610
611     private void updateMetadataVersions(Collection<String> allVersions, ArchivaRepositoryMetadata metadata) 
612     {
613         // Sort the versions
614         List<String> sortedVersions = new ArrayList<String>(allVersions);
615         Collections.sort(sortedVersions, VersionComparator.getInstance());
616
617         // Split the versions into released and snapshots.
618         List<String> releasedVersions = new ArrayList<String>();
619         List<String> snapshotVersions = new ArrayList<String>();
620
621         for (String version : sortedVersions) 
622         {
623             if (VersionUtil.isSnapshot(version)) 
624             {
625                 snapshotVersions.add(version);
626             }
627             else 
628             {
629                 releasedVersions.add(version);
630             }
631         }
632
633         Collections.sort(releasedVersions, VersionComparator.getInstance());
634         Collections.sort(snapshotVersions, VersionComparator.getInstance());
635
636         String latestVersion = sortedVersions.get(sortedVersions.size() - 1);
637         String releaseVersion = null;
638
639         if (CollectionUtils.isNotEmpty(releasedVersions)) 
640         {
641             releaseVersion = releasedVersions.get(releasedVersions.size() - 1);
642         }
643
644         // Add the versions to the metadata model.
645         metadata.setAvailableVersions(sortedVersions);
646
647         metadata.setLatestVersion(latestVersion);
648         metadata.setReleasedVersion(releaseVersion);
649     }
650
651     private Date toLastUpdatedDate( long lastUpdated )
652     {
653         Calendar cal = Calendar.getInstance( DateUtils.UTC_TIME_ZONE );
654         cal.setTimeInMillis( lastUpdated );
655
656         return cal.getTime();
657     }
658     
659     private long toLastUpdatedLong( String timestampString )
660     {
661         try
662         {
663             Date date = lastUpdatedFormat.parse( timestampString );
664             Calendar cal = Calendar.getInstance( DateUtils.UTC_TIME_ZONE );
665             cal.setTime( date );
666
667             return cal.getTimeInMillis();
668         }
669         catch ( ParseException e )
670         {
671             return 0;
672         }
673     }
674
675     private long getLastUpdated( ArchivaRepositoryMetadata metadata )
676     {
677         if ( metadata == null )
678         {
679             // Doesn't exist.
680             return 0;
681         }
682
683         try
684         {
685             String lastUpdated = metadata.getLastUpdated();
686             if ( StringUtils.isBlank( lastUpdated ) )
687             {
688                 // Not set.
689                 return 0;
690             }
691
692             Date lastUpdatedDate = lastUpdatedFormat.parse( lastUpdated );
693             return lastUpdatedDate.getTime();
694         }
695         catch ( ParseException e )
696         {
697             // Bad format on the last updated string.
698             return 0;
699         }
700     }
701
702     private long getExistingLastUpdated( File metadataFile )
703     {
704         if ( !metadataFile.exists() )
705         {
706             // Doesn't exist.
707             return 0;
708         }
709
710         try
711         {
712             ArchivaRepositoryMetadata metadata = RepositoryMetadataReader.read( metadataFile );
713
714             return getLastUpdated( metadata );
715         }
716         catch ( RepositoryMetadataException e )
717         {
718             // Error.
719             return 0;
720         }
721     }
722
723     /**
724      * Update the metadata based on the following rules.
725      * <p/>
726      * 1) If this is a SNAPSHOT reference, then utilize the proxy/repository specific
727      * metadata files to represent the current / latest SNAPSHOT available.
728      * 2) If this is a RELEASE reference, and the metadata file does not exist, then
729      * create the metadata file with contents required of the VersionedReference
730      *
731      * @deprecated
732      * @param managedRepository the managed repository where the metadata is kept.
733      * @param reference         the versioned reference to update
734      * @throws LayoutException
735      * @throws RepositoryMetadataException
736      * @throws IOException
737      * @throws ContentNotFoundException 
738      */
739     public void updateMetadata( ManagedRepositoryContent managedRepository, VersionedReference reference )
740         throws LayoutException, RepositoryMetadataException, IOException, ContentNotFoundException
741     {
742         File metadataFile = new File( managedRepository.getRepoRoot(), toPath( reference ) );
743
744         long lastUpdated = getExistingLastUpdated( metadataFile );
745
746         ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
747         metadata.setGroupId( reference.getGroupId() );
748         metadata.setArtifactId( reference.getArtifactId() );
749         
750         if ( VersionUtil.isSnapshot( reference.getVersion() ) )
751         {
752             // Do SNAPSHOT handling.
753             metadata.setVersion( VersionUtil.getBaseVersion( reference.getVersion() ) );
754
755             // Gather up all of the versions found in the reference dir, and any
756             // proxied maven-metadata.xml files.
757             Set<String> snapshotVersions = gatherSnapshotVersions( managedRepository, reference );
758
759             if ( snapshotVersions.isEmpty() )
760             {
761                 throw new ContentNotFoundException( "No snapshot versions found on reference ["
762                     + VersionedReference.toKey( reference ) + "]." );
763             }
764
765             // sort the list to determine to aide in determining the Latest version.
766             List<String> sortedVersions = new ArrayList<String>();
767             sortedVersions.addAll( snapshotVersions );
768             Collections.sort( sortedVersions, new VersionComparator() );
769
770             String latestVersion = sortedVersions.get( sortedVersions.size() - 1 );
771
772             if ( VersionUtil.isUniqueSnapshot( latestVersion ) )
773             {
774                 // The latestVersion will contain the full version string "1.0-alpha-5-20070821.213044-8"
775                 // This needs to be broken down into ${base}-${timestamp}-${build_number}
776
777                 Matcher m = VersionUtil.UNIQUE_SNAPSHOT_PATTERN.matcher( latestVersion );
778                 if ( m.matches() )
779                 {
780                     metadata.setSnapshotVersion( new SnapshotVersion() );
781                     int buildNumber = NumberUtils.toInt( m.group( 3 ), -1 );
782                     metadata.getSnapshotVersion().setBuildNumber( buildNumber );
783
784                     Matcher mtimestamp = VersionUtil.TIMESTAMP_PATTERN.matcher( m.group( 2 ) );
785                     if ( mtimestamp.matches() )
786                     {
787                         String tsDate = mtimestamp.group( 1 );
788                         String tsTime = mtimestamp.group( 2 );
789                         
790                         long snapshotLastUpdated = toLastUpdatedLong( tsDate + tsTime );
791                         
792                         lastUpdated = Math.max( lastUpdated, snapshotLastUpdated );
793                         
794                         metadata.getSnapshotVersion().setTimestamp( m.group( 2 ) );
795                     }
796                 }
797             }
798             else if ( VersionUtil.isGenericSnapshot( latestVersion ) )
799             {
800                 // The latestVersion ends with the generic version string.
801                 // Example: 1.0-alpha-5-SNAPSHOT
802
803                 metadata.setSnapshotVersion( new SnapshotVersion() );
804
805                 /* Disabled due to decision in [MRM-535].
806                  * Do not set metadata.lastUpdated to file.lastModified.
807                  * 
808                  * Should this be the last updated timestamp of the file, or in the case of an 
809                  * archive, the most recent timestamp in the archive?
810                  * 
811                 ArtifactReference artifact = getFirstArtifact( managedRepository, reference );
812
813                 if ( artifact == null )
814                 {
815                     throw new IOException( "Not snapshot artifact found to reference in " + reference );
816                 }
817
818                 File artifactFile = managedRepository.toFile( artifact );
819
820                 if ( artifactFile.exists() )
821                 {
822                     Date lastModified = new Date( artifactFile.lastModified() );
823                     metadata.setLastUpdatedTimestamp( lastModified );
824                 }
825                 */
826             }
827             else
828             {
829                 throw new RepositoryMetadataException( "Unable to process snapshot version <" + latestVersion
830                     + "> reference <" + reference + ">" );
831             }
832         }
833         else
834         {
835             // Do RELEASE handling.
836             metadata.setVersion( reference.getVersion() );
837         }
838
839         // Set last updated
840         if ( lastUpdated > 0 )
841         {
842             metadata.setLastUpdatedTimestamp( toLastUpdatedDate( lastUpdated ) );
843         }
844
845         // Save the metadata model to disk.
846         RepositoryMetadataWriter.write( metadata, metadataFile );
847         ChecksummedFile checksum = new ChecksummedFile( metadataFile );
848         checksum.fixChecksums( algorithms );
849     }
850
851     private void initConfigVariables()
852     {
853         synchronized ( this.artifactPatterns )
854         {
855             this.artifactPatterns.clear();
856
857             this.artifactPatterns.addAll( filetypes.getFileTypePatterns( FileTypes.ARTIFACTS ) );
858         }
859
860         synchronized ( proxies )
861         {
862             this.proxies.clear();
863
864             List<ProxyConnectorConfiguration> proxyConfigs = configuration.getConfiguration().getProxyConnectors();
865             for( ProxyConnectorConfiguration proxyConfig: proxyConfigs )
866             {
867                 String key = proxyConfig.getSourceRepoId();
868
869                 Set<String> remoteRepoIds = this.proxies.get( key );
870
871                 if ( remoteRepoIds == null )
872                 {
873                     remoteRepoIds = new HashSet<String>();
874                 }
875
876                 remoteRepoIds.add( proxyConfig.getTargetRepoId() );
877
878                 this.proxies.put( key, remoteRepoIds );
879             }
880         }
881     }
882
883     /**
884      * Get the first Artifact found in the provided VersionedReference location.
885      *
886      * @param managedRepository the repository to search within.
887      * @param reference         the reference to the versioned reference to search within
888      * @return the ArtifactReference to the first artifact located within the versioned reference. or null if
889      *         no artifact was found within the versioned reference.
890      * @throws IOException     if the versioned reference is invalid (example: doesn't exist, or isn't a directory)
891      * @throws LayoutException
892      */
893     public ArtifactReference getFirstArtifact( ManagedRepositoryContent managedRepository, VersionedReference reference )
894         throws LayoutException, IOException
895     {
896         String path = toPath( reference );
897
898         int idx = path.lastIndexOf( '/' );
899         if ( idx > 0 )
900         {
901             path = path.substring( 0, idx );
902         }
903
904         File repoDir = new File( managedRepository.getRepoRoot(), path );
905
906         if ( !repoDir.exists() )
907         {
908             throw new IOException( "Unable to gather the list of snapshot versions on a non-existant directory: "
909                 + repoDir.getAbsolutePath() );
910         }
911
912         if ( !repoDir.isDirectory() )
913         {
914             throw new IOException( "Unable to gather the list of snapshot versions on a non-directory: "
915                 + repoDir.getAbsolutePath() );
916         }
917
918         File repoFiles[] = repoDir.listFiles();
919         for ( int i = 0; i < repoFiles.length; i++ )
920         {
921             if ( repoFiles[i].isDirectory() )
922             {
923                 // Skip it. it's a directory.
924                 continue;
925             }
926
927             String relativePath = PathUtil.getRelative( managedRepository.getRepoRoot(), repoFiles[i] );
928
929             if ( filetypes.matchesArtifactPattern( relativePath ) )
930             {
931                 ArtifactReference artifact = managedRepository.toArtifactReference( relativePath );
932
933                 return artifact;
934             }
935         }
936
937         // No artifact was found.
938         return null;
939     }
940 }