]> source.dussan.org Git - archiva.git/blob
d6a0f91342f506b709fb7e3d935fae7699744fc2
[archiva.git] /
1 package org.apache.maven.archiva.repository.metadata;
2
3 /*
4  * Copyright 2001-2007 The Apache Software Foundation.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 import org.apache.commons.collections.CollectionUtils;
20 import org.apache.commons.lang.StringUtils;
21 import org.apache.commons.lang.math.NumberUtils;
22 import org.apache.commons.lang.time.DateUtils;
23 import org.apache.maven.archiva.common.utils.Checksums;
24 import org.apache.maven.archiva.common.utils.PathUtil;
25 import org.apache.maven.archiva.common.utils.VersionComparator;
26 import org.apache.maven.archiva.common.utils.VersionUtil;
27 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
28 import org.apache.maven.archiva.configuration.ConfigurationNames;
29 import org.apache.maven.archiva.configuration.FileTypes;
30 import org.apache.maven.archiva.configuration.ProxyConnectorConfiguration;
31 import org.apache.maven.archiva.model.ArchivaRepositoryMetadata;
32 import org.apache.maven.archiva.model.ArtifactReference;
33 import org.apache.maven.archiva.model.ProjectReference;
34 import org.apache.maven.archiva.model.SnapshotVersion;
35 import org.apache.maven.archiva.model.VersionedReference;
36 import org.apache.maven.archiva.repository.ContentNotFoundException;
37 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
38 import org.apache.maven.archiva.repository.RemoteRepositoryContent;
39 import org.apache.maven.archiva.repository.layout.LayoutException;
40 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
41 import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
42 import org.codehaus.plexus.registry.Registry;
43 import org.codehaus.plexus.registry.RegistryListener;
44 import org.codehaus.plexus.util.SelectorUtils;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import java.io.File;
49 import java.io.IOException;
50 import java.text.ParseException;
51 import java.text.SimpleDateFormat;
52 import java.util.ArrayList;
53 import java.util.Calendar;
54 import java.util.Collections;
55 import java.util.Date;
56 import java.util.HashMap;
57 import java.util.HashSet;
58 import java.util.Iterator;
59 import java.util.List;
60 import java.util.Map;
61 import java.util.Set;
62 import java.util.regex.Matcher;
63
64 /**
65  * MetadataTools
66  *
67  * @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
68  * @version $Id$
69  * 
70  * @plexus.component role="org.apache.maven.archiva.repository.metadata.MetadataTools"
71  */
72 public class MetadataTools
73     implements RegistryListener, Initializable
74 {
75     private static Logger log = LoggerFactory.getLogger( MetadataTools.class );
76
77     public static final String MAVEN_METADATA = "maven-metadata.xml";
78
79     private static final char PATH_SEPARATOR = '/';
80
81     private static final char GROUP_SEPARATOR = '.';
82
83     /**
84      * @plexus.requirement
85      */
86     private ArchivaConfiguration configuration;
87
88     /**
89      * @plexus.requirement
90      */
91     private FileTypes filetypes;
92     
93     /**
94      * @plexus.requirement
95      */
96     private Checksums checksums;
97
98     private List<String> artifactPatterns;
99
100     private Map<String, Set<String>> proxies;
101
102     private static final char NUMS[] = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
103
104     private static final SimpleDateFormat lastUpdatedFormat;
105
106     static
107     {
108         lastUpdatedFormat = new SimpleDateFormat( "yyyyMMddHHmmss" );
109         lastUpdatedFormat.setTimeZone( DateUtils.UTC_TIME_ZONE );
110     }
111
112     public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
113     {
114         if ( ConfigurationNames.isProxyConnector( propertyName ) )
115         {
116             initConfigVariables();
117         }
118     }
119
120     public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
121     {
122         /* nothing to do */
123     }
124
125     /**
126      * Gather the set of snapshot versions found in a particular versioned reference.
127      *
128      * @return the Set of snapshot artifact versions found.
129      * @throws LayoutException
130      * @throws ContentNotFoundException 
131      */
132     public Set<String> gatherSnapshotVersions( ManagedRepositoryContent managedRepository, VersionedReference reference )
133         throws LayoutException, IOException, ContentNotFoundException
134     {
135         Set<String> foundVersions = managedRepository.getVersions( reference );
136
137         // Next gather up the referenced 'latest' versions found in any proxied repositories
138         // maven-metadata-${proxyId}.xml files that may be present.
139
140         // Does this repository have a set of remote proxied repositories?
141         Set<String> proxiedRepoIds = this.proxies.get( managedRepository.getId() );
142
143         if ( CollectionUtils.isNotEmpty( proxiedRepoIds ) )
144         {
145             String baseVersion = VersionUtil.getBaseVersion( reference.getVersion() );
146             baseVersion = baseVersion.substring( 0, baseVersion.indexOf( VersionUtil.SNAPSHOT ) - 1 );
147
148             // Add in the proxied repo version ids too.
149             Iterator<String> it = proxiedRepoIds.iterator();
150             while ( it.hasNext() )
151             {
152                 String proxyId = it.next();
153
154                 ArchivaRepositoryMetadata proxyMetadata = readProxyMetadata( managedRepository, reference, proxyId );
155                 if ( proxyMetadata == null )
156                 {
157                     // There is no proxy metadata, skip it.
158                     continue;
159                 }
160
161                 // Is there some snapshot info?
162                 SnapshotVersion snapshot = proxyMetadata.getSnapshotVersion();
163                 if ( snapshot != null )
164                 {
165                     String timestamp = snapshot.getTimestamp();
166                     int buildNumber = snapshot.getBuildNumber();
167
168                     // Only interested in the timestamp + buildnumber.
169                     if ( StringUtils.isNotBlank( timestamp ) && ( buildNumber > 0 ) )
170                     {
171                         foundVersions.add( baseVersion + "-" + timestamp + "-" + buildNumber );
172                     }
173                 }
174             }
175         }
176
177         return foundVersions;
178     }
179
180     /**
181      * Take a path to a maven-metadata.xml, and attempt to translate it to a VersionedReference.
182      *
183      * @param path
184      * @return
185      */
186     public VersionedReference toVersionedReference( String path )
187         throws RepositoryMetadataException
188     {
189         if ( !path.endsWith( "/" + MAVEN_METADATA ) )
190         {
191             throw new RepositoryMetadataException( "Cannot convert to versioned reference, not a metadata file. " );
192         }
193
194         VersionedReference reference = new VersionedReference();
195
196         String normalizedPath = StringUtils.replace( path, "\\", "/" );
197         String pathParts[] = StringUtils.split( normalizedPath, '/' );
198
199         int versionOffset = pathParts.length - 2;
200         int artifactIdOffset = versionOffset - 1;
201         int groupIdEnd = artifactIdOffset - 1;
202
203         reference.setVersion( pathParts[versionOffset] );
204
205         if ( !hasNumberAnywhere( reference.getVersion() ) )
206         {
207             // Scary check, but without it, all paths are version references;
208             throw new RepositoryMetadataException(
209                                                    "Not a versioned reference, as version id on path has no number in it." );
210         }
211
212         reference.setArtifactId( pathParts[artifactIdOffset] );
213
214         StringBuffer gid = new StringBuffer();
215         for ( int i = 0; i <= groupIdEnd; i++ )
216         {
217             if ( i > 0 )
218             {
219                 gid.append( "." );
220             }
221             gid.append( pathParts[i] );
222         }
223
224         reference.setGroupId( gid.toString() );
225
226         return reference;
227     }
228
229     private boolean hasNumberAnywhere( String version )
230     {
231         return StringUtils.indexOfAny( version, NUMS ) != ( -1 );
232     }
233
234     public ProjectReference toProjectReference( String path )
235         throws RepositoryMetadataException
236     {
237         if ( !path.endsWith( "/" + MAVEN_METADATA ) )
238         {
239             throw new RepositoryMetadataException( "Cannot convert to versioned reference, not a metadata file. " );
240         }
241
242         ProjectReference reference = new ProjectReference();
243
244         String normalizedPath = StringUtils.replace( path, "\\", "/" );
245         String pathParts[] = StringUtils.split( normalizedPath, '/' );
246
247         // Assume last part of the path is the version.
248
249         int artifactIdOffset = pathParts.length - 2;
250         int groupIdEnd = artifactIdOffset - 1;
251
252         reference.setArtifactId( pathParts[artifactIdOffset] );
253
254         StringBuffer gid = new StringBuffer();
255         for ( int i = 0; i <= groupIdEnd; i++ )
256         {
257             if ( i > 0 )
258             {
259                 gid.append( "." );
260             }
261             gid.append( pathParts[i] );
262         }
263
264         reference.setGroupId( gid.toString() );
265
266         return reference;
267     }
268
269     public String toPath( ProjectReference reference )
270     {
271         StringBuffer path = new StringBuffer();
272
273         path.append( formatAsDirectory( reference.getGroupId() ) ).append( PATH_SEPARATOR );
274         path.append( reference.getArtifactId() ).append( PATH_SEPARATOR );
275         path.append( MAVEN_METADATA );
276
277         return path.toString();
278     }
279
280     public String toPath( VersionedReference reference )
281     {
282         StringBuffer path = new StringBuffer();
283
284         path.append( formatAsDirectory( reference.getGroupId() ) ).append( PATH_SEPARATOR );
285         path.append( reference.getArtifactId() ).append( PATH_SEPARATOR );
286         if ( reference.getVersion() != null )
287         {
288             // add the version only if it is present
289             path.append( VersionUtil.getBaseVersion( reference.getVersion() ) ).append( PATH_SEPARATOR );
290         }
291         path.append( MAVEN_METADATA );
292
293         return path.toString();
294     }
295
296     private String formatAsDirectory( String directory )
297     {
298         return directory.replace( GROUP_SEPARATOR, PATH_SEPARATOR );
299     }
300
301     /**
302      * Adjusts a path for a metadata.xml file to its repository specific path.
303      *
304      * @param repository the repository to base new path off of.
305      * @param path       the path to the metadata.xml file to adjust the name of.
306      * @return the newly adjusted path reference to the repository specific metadata path.
307      */
308     public String getRepositorySpecificName( RemoteRepositoryContent repository, String path )
309     {
310         return getRepositorySpecificName( repository.getId(), path );
311     }
312
313     /**
314      * Adjusts a path for a metadata.xml file to its repository specific path.
315      *
316      * @param proxyId the repository id to base new path off of.
317      * @param path    the path to the metadata.xml file to adjust the name of.
318      * @return the newly adjusted path reference to the repository specific metadata path.
319      */
320     public String getRepositorySpecificName( String proxyId, String path )
321     {
322         StringBuffer ret = new StringBuffer();
323
324         int idx = path.lastIndexOf( "/" );
325         if ( idx > 0 )
326         {
327             ret.append( path.substring( 0, idx + 1 ) );
328         }
329
330         // TODO: need to filter out 'bad' characters from the proxy id.
331         ret.append( "maven-metadata-" ).append( proxyId ).append( ".xml" );
332
333         return ret.toString();
334     }
335
336     public void initialize()
337         throws InitializationException
338     {
339         this.artifactPatterns = new ArrayList<String>();
340         this.proxies = new HashMap<String, Set<String>>();
341         initConfigVariables();
342
343         configuration.addChangeListener( this );
344     }
345
346     public ArchivaRepositoryMetadata readProxyMetadata( ManagedRepositoryContent managedRepository,
347                                                         ProjectReference reference, String proxyId )
348     {
349         String metadataPath = getRepositorySpecificName( proxyId, toPath( reference ) );
350         File metadataFile = new File( managedRepository.getRepoRoot(), metadataPath );
351         
352         if ( !metadataFile.exists() || !metadataFile.isFile() )
353         {
354             // Nothing to do. return null.
355             return null;
356         }
357
358         try
359         {
360             return RepositoryMetadataReader.read( metadataFile );
361         }
362         catch ( RepositoryMetadataException e )
363         {
364             // TODO: [monitor] consider a monitor for this event.
365             // TODO: consider a read-redo on monitor return code?
366             log.warn( "Unable to read metadata: " + metadataFile.getAbsolutePath(), e );
367             return null;
368         }
369     }
370
371     public ArchivaRepositoryMetadata readProxyMetadata( ManagedRepositoryContent managedRepository,
372                                                         VersionedReference reference, String proxyId )
373     {
374         String metadataPath = getRepositorySpecificName( proxyId, toPath( reference ) );
375         File metadataFile = new File( managedRepository.getRepoRoot(), metadataPath );
376         
377         if ( !metadataFile.exists() || !metadataFile.isFile() )
378         {
379             // Nothing to do. return null.
380             return null;
381         }
382
383         try
384         {
385             return RepositoryMetadataReader.read( metadataFile );
386         }
387         catch ( RepositoryMetadataException e )
388         {
389             // TODO: [monitor] consider a monitor for this event.
390             // TODO: consider a read-redo on monitor return code?
391             log.warn( "Unable to read metadata: " + metadataFile.getAbsolutePath(), e );
392             return null;
393         }
394     }
395
396     /**
397      * Update the metadata to represent the all versions of
398      * the provided groupId:artifactId project reference,
399      * based off of information present in the repository,
400      * the maven-metadata.xml files, and the proxy/repository specific
401      * metadata file contents.
402      *
403      * @param managedRepository the managed repository where the metadata is kept.
404      * @param reference         the versioned referencfe to update.
405      * @throws LayoutException
406      * @throws RepositoryMetadataException
407      * @throws IOException
408      * @throws ContentNotFoundException 
409      */
410     public void updateMetadata( ManagedRepositoryContent managedRepository, ProjectReference reference )
411         throws LayoutException, RepositoryMetadataException, IOException, ContentNotFoundException
412     {
413         File metadataFile = new File( managedRepository.getRepoRoot(), toPath( reference ) );
414
415         long lastUpdated = getExistingLastUpdated( metadataFile );
416
417         ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
418         metadata.setGroupId( reference.getGroupId() );
419         metadata.setArtifactId( reference.getArtifactId() );
420
421         // Gather up all versions found in the managed repository.
422         Set<String> allVersions = managedRepository.getVersions( reference );
423
424         // Does this repository have a set of remote proxied repositories?
425         Set<String> proxiedRepoIds = this.proxies.get( managedRepository.getId() );
426
427         if ( CollectionUtils.isNotEmpty( proxiedRepoIds ) )
428         {
429             // Add in the proxied repo version ids too.
430             Iterator<String> it = proxiedRepoIds.iterator();
431             while ( it.hasNext() )
432             {
433                 String proxyId = it.next();
434
435                 ArchivaRepositoryMetadata proxyMetadata = readProxyMetadata( managedRepository, reference, proxyId );
436                 if ( proxyMetadata != null )
437                 {
438                     allVersions.addAll( proxyMetadata.getAvailableVersions() );
439                     long proxyLastUpdated = getLastUpdated( proxyMetadata );
440
441                     lastUpdated = Math.max( lastUpdated, proxyLastUpdated );
442                 }
443             }
444         }
445
446         if ( allVersions.size() == 0 )
447         {
448             throw new IOException( "No versions found for reference." );
449         }
450
451         // Sort the versions
452         List<String> sortedVersions = new ArrayList<String>( allVersions );
453         Collections.sort( sortedVersions, VersionComparator.getInstance() );
454
455         // Split the versions into released and snapshots.
456         List<String> releasedVersions = new ArrayList<String>();
457         List<String> snapshotVersions = new ArrayList<String>();
458
459         for ( String version : sortedVersions )
460         {
461             if ( VersionUtil.isSnapshot( version ) )
462             {
463                 snapshotVersions.add( version );
464             }
465             else
466             {
467                 releasedVersions.add( version );
468             }
469         }
470
471         Collections.sort( releasedVersions, VersionComparator.getInstance() );
472         Collections.sort( snapshotVersions, VersionComparator.getInstance() );
473
474         String latestVersion = sortedVersions.get( sortedVersions.size() - 1 );
475         String releaseVersion = null;
476
477         if ( CollectionUtils.isNotEmpty( releasedVersions ) )
478         {
479             releaseVersion = releasedVersions.get( releasedVersions.size() - 1 );
480         }
481
482         // Add the versions to the metadata model.
483         metadata.setAvailableVersions( sortedVersions );
484
485         metadata.setLatestVersion( latestVersion );
486         metadata.setReleasedVersion( releaseVersion );
487         if ( lastUpdated > 0 )
488         {
489             metadata.setLastUpdatedTimestamp( toLastUpdatedDate( lastUpdated ) );
490         }
491
492         // Save the metadata model to disk.
493         RepositoryMetadataWriter.write( metadata, metadataFile );
494         checksums.update( metadataFile );
495     }
496
497     private Date toLastUpdatedDate( long lastUpdated )
498     {
499         Calendar cal = Calendar.getInstance( DateUtils.UTC_TIME_ZONE );
500         cal.setTimeInMillis( lastUpdated );
501
502         return cal.getTime();
503     }
504     
505     private long toLastUpdatedLong( String timestampString )
506     {
507         try
508         {
509             Date date = lastUpdatedFormat.parse( timestampString );
510             Calendar cal = Calendar.getInstance( DateUtils.UTC_TIME_ZONE );
511             cal.setTime( date );
512
513             return cal.getTimeInMillis();
514         }
515         catch ( ParseException e )
516         {
517             return 0;
518         }
519     }
520
521     private long getLastUpdated( ArchivaRepositoryMetadata metadata )
522     {
523         if ( metadata == null )
524         {
525             // Doesn't exist.
526             return 0;
527         }
528
529         try
530         {
531             String lastUpdated = metadata.getLastUpdated();
532             if ( StringUtils.isBlank( lastUpdated ) )
533             {
534                 // Not set.
535                 return 0;
536             }
537
538             Date lastUpdatedDate = lastUpdatedFormat.parse( lastUpdated );
539             return lastUpdatedDate.getTime();
540         }
541         catch ( ParseException e )
542         {
543             // Bad format on the last updated string.
544             return 0;
545         }
546     }
547
548     private long getExistingLastUpdated( File metadataFile )
549     {
550         if ( !metadataFile.exists() )
551         {
552             // Doesn't exist.
553             return 0;
554         }
555
556         try
557         {
558             ArchivaRepositoryMetadata metadata = RepositoryMetadataReader.read( metadataFile );
559
560             return getLastUpdated( metadata );
561         }
562         catch ( RepositoryMetadataException e )
563         {
564             // Error.
565             return 0;
566         }
567     }
568
569     /**
570      * Update the metadata based on the following rules.
571      * <p/>
572      * 1) If this is a SNAPSHOT reference, then utilize the proxy/repository specific
573      * metadata files to represent the current / latest SNAPSHOT available.
574      * 2) If this is a RELEASE reference, and the metadata file does not exist, then
575      * create the metadata file with contents required of the VersionedReference
576      *
577      * @param managedRepository the managed repository where the metadata is kept.
578      * @param reference         the versioned reference to update
579      * @throws LayoutException
580      * @throws RepositoryMetadataException
581      * @throws IOException
582      * @throws ContentNotFoundException 
583      */
584     public void updateMetadata( ManagedRepositoryContent managedRepository, VersionedReference reference )
585         throws LayoutException, RepositoryMetadataException, IOException, ContentNotFoundException
586     {
587         File metadataFile = new File( managedRepository.getRepoRoot(), toPath( reference ) );
588
589         long lastUpdated = getExistingLastUpdated( metadataFile );
590
591         ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
592         metadata.setGroupId( reference.getGroupId() );
593         metadata.setArtifactId( reference.getArtifactId() );
594         
595         if ( VersionUtil.isSnapshot( reference.getVersion() ) )
596         {
597             // Do SNAPSHOT handling.
598             metadata.setVersion( VersionUtil.getBaseVersion( reference.getVersion() ) );
599
600             // Gather up all of the versions found in the reference dir, and any
601             // proxied maven-metadata.xml files.
602             Set<String> snapshotVersions = gatherSnapshotVersions( managedRepository, reference );
603
604             if ( snapshotVersions.isEmpty() )
605             {
606                 throw new ContentNotFoundException( "No snapshot versions found on reference ["
607                     + VersionedReference.toKey( reference ) + "]." );
608             }
609
610             // sort the list to determine to aide in determining the Latest version.
611             List<String> sortedVersions = new ArrayList<String>();
612             sortedVersions.addAll( snapshotVersions );
613             Collections.sort( sortedVersions, new VersionComparator() );
614
615             String latestVersion = sortedVersions.get( sortedVersions.size() - 1 );
616
617             if ( VersionUtil.isUniqueSnapshot( latestVersion ) )
618             {
619                 // The latestVersion will contain the full version string "1.0-alpha-5-20070821.213044-8"
620                 // This needs to be broken down into ${base}-${timestamp}-${build_number}
621
622                 Matcher m = VersionUtil.UNIQUE_SNAPSHOT_PATTERN.matcher( latestVersion );
623                 if ( m.matches() )
624                 {
625                     metadata.setSnapshotVersion( new SnapshotVersion() );
626                     int buildNumber = NumberUtils.toInt( m.group( 3 ), -1 );
627                     metadata.getSnapshotVersion().setBuildNumber( buildNumber );
628
629                     Matcher mtimestamp = VersionUtil.TIMESTAMP_PATTERN.matcher( m.group( 2 ) );
630                     if ( mtimestamp.matches() )
631                     {
632                         String tsDate = mtimestamp.group( 1 );
633                         String tsTime = mtimestamp.group( 2 );
634                         
635                         long snapshotLastUpdated = toLastUpdatedLong( tsDate + tsTime );
636                         
637                         lastUpdated = Math.max( lastUpdated, snapshotLastUpdated );
638                         
639                         metadata.getSnapshotVersion().setTimestamp( m.group( 2 ) );
640                     }
641                 }
642             }
643             else if ( VersionUtil.isGenericSnapshot( latestVersion ) )
644             {
645                 // The latestVersion ends with the generic version string.
646                 // Example: 1.0-alpha-5-SNAPSHOT
647
648                 metadata.setSnapshotVersion( new SnapshotVersion() );
649
650                 /* Disabled due to decision in [MRM-535].
651                  * Do not set metadata.lastUpdated to file.lastModified.
652                  * 
653                  * Should this be the last updated timestamp of the file, or in the case of an 
654                  * archive, the most recent timestamp in the archive?
655                  * 
656                 ArtifactReference artifact = getFirstArtifact( managedRepository, reference );
657
658                 if ( artifact == null )
659                 {
660                     throw new IOException( "Not snapshot artifact found to reference in " + reference );
661                 }
662
663                 File artifactFile = managedRepository.toFile( artifact );
664
665                 if ( artifactFile.exists() )
666                 {
667                     Date lastModified = new Date( artifactFile.lastModified() );
668                     metadata.setLastUpdatedTimestamp( lastModified );
669                 }
670                 */
671             }
672             else
673             {
674                 throw new RepositoryMetadataException( "Unable to process snapshot version <" + latestVersion
675                     + "> reference <" + reference + ">" );
676             }
677         }
678         else
679         {
680             // Do RELEASE handling.
681             metadata.setVersion( reference.getVersion() );
682         }
683
684         // Set last updated
685         if ( lastUpdated > 0 )
686         {
687             metadata.setLastUpdatedTimestamp( toLastUpdatedDate( lastUpdated ) );
688         }
689
690         // Save the metadata model to disk.
691         RepositoryMetadataWriter.write( metadata, metadataFile );
692         checksums.update( metadataFile );
693     }
694
695     private void initConfigVariables()
696     {
697         synchronized ( this.artifactPatterns )
698         {
699             this.artifactPatterns.clear();
700
701             this.artifactPatterns.addAll( filetypes.getFileTypePatterns( FileTypes.ARTIFACTS ) );
702         }
703
704         synchronized ( proxies )
705         {
706             this.proxies.clear();
707
708             List<ProxyConnectorConfiguration> proxyConfigs = configuration.getConfiguration().getProxyConnectors();
709             for( ProxyConnectorConfiguration proxyConfig: proxyConfigs )
710             {
711                 String key = proxyConfig.getSourceRepoId();
712
713                 Set<String> remoteRepoIds = this.proxies.get( key );
714
715                 if ( remoteRepoIds == null )
716                 {
717                     remoteRepoIds = new HashSet<String>();
718                 }
719
720                 remoteRepoIds.add( proxyConfig.getTargetRepoId() );
721
722                 this.proxies.put( key, remoteRepoIds );
723             }
724         }
725     }
726
727     /**
728      * Get the first Artifact found in the provided VersionedReference location.
729      *
730      * @param managedRepository the repository to search within.
731      * @param reference         the reference to the versioned reference to search within
732      * @return the ArtifactReference to the first artifact located within the versioned reference. or null if
733      *         no artifact was found within the versioned reference.
734      * @throws IOException     if the versioned reference is invalid (example: doesn't exist, or isn't a directory)
735      * @throws LayoutException
736      */
737     public ArtifactReference getFirstArtifact( ManagedRepositoryContent managedRepository, VersionedReference reference )
738         throws LayoutException, IOException
739     {
740         String path = toPath( reference );
741
742         int idx = path.lastIndexOf( '/' );
743         if ( idx > 0 )
744         {
745             path = path.substring( 0, idx );
746         }
747
748         File repoDir = new File( managedRepository.getRepoRoot(), path );
749
750         if ( !repoDir.exists() )
751         {
752             throw new IOException( "Unable to gather the list of snapshot versions on a non-existant directory: "
753                 + repoDir.getAbsolutePath() );
754         }
755
756         if ( !repoDir.isDirectory() )
757         {
758             throw new IOException( "Unable to gather the list of snapshot versions on a non-directory: "
759                 + repoDir.getAbsolutePath() );
760         }
761
762         File repoFiles[] = repoDir.listFiles();
763         for ( int i = 0; i < repoFiles.length; i++ )
764         {
765             if ( repoFiles[i].isDirectory() )
766             {
767                 // Skip it. it's a directory.
768                 continue;
769             }
770
771             String relativePath = PathUtil.getRelative( managedRepository.getRepoRoot(), repoFiles[i] );
772
773             if ( matchesArtifactPattern( relativePath ) )
774             {
775                 ArtifactReference artifact = managedRepository.toArtifactReference( relativePath );
776
777                 return artifact;
778             }
779         }
780
781         // No artifact was found.
782         return null;
783     }
784
785     private boolean matchesArtifactPattern( String relativePath )
786     {
787         // Correct the slash pattern.
788         relativePath = relativePath.replace( '\\', '/' );
789
790         Iterator<String> it = this.artifactPatterns.iterator();
791         while ( it.hasNext() )
792         {
793             String pattern = it.next();
794
795             if ( SelectorUtils.matchPath( pattern, relativePath, false ) )
796             {
797                 // Found match
798                 return true;
799             }
800         }
801
802         // No match.
803         return false;
804     }
805 }