]> source.dussan.org Git - archiva.git/blob
e2dfbc662db4aad6244a664c6e165bdffe7aab85
[archiva.git] /
1 package org.apache.archiva.metadata.repository.file;
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.metadata.model.ArtifactMetadata;
23 import org.apache.archiva.metadata.model.CiManagement;
24 import org.apache.archiva.metadata.model.Dependency;
25 import org.apache.archiva.metadata.model.IssueManagement;
26 import org.apache.archiva.metadata.model.License;
27 import org.apache.archiva.metadata.model.MailingList;
28 import org.apache.archiva.metadata.model.MetadataFacet;
29 import org.apache.archiva.metadata.model.MetadataFacetFactory;
30 import org.apache.archiva.metadata.model.Organization;
31 import org.apache.archiva.metadata.model.ProjectMetadata;
32 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
33 import org.apache.archiva.metadata.model.ProjectVersionReference;
34 import org.apache.archiva.metadata.model.Scm;
35 import org.apache.archiva.metadata.repository.MetadataRepository;
36 import org.apache.commons.io.FileUtils;
37 import org.apache.commons.io.IOUtils;
38 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import java.io.File;
43 import java.io.FileInputStream;
44 import java.io.FileNotFoundException;
45 import java.io.FileOutputStream;
46 import java.io.IOException;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.Comparator;
52 import java.util.Date;
53 import java.util.HashMap;
54 import java.util.HashSet;
55 import java.util.LinkedHashSet;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Properties;
59 import java.util.Set;
60 import java.util.StringTokenizer;
61
62 /**
63  * @plexus.component role="org.apache.archiva.metadata.repository.MetadataRepository"
64  */
65 public class FileMetadataRepository
66     implements MetadataRepository
67 {
68     /**
69      * @plexus.requirement role="org.apache.archiva.metadata.model.MetadataFacetFactory"
70      */
71     private Map<String, MetadataFacetFactory> metadataFacetFactories;
72
73     /**
74      * @plexus.requirement
75      */
76     private ArchivaConfiguration configuration;
77
78     private static final Logger log = LoggerFactory.getLogger( FileMetadataRepository.class );
79
80     private static final String PROJECT_METADATA_KEY = "project-metadata";
81
82     private static final String PROJECT_VERSION_METADATA_KEY = "version-metadata";
83
84     private static final String NAMESPACE_METADATA_KEY = "namespace-metadata";
85
86     private static final String METADATA_KEY = "metadata";
87
88     private File getBaseDirectory( String repoId )
89     {
90         // TODO: should be configurable, like the index
91         String basedir = configuration.getConfiguration().getManagedRepositoriesAsMap().get( repoId ).getLocation();
92         File dir = new File( basedir, ".archiva" );
93         return dir;
94     }
95
96     private File getDirectory( String repoId )
97     {
98         return new File( getBaseDirectory( repoId ), "content" );
99     }
100
101     public void updateProject( String repoId, ProjectMetadata project )
102     {
103         updateProject( repoId, project.getNamespace(), project.getId() );
104     }
105
106     private void updateProject( String repoId, String namespace, String id )
107     {
108         // TODO: this is a more braindead implementation than we would normally expect, for prototyping purposes
109         updateNamespace( repoId, namespace );
110
111         try
112         {
113             File namespaceDirectory = new File( getDirectory( repoId ), namespace );
114             Properties properties = new Properties();
115             properties.setProperty( "namespace", namespace );
116             properties.setProperty( "id", id );
117             writeProperties( properties, new File( namespaceDirectory, id ), PROJECT_METADATA_KEY );
118         }
119         catch ( IOException e )
120         {
121             // TODO!
122             e.printStackTrace();
123         }
124     }
125
126     public void updateProjectVersion( String repoId, String namespace, String projectId,
127                                       ProjectVersionMetadata versionMetadata )
128     {
129         updateProject( repoId, namespace, projectId );
130
131         File directory = new File( getDirectory( repoId ),
132                                    namespace + "/" + projectId + "/" + versionMetadata.getId() );
133
134         Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY );
135         // remove properties that are not references or artifacts
136         for ( Object key : new ArrayList( properties.keySet() ) )
137         {
138             String name = (String) key;
139             if ( !name.contains( ":" ) && !name.equals( "facetIds" ) )
140             {
141                 properties.remove( name );
142             }
143         }
144         properties.setProperty( "id", versionMetadata.getId() );
145         setProperty( properties, "name", versionMetadata.getName() );
146         setProperty( properties, "description", versionMetadata.getDescription() );
147         setProperty( properties, "url", versionMetadata.getUrl() );
148         setProperty( properties, "incomplete", String.valueOf( versionMetadata.isIncomplete() ) );
149         if ( versionMetadata.getScm() != null )
150         {
151             setProperty( properties, "scm.connection", versionMetadata.getScm().getConnection() );
152             setProperty( properties, "scm.developerConnection", versionMetadata.getScm().getDeveloperConnection() );
153             setProperty( properties, "scm.url", versionMetadata.getScm().getUrl() );
154         }
155         if ( versionMetadata.getCiManagement() != null )
156         {
157             setProperty( properties, "ci.system", versionMetadata.getCiManagement().getSystem() );
158             setProperty( properties, "ci.url", versionMetadata.getCiManagement().getUrl() );
159         }
160         if ( versionMetadata.getIssueManagement() != null )
161         {
162             setProperty( properties, "issue.system", versionMetadata.getIssueManagement().getSystem() );
163             setProperty( properties, "issue.url", versionMetadata.getIssueManagement().getUrl() );
164         }
165         if ( versionMetadata.getOrganization() != null )
166         {
167             setProperty( properties, "org.name", versionMetadata.getOrganization().getName() );
168             setProperty( properties, "org.url", versionMetadata.getOrganization().getUrl() );
169         }
170         int i = 0;
171         for ( License license : versionMetadata.getLicenses() )
172         {
173             setProperty( properties, "license." + i + ".name", license.getName() );
174             setProperty( properties, "license." + i + ".url", license.getUrl() );
175             i++;
176         }
177         i = 0;
178         for ( MailingList mailingList : versionMetadata.getMailingLists() )
179         {
180             setProperty( properties, "mailingList." + i + ".archive", mailingList.getMainArchiveUrl() );
181             setProperty( properties, "mailingList." + i + ".name", mailingList.getName() );
182             setProperty( properties, "mailingList." + i + ".post", mailingList.getPostAddress() );
183             setProperty( properties, "mailingList." + i + ".unsubscribe", mailingList.getUnsubscribeAddress() );
184             setProperty( properties, "mailingList." + i + ".subscribe", mailingList.getSubscribeAddress() );
185             setProperty( properties, "mailingList." + i + ".otherArchives", join( mailingList.getOtherArchives() ) );
186             i++;
187         }
188         i = 0;
189         for ( Dependency dependency : versionMetadata.getDependencies() )
190         {
191             setProperty( properties, "dependency." + i + ".classifier", dependency.getClassifier() );
192             setProperty( properties, "dependency." + i + ".scope", dependency.getScope() );
193             setProperty( properties, "dependency." + i + ".systemPath", dependency.getSystemPath() );
194             setProperty( properties, "dependency." + i + ".artifactId", dependency.getArtifactId() );
195             setProperty( properties, "dependency." + i + ".groupId", dependency.getGroupId() );
196             setProperty( properties, "dependency." + i + ".version", dependency.getVersion() );
197             setProperty( properties, "dependency." + i + ".type", dependency.getType() );
198             i++;
199         }
200         Set<String> facetIds = new LinkedHashSet<String>( versionMetadata.getFacetIds() );
201         facetIds.addAll( Arrays.asList( properties.getProperty( "facetIds", "" ).split( "," ) ) );
202         properties.setProperty( "facetIds", join( facetIds ) );
203
204         updateProjectVersionFacets( versionMetadata, properties );
205
206         try
207         {
208             writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY );
209         }
210         catch ( IOException e )
211         {
212             // TODO
213             e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
214         }
215     }
216
217     private void updateProjectVersionFacets( ProjectVersionMetadata versionMetadata, Properties properties )
218     {
219         for ( MetadataFacet facet : versionMetadata.getFacetList() )
220         {
221             for ( Map.Entry<String,String> entry : facet.toProperties().entrySet() )
222             {
223                 properties.setProperty( facet.getFacetId() + ":" + entry.getKey(), entry.getValue() );
224             }
225         }
226     }
227
228     public void updateProjectReference( String repoId, String namespace, String projectId, String projectVersion,
229                                         ProjectVersionReference reference )
230     {
231         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion );
232
233         Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY );
234         int i = Integer.valueOf( properties.getProperty( "ref:lastReferenceNum", "-1" ) ) + 1;
235         setProperty( properties, "ref:lastReferenceNum", Integer.toString( i ) );
236         setProperty( properties, "ref:reference." + i + ".namespace", reference.getNamespace() );
237         setProperty( properties, "ref:reference." + i + ".projectId", reference.getProjectId() );
238         setProperty( properties, "ref:reference." + i + ".projectVersion", reference.getProjectVersion() );
239         setProperty( properties, "ref:reference." + i + ".referenceType", reference.getReferenceType().toString() );
240
241         try
242         {
243             writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY );
244         }
245         catch ( IOException e )
246         {
247             // TODO
248             e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
249         }
250     }
251
252     public void updateNamespace( String repoId, String namespace )
253     {
254         try
255         {
256             File namespaceDirectory = new File( getDirectory( repoId ), namespace );
257             Properties properties = new Properties();
258             properties.setProperty( "namespace", namespace );
259             writeProperties( properties, namespaceDirectory, NAMESPACE_METADATA_KEY );
260
261         }
262         catch ( IOException e )
263         {
264             // TODO!
265             e.printStackTrace();
266         }
267     }
268
269     public List<String> getMetadataFacets( String repoId, String facetId )
270     {
271         File directory = getMetadataDirectory( repoId, facetId );
272         List<String> facets = new ArrayList<String>();
273         recurse( facets, "", directory );
274         return facets;
275     }
276
277     private void recurse( List<String> facets, String prefix, File directory )
278     {
279         File[] list = directory.listFiles();
280         if ( list != null )
281         {
282             for ( File dir : list )
283             {
284                 if ( dir.isDirectory() )
285                 {
286                     recurse( facets, prefix + "/" + dir.getName(), dir );
287                 }
288                 else if ( dir.getName().equals( METADATA_KEY + ".properties" ) )
289                 {
290                     facets.add( prefix.substring( 1 ) );
291                 }
292             }
293         }
294     }
295
296     public MetadataFacet getMetadataFacet( String repositoryId, String facetId, String name )
297     {
298         Properties properties;
299         try
300         {
301             properties = readProperties( new File( getMetadataDirectory( repositoryId, facetId ), name ),
302                                          METADATA_KEY );
303         }
304         catch ( FileNotFoundException e )
305         {
306             return null;
307         }
308         catch ( IOException e )
309         {
310             // TODO
311             e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
312             return null;
313         }
314         MetadataFacet metadataFacet = null;
315         MetadataFacetFactory metadataFacetFactory = metadataFacetFactories.get( facetId );
316         if ( metadataFacetFactory != null )
317         {
318             metadataFacet = metadataFacetFactory.createMetadataFacet( repositoryId, name );
319             Map<String, String> map = new HashMap<String, String>();
320             for ( Object key : new ArrayList( properties.keySet() ) )
321             {
322                 String property = (String) key;
323                 map.put( property, properties.getProperty( property ) );
324             }
325             metadataFacet.fromProperties( map );
326         }
327         return metadataFacet;
328     }
329
330     public void addMetadataFacet( String repositoryId, MetadataFacet metadataFacet )
331     {
332         Properties properties = new Properties();
333         properties.putAll( metadataFacet.toProperties() );
334
335         try
336         {
337             File directory = new File( getMetadataDirectory( repositoryId, metadataFacet.getFacetId() ),
338                                        metadataFacet.getName() );
339             writeProperties( properties, directory, METADATA_KEY );
340         }
341         catch ( IOException e )
342         {
343             // TODO!
344             e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
345         }
346     }
347
348     public void removeMetadataFacets( String repositoryId, String facetId )
349     {
350         try
351         {
352             FileUtils.deleteDirectory( getMetadataDirectory( repositoryId, facetId ) );
353         }
354         catch ( IOException e )
355         {
356             // TODO!
357             e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
358         }
359     }
360
361     public void removeMetadataFacet( String repoId, String facetId, String name )
362     {
363         File dir = new File( getMetadataDirectory( repoId, facetId ), name );
364         try
365         {
366             FileUtils.deleteDirectory( dir );
367         }
368         catch ( IOException e )
369         {
370             // TODO
371             e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
372         }
373     }
374
375     public List<ArtifactMetadata> getArtifactsByDateRange( String repoId, Date startTime, Date endTime )
376     {
377         // TODO: this is quite slow - if we are to persist with this repository implementation we should build an index
378         //  of this information (eg. in Lucene, as before)
379
380         List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
381         for ( String ns : getRootNamespaces( repoId ) )
382         {
383             getArtifactsByDateRange( artifacts, repoId, ns, startTime, endTime );
384         }
385         Collections.sort( artifacts, new ArtifactComparator() );
386         return artifacts;
387     }
388
389     private void getArtifactsByDateRange( List<ArtifactMetadata> artifacts, String repoId, String ns, Date startTime,
390                                           Date endTime )
391     {
392         for ( String namespace : getNamespaces( repoId, ns ) )
393         {
394             getArtifactsByDateRange( artifacts, repoId, ns + "." + namespace, startTime, endTime );
395         }
396
397         for ( String project : getProjects( repoId, ns ) )
398         {
399             for ( String version : getProjectVersions( repoId, ns, project ) )
400             {
401                 for ( ArtifactMetadata artifact : getArtifacts( repoId, ns, project, version ) )
402                 {
403                     if ( startTime == null || startTime.before( artifact.getWhenGathered() ) )
404                     {
405                         if ( endTime == null || endTime.after( artifact.getWhenGathered() ) )
406                         {
407                             artifacts.add( artifact );
408                         }
409                     }
410                 }
411             }
412         }
413     }
414
415     public Collection<ArtifactMetadata> getArtifacts( String repoId, String namespace, String projectId,
416                                                       String projectVersion )
417     {
418         Map<String, ArtifactMetadata> artifacts = new HashMap<String, ArtifactMetadata>();
419
420         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion );
421
422         Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY );
423
424         for ( Map.Entry entry : properties.entrySet() )
425         {
426             String name = (String) entry.getKey();
427             StringTokenizer tok = new StringTokenizer( name, ":" );
428             if ( tok.hasMoreTokens() && "artifact".equals( tok.nextToken() ) )
429             {
430                 String field = tok.nextToken();
431                 String id = tok.nextToken();
432
433                 ArtifactMetadata artifact = artifacts.get( id );
434                 if ( artifact == null )
435                 {
436                     artifact = new ArtifactMetadata();
437                     artifact.setRepositoryId( repoId );
438                     artifact.setNamespace( namespace );
439                     artifact.setProject( projectId );
440                     artifact.setVersion( projectVersion );
441                     artifact.setId( id );
442                     artifacts.put( id, artifact );
443                 }
444
445                 String value = (String) entry.getValue();
446                 if ( "updated".equals( field ) )
447                 {
448                     artifact.setFileLastModified( Long.valueOf( value ) );
449                 }
450                 else if ( "size".equals( field ) )
451                 {
452                     artifact.setSize( Long.valueOf( value ) );
453                 }
454                 else if ( "whenGathered".equals( field ) )
455                 {
456                     artifact.setWhenGathered( new Date( Long.valueOf( value ) ) );
457                 }
458                 else if ( "version".equals( field ) )
459                 {
460                     artifact.setVersion( value );
461                 }
462                 else if ( "md5".equals( field ) )
463                 {
464                     artifact.setMd5( value );
465                 }
466                 else if ( "sha1".equals( field ) )
467                 {
468                     artifact.setSha1( value );
469                 }
470                 else if ( "facetIds".equals( field ) )
471                 {
472                     if ( value.length() > 0 )
473                     {
474                         String propertyPrefix = "artifact:facet:" + id + ":";
475                         for ( String facetId : value.split( "," ) )
476                         {
477                             MetadataFacetFactory factory = metadataFacetFactories.get( facetId );
478                             if ( factory == null )
479                             {
480                                 log.error( "Attempted to load unknown artifact metadata facet: " + facetId );
481                             }
482                             else
483                             {
484                                 MetadataFacet facet = factory.createMetadataFacet();
485                                 String prefix = propertyPrefix + facet.getFacetId();
486                                 Map<String, String> map = new HashMap<String, String>();
487                                 for ( Object key : new ArrayList( properties.keySet() ) )
488                                 {
489                                     String property = (String) key;
490                                     if ( property.startsWith( prefix ) )
491                                     {
492                                         map.put( property.substring( prefix.length() + 1 ), properties.getProperty(
493                                             property ) );
494                                     }
495                                 }
496                                 facet.fromProperties( map );
497                                 artifact.addFacet( facet );
498                             }
499                         }
500                     }
501
502                     updateArtifactFacets( artifact, properties );
503                 }
504             }
505         }
506         return artifacts.values();
507     }
508
509     private void updateArtifactFacets( ArtifactMetadata artifact, Properties properties )
510     {
511         String propertyPrefix = "artifact:facet:" + artifact.getId() + ":";
512         for ( MetadataFacet facet : artifact.getFacetList() )
513         {
514             for ( Map.Entry<String, String> e : facet.toProperties().entrySet() )
515             {
516                 String key = propertyPrefix + facet.getFacetId() + ":" + e.getKey();
517                 properties.setProperty( key, e.getValue() );
518             }
519         }
520     }
521
522     public Collection<String> getRepositories()
523     {
524         return configuration.getConfiguration().getManagedRepositoriesAsMap().keySet();
525     }
526
527     public List<ArtifactMetadata> getArtifactsByChecksum( String repositoryId, String checksum )
528     {
529         // TODO: this is quite slow - if we are to persist with this repository implementation we should build an index
530         //  of this information (eg. in Lucene, as before)
531         // alternatively, we could build a referential tree in the content repository, however it would need some levels
532         // of depth to avoid being too broad to be useful (eg. /repository/checksums/a/ab/abcdef1234567)
533
534         List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
535         for ( String ns : getRootNamespaces( repositoryId ) )
536         {
537             getArtifactsByChecksum( artifacts, repositoryId, ns, checksum );
538         }
539         return artifacts;
540     }
541
542     public void deleteArtifact( String repoId, String namespace, String project, String version, String id )
543     {
544         File directory = new File( getDirectory( repoId ), namespace + "/" + project + "/" + version );
545
546         Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY );
547
548         properties.remove( "artifact:updated:" + id );
549         properties.remove( "artifact:whenGathered:" + id );
550         properties.remove( "artifact:size:" + id );
551         properties.remove( "artifact:md5:" + id );
552         properties.remove( "artifact:sha1:" + id );
553         properties.remove( "artifact:version:" + id );
554         properties.remove( "artifact:facetIds:" + id );
555         
556         String prefix = "artifact:facet:" + id + ":";
557         for ( Object key : new ArrayList( properties.keySet() ) )
558         {
559             String property = (String) key;
560             if ( property.startsWith( prefix ) )
561             {
562                 properties.remove( property );
563             }
564         }
565
566         try
567         {
568             writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY );
569         }
570         catch ( IOException e )
571         {
572             // TODO
573             e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
574         }
575     }
576
577     public void deleteRepository( String repoId )
578     {
579         try
580         {
581             FileUtils.deleteDirectory( getDirectory( repoId ) );
582         }
583         catch ( IOException e )
584         {
585             // TODO
586             e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
587         }
588     }
589
590     private void getArtifactsByChecksum( List<ArtifactMetadata> artifacts, String repositoryId, String ns,
591                                          String checksum )
592     {
593         for ( String namespace : getNamespaces( repositoryId, ns ) )
594         {
595             getArtifactsByChecksum( artifacts, repositoryId, ns + "." + namespace, checksum );
596         }
597
598         for ( String project : getProjects( repositoryId, ns ) )
599         {
600             for ( String version : getProjectVersions( repositoryId, ns, project ) )
601             {
602                 for ( ArtifactMetadata artifact : getArtifacts( repositoryId, ns, project, version ) )
603                 {
604                     if ( checksum.equals( artifact.getMd5() ) || checksum.equals( artifact.getSha1() ) )
605                     {
606                         artifacts.add( artifact );
607                     }
608                 }
609             }
610         }
611     }
612
613     private File getMetadataDirectory( String repoId, String facetId )
614     {
615         return new File( getBaseDirectory( repoId ), "facets/" + facetId );
616     }
617
618     private String join( Collection<String> ids )
619     {
620         if ( !ids.isEmpty() )
621         {
622             StringBuilder s = new StringBuilder();
623             for ( String id : ids )
624             {
625                 s.append( id );
626                 s.append( "," );
627             }
628             return s.substring( 0, s.length() - 1 );
629         }
630         return "";
631     }
632
633     private void setProperty( Properties properties, String name, String value )
634     {
635         if ( value != null )
636         {
637             properties.setProperty( name, value );
638         }
639     }
640
641     public void updateArtifact( String repoId, String namespace, String projectId, String projectVersion,
642                                 ArtifactMetadata artifact )
643     {
644         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion );
645
646         Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY );
647
648         String id = artifact.getId();
649         properties.setProperty( "artifact:updated:" + id, Long.toString( artifact.getFileLastModified().getTime() ) );
650         properties.setProperty( "artifact:whenGathered:" + id, Long.toString( artifact.getWhenGathered().getTime() ) );
651         properties.setProperty( "artifact:size:" + id, Long.toString( artifact.getSize() ) );
652         if ( artifact.getMd5() != null )
653         {
654             properties.setProperty( "artifact:md5:" + id, artifact.getMd5() );
655         }
656         if ( artifact.getSha1() != null )
657         {
658             properties.setProperty( "artifact:sha1:" + id, artifact.getSha1() );
659         }
660         properties.setProperty( "artifact:version:" + id, artifact.getVersion() );
661
662         Set<String> facetIds = new LinkedHashSet<String>( artifact.getFacetIds() );
663         String property = "artifact:facetIds:" + id;
664         facetIds.addAll( Arrays.asList( properties.getProperty( property, "" ).split( "," ) ) );
665         properties.setProperty( property, join( facetIds ) );
666
667         updateArtifactFacets( artifact, properties );
668
669         try
670         {
671             writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY );
672         }
673         catch ( IOException e )
674         {
675             // TODO
676             e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
677         }
678     }
679
680     private Properties readOrCreateProperties( File directory, String propertiesKey )
681     {
682         try
683         {
684             return readProperties( directory, propertiesKey );
685         }
686         catch ( FileNotFoundException e )
687         {
688             // ignore and return new properties
689         }
690         catch ( IOException e )
691         {
692             // TODO
693             e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
694         }
695         return new Properties();
696     }
697
698     private Properties readProperties( File directory, String propertiesKey )
699         throws IOException
700     {
701         Properties properties = new Properties();
702         FileInputStream in = null;
703         try
704         {
705             in = new FileInputStream( new File( directory, propertiesKey + ".properties" ) );
706             properties.load( in );
707         }
708         finally
709         {
710             IOUtils.closeQuietly( in );
711         }
712         return properties;
713     }
714
715     public ProjectMetadata getProject( String repoId, String namespace, String projectId )
716     {
717         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId );
718
719         Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY );
720
721         ProjectMetadata project = new ProjectMetadata();
722         project.setNamespace( properties.getProperty( "namespace" ) );
723         project.setId( properties.getProperty( "id" ) );
724         return project;
725     }
726
727     public ProjectVersionMetadata getProjectVersion( String repoId, String namespace, String projectId,
728                                                      String projectVersion )
729     {
730         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion );
731
732         Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY );
733         String id = properties.getProperty( "id" );
734         ProjectVersionMetadata versionMetadata = null;
735         if ( id != null )
736         {
737             versionMetadata = new ProjectVersionMetadata();
738             versionMetadata.setId( id );
739             versionMetadata.setName( properties.getProperty( "name" ) );
740             versionMetadata.setDescription( properties.getProperty( "description" ) );
741             versionMetadata.setUrl( properties.getProperty( "url" ) );
742             versionMetadata.setIncomplete( Boolean.valueOf( properties.getProperty( "incomplete", "false" ) ) );
743
744             String scmConnection = properties.getProperty( "scm.connection" );
745             String scmDeveloperConnection = properties.getProperty( "scm.developerConnection" );
746             String scmUrl = properties.getProperty( "scm.url" );
747             if ( scmConnection != null || scmDeveloperConnection != null || scmUrl != null )
748             {
749                 Scm scm = new Scm();
750                 scm.setConnection( scmConnection );
751                 scm.setDeveloperConnection( scmDeveloperConnection );
752                 scm.setUrl( scmUrl );
753                 versionMetadata.setScm( scm );
754             }
755
756             String ciSystem = properties.getProperty( "ci.system" );
757             String ciUrl = properties.getProperty( "ci.url" );
758             if ( ciSystem != null || ciUrl != null )
759             {
760                 CiManagement ci = new CiManagement();
761                 ci.setSystem( ciSystem );
762                 ci.setUrl( ciUrl );
763                 versionMetadata.setCiManagement( ci );
764             }
765
766             String issueSystem = properties.getProperty( "issue.system" );
767             String issueUrl = properties.getProperty( "issue.url" );
768             if ( issueSystem != null || issueUrl != null )
769             {
770                 IssueManagement issueManagement = new IssueManagement();
771                 issueManagement.setSystem( issueSystem );
772                 issueManagement.setUrl( issueUrl );
773                 versionMetadata.setIssueManagement( issueManagement );
774             }
775
776             String orgName = properties.getProperty( "org.name" );
777             String orgUrl = properties.getProperty( "org.url" );
778             if ( orgName != null || orgUrl != null )
779             {
780                 Organization org = new Organization();
781                 org.setName( orgName );
782                 org.setUrl( orgUrl );
783                 versionMetadata.setOrganization( org );
784             }
785
786             boolean done = false;
787             int i = 0;
788             while ( !done )
789             {
790                 String licenseName = properties.getProperty( "license." + i + ".name" );
791                 String licenseUrl = properties.getProperty( "license." + i + ".url" );
792                 if ( licenseName != null || licenseUrl != null )
793                 {
794                     License license = new License();
795                     license.setName( licenseName );
796                     license.setUrl( licenseUrl );
797                     versionMetadata.addLicense( license );
798                 }
799                 else
800                 {
801                     done = true;
802                 }
803                 i++;
804             }
805
806             done = false;
807             i = 0;
808             while ( !done )
809             {
810                 String mailingListName = properties.getProperty( "mailingList." + i + ".name" );
811                 if ( mailingListName != null )
812                 {
813                     MailingList mailingList = new MailingList();
814                     mailingList.setName( mailingListName );
815                     mailingList.setMainArchiveUrl( properties.getProperty( "mailingList." + i + ".archive" ) );
816                     mailingList.setOtherArchives( Arrays.asList( properties.getProperty(
817                         "mailingList." + i + ".otherArchives" ).split( "," ) ) );
818                     mailingList.setPostAddress( properties.getProperty( "mailingList." + i + ".post" ) );
819                     mailingList.setSubscribeAddress( properties.getProperty( "mailingList." + i + ".subscribe" ) );
820                     mailingList.setUnsubscribeAddress( properties.getProperty( "mailingList." + i + ".unsubscribe" ) );
821                     versionMetadata.addMailingList( mailingList );
822                 }
823                 else
824                 {
825                     done = true;
826                 }
827                 i++;
828             }
829
830             done = false;
831             i = 0;
832             while ( !done )
833             {
834                 String dependencyArtifactId = properties.getProperty( "dependency." + i + ".artifactId" );
835                 if ( dependencyArtifactId != null )
836                 {
837                     Dependency dependency = new Dependency();
838                     dependency.setArtifactId( dependencyArtifactId );
839                     dependency.setGroupId( properties.getProperty( "dependency." + i + ".groupId" ) );
840                     dependency.setClassifier( properties.getProperty( "dependency." + i + ".classifier" ) );
841                     dependency.setOptional( Boolean.valueOf( properties.getProperty(
842                         "dependency." + i + ".optional" ) ) );
843                     dependency.setScope( properties.getProperty( "dependency." + i + ".scope" ) );
844                     dependency.setSystemPath( properties.getProperty( "dependency." + i + ".systemPath" ) );
845                     dependency.setType( properties.getProperty( "dependency." + i + ".type" ) );
846                     dependency.setVersion( properties.getProperty( "dependency." + i + ".version" ) );
847                     versionMetadata.addDependency( dependency );
848                 }
849                 else
850                 {
851                     done = true;
852                 }
853                 i++;
854             }
855
856             String facetIds = properties.getProperty( "facetIds", "" );
857             if ( facetIds.length() > 0 )
858             {
859                 for ( String facetId : facetIds.split( "," ) )
860                 {
861                     MetadataFacetFactory factory = metadataFacetFactories.get( facetId );
862                     if ( factory == null )
863                     {
864                         log.error( "Attempted to load unknown project version metadata facet: " + facetId );
865                     }
866                     else
867                     {
868                         MetadataFacet facet = factory.createMetadataFacet();
869                         Map<String, String> map = new HashMap<String, String>();
870                         for ( Object key : new ArrayList( properties.keySet() ) )
871                         {
872                             String property = (String) key;
873                             if ( property.startsWith( facet.getFacetId() ) )
874                             {
875                                 map.put( property.substring( facet.getFacetId().length() + 1 ), properties.getProperty(
876                                     property ) );
877                             }
878                         }
879                         facet.fromProperties( map );
880                         versionMetadata.addFacet( facet );
881                     }
882                 }
883             }
884
885             updateProjectVersionFacets( versionMetadata, properties );
886         }
887         return versionMetadata;
888     }
889
890     public Collection<String> getArtifactVersions( String repoId, String namespace, String projectId,
891                                                    String projectVersion )
892     {
893         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion );
894
895         Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY );
896
897         Set<String> versions = new HashSet<String>();
898         for ( Map.Entry entry : properties.entrySet() )
899         {
900             String name = (String) entry.getKey();
901             if ( name.startsWith( "artifact:version:" ) )
902             {
903                 versions.add( (String) entry.getValue() );
904             }
905         }
906         return versions;
907     }
908
909     public Collection<ProjectVersionReference> getProjectReferences( String repoId, String namespace, String projectId,
910                                                                      String projectVersion )
911     {
912         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion );
913
914         Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY );
915         int numberOfRefs = Integer.valueOf( properties.getProperty( "ref:lastReferenceNum", "-1" ) ) + 1;
916
917         List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
918         for ( int i = 0; i < numberOfRefs; i++ )
919         {
920             ProjectVersionReference reference = new ProjectVersionReference();
921             reference.setProjectId( properties.getProperty( "ref:reference." + i + ".projectId" ) );
922             reference.setNamespace( properties.getProperty( "ref:reference." + i + ".namespace" ) );
923             reference.setProjectVersion( properties.getProperty( "ref:reference." + i + ".projectVersion" ) );
924             reference.setReferenceType( ProjectVersionReference.ReferenceType.valueOf( properties.getProperty(
925                 "ref:reference." + i + ".referenceType" ) ) );
926             references.add( reference );
927         }
928         return references;
929     }
930
931     public Collection<String> getRootNamespaces( String repoId )
932     {
933         return getNamespaces( repoId, null );
934     }
935
936     public Collection<String> getNamespaces( String repoId, String baseNamespace )
937     {
938         List<String> allNamespaces = new ArrayList<String>();
939         File directory = getDirectory( repoId );
940         File[] files = directory.listFiles();
941         if ( files != null )
942         {
943             for ( File namespace : files )
944             {
945                 if ( new File( namespace, NAMESPACE_METADATA_KEY + ".properties" ).exists() )
946                 {
947                     allNamespaces.add( namespace.getName() );
948                 }
949             }
950         }
951
952         Set<String> namespaces = new LinkedHashSet<String>();
953         int fromIndex = baseNamespace != null ? baseNamespace.length() + 1 : 0;
954         for ( String namespace : allNamespaces )
955         {
956             if ( baseNamespace == null || namespace.startsWith( baseNamespace + "." ) )
957             {
958                 int i = namespace.indexOf( '.', fromIndex );
959                 if ( i >= 0 )
960                 {
961                     namespaces.add( namespace.substring( fromIndex, i ) );
962                 }
963                 else
964                 {
965                     namespaces.add( namespace.substring( fromIndex ) );
966                 }
967             }
968         }
969         return new ArrayList<String>( namespaces );
970     }
971
972     public Collection<String> getProjects( String repoId, String namespace )
973     {
974         List<String> projects = new ArrayList<String>();
975         File directory = new File( getDirectory( repoId ), namespace );
976         File[] files = directory.listFiles();
977         if ( files != null )
978         {
979             for ( File project : files )
980             {
981                 if ( new File( project, PROJECT_METADATA_KEY + ".properties" ).exists() )
982                 {
983                     projects.add( project.getName() );
984                 }
985             }
986         }
987         return projects;
988     }
989
990     public Collection<String> getProjectVersions( String repoId, String namespace, String projectId )
991     {
992         List<String> projectVersions = new ArrayList<String>();
993         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId );
994         File[] files = directory.listFiles();
995         if ( files != null )
996         {
997             for ( File projectVersion : files )
998             {
999                 if ( new File( projectVersion, PROJECT_VERSION_METADATA_KEY + ".properties" ).exists() )
1000                 {
1001                     projectVersions.add( projectVersion.getName() );
1002                 }
1003             }
1004         }
1005         return projectVersions;
1006     }
1007
1008     private void writeProperties( Properties properties, File directory, String propertiesKey )
1009         throws IOException
1010     {
1011         directory.mkdirs();
1012         FileOutputStream os = new FileOutputStream( new File( directory, propertiesKey + ".properties" ) );
1013         try
1014         {
1015             properties.store( os, null );
1016         }
1017         finally
1018         {
1019             IOUtils.closeQuietly( os );
1020         }
1021     }
1022
1023     public void setMetadataFacetFactories( Map<String, MetadataFacetFactory> metadataFacetFactories )
1024     {
1025         this.metadataFacetFactories = metadataFacetFactories;
1026     }
1027
1028     public void setConfiguration( ArchivaConfiguration configuration )
1029     {
1030         this.configuration = configuration;
1031     }
1032
1033     private static class ArtifactComparator
1034         implements Comparator<ArtifactMetadata>
1035     {
1036         public int compare( ArtifactMetadata artifact1, ArtifactMetadata artifact2 )
1037         {
1038             if ( artifact1.getWhenGathered() == artifact2.getWhenGathered() )
1039             {
1040                 return 0;
1041             }
1042             if ( artifact1.getWhenGathered() == null )
1043             {
1044                 return 1;
1045             }
1046             if ( artifact2.getWhenGathered() == null )
1047             {
1048                 return -1;
1049             }
1050             return artifact1.getWhenGathered().compareTo( artifact2.getWhenGathered() );
1051         }
1052     }
1053 }