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