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