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