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