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