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