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