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