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