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