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