]> source.dussan.org Git - archiva.git/blob
cca21bcd4c252353dfd9791122ae952a052a1180
[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     @Override
782     public List<ArtifactMetadata> getArtifactsByProjectVersionMetadata( String key, String value, String repositoryId )
783         throws MetadataRepositoryException
784     {
785         throw new UnsupportedOperationException( "not yet implemented in File backend" );
786     }
787
788     @Override
789     public List<ArtifactMetadata> getArtifactsByMetadata( String key, String value, String repositoryId )
790         throws MetadataRepositoryException
791     {
792         throw new UnsupportedOperationException( "not yet implemented in File backend" );
793     }
794
795     @Override
796     public List<ArtifactMetadata> getArtifactsByProperty( String key, String value, String repositoryId )
797         throws MetadataRepositoryException
798     {
799         throw new UnsupportedOperationException( "getArtifactsByProperty not yet implemented in File backend" );
800     }
801
802     private File getMetadataDirectory( String repoId, String facetId )
803     {
804         return new File( getBaseDirectory( repoId ), "facets/" + facetId );
805     }
806
807     private String join( Collection<String> ids )
808     {
809         if ( ids != null && !ids.isEmpty() )
810         {
811             StringBuilder s = new StringBuilder();
812             for ( String id : ids )
813             {
814                 s.append( id );
815                 s.append( "," );
816             }
817             return s.substring( 0, s.length() - 1 );
818         }
819         return "";
820     }
821
822     private void setProperty( Properties properties, String name, String value )
823     {
824         if ( value != null )
825         {
826             properties.setProperty( name, value );
827         }
828     }
829
830     @Override
831     public void updateArtifact( String repoId, String namespace, String projectId, String projectVersion,
832                                 ArtifactMetadata artifact )
833     {
834         ProjectVersionMetadata metadata = new ProjectVersionMetadata();
835         metadata.setId( projectVersion );
836         updateProjectVersion( repoId, namespace, projectId, metadata );
837
838         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion );
839
840         Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY );
841
842         clearMetadataFacetProperties( artifact.getFacetList(), properties, "artifact:facet:" + artifact.getId() + ":" );
843
844         String id = artifact.getId();
845         properties.setProperty( "artifact:updated:" + id, Long.toString( artifact.getFileLastModified().getTime() ) );
846         properties.setProperty( "artifact:whenGathered:" + id, Long.toString( artifact.getWhenGathered().getTime() ) );
847         properties.setProperty( "artifact:size:" + id, Long.toString( artifact.getSize() ) );
848         if ( artifact.getMd5() != null )
849         {
850             properties.setProperty( "artifact:md5:" + id, artifact.getMd5() );
851         }
852         if ( artifact.getSha1() != null )
853         {
854             properties.setProperty( "artifact:sha1:" + id, artifact.getSha1() );
855         }
856         properties.setProperty( "artifact:version:" + id, artifact.getVersion() );
857
858         Set<String> facetIds = new LinkedHashSet<String>( artifact.getFacetIds() );
859         String property = "artifact:facetIds:" + id;
860         facetIds.addAll( Arrays.asList( properties.getProperty( property, "" ).split( "," ) ) );
861         properties.setProperty( property, join( facetIds ) );
862
863         updateArtifactFacets( artifact, properties );
864
865         try
866         {
867             writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY );
868         }
869         catch ( IOException e )
870         {
871             // TODO
872             log.error( e.getMessage(), e );
873         }
874     }
875
876     private Properties readOrCreateProperties( File directory, String propertiesKey )
877     {
878         try
879         {
880             return readProperties( directory, propertiesKey );
881         }
882         catch ( FileNotFoundException | NoSuchFileException e )
883         {
884             // ignore and return new properties
885         }
886         catch ( IOException e )
887         {
888             // TODO
889             log.error( e.getMessage(), e );
890         }
891         return new Properties();
892     }
893
894     private Properties readProperties( File directory, String propertiesKey )
895         throws IOException
896     {
897         Properties properties = new Properties();
898         try (InputStream in = Files.newInputStream( new File( directory, propertiesKey + ".properties" ).toPath() ))
899         {
900
901             properties.load( in );
902         }
903         return properties;
904     }
905
906     @Override
907     public ProjectMetadata getProject( String repoId, String namespace, String projectId )
908     {
909         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId );
910
911         Properties properties = readOrCreateProperties( directory, PROJECT_METADATA_KEY );
912
913         ProjectMetadata project = null;
914
915         String id = properties.getProperty( "id" );
916         if ( id != null )
917         {
918             project = new ProjectMetadata();
919             project.setNamespace( properties.getProperty( "namespace" ) );
920             project.setId( id );
921         }
922
923         return project;
924     }
925
926     @Override
927     public ProjectVersionMetadata getProjectVersion( String repoId, String namespace, String projectId,
928                                                      String projectVersion )
929     {
930         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion );
931
932         Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY );
933         String id = properties.getProperty( "id" );
934         ProjectVersionMetadata versionMetadata = null;
935         if ( id != null )
936         {
937             versionMetadata = new ProjectVersionMetadata();
938             versionMetadata.setId( id );
939             versionMetadata.setName( properties.getProperty( "name" ) );
940             versionMetadata.setDescription( properties.getProperty( "description" ) );
941             versionMetadata.setUrl( properties.getProperty( "url" ) );
942             versionMetadata.setIncomplete( Boolean.valueOf( properties.getProperty( "incomplete", "false" ) ) );
943
944             String scmConnection = properties.getProperty( "scm.connection" );
945             String scmDeveloperConnection = properties.getProperty( "scm.developerConnection" );
946             String scmUrl = properties.getProperty( "scm.url" );
947             if ( scmConnection != null || scmDeveloperConnection != null || scmUrl != null )
948             {
949                 Scm scm = new Scm();
950                 scm.setConnection( scmConnection );
951                 scm.setDeveloperConnection( scmDeveloperConnection );
952                 scm.setUrl( scmUrl );
953                 versionMetadata.setScm( scm );
954             }
955
956             String ciSystem = properties.getProperty( "ci.system" );
957             String ciUrl = properties.getProperty( "ci.url" );
958             if ( ciSystem != null || ciUrl != null )
959             {
960                 CiManagement ci = new CiManagement();
961                 ci.setSystem( ciSystem );
962                 ci.setUrl( ciUrl );
963                 versionMetadata.setCiManagement( ci );
964             }
965
966             String issueSystem = properties.getProperty( "issue.system" );
967             String issueUrl = properties.getProperty( "issue.url" );
968             if ( issueSystem != null || issueUrl != null )
969             {
970                 IssueManagement issueManagement = new IssueManagement();
971                 issueManagement.setSystem( issueSystem );
972                 issueManagement.setUrl( issueUrl );
973                 versionMetadata.setIssueManagement( issueManagement );
974             }
975
976             String orgName = properties.getProperty( "org.name" );
977             String orgUrl = properties.getProperty( "org.url" );
978             if ( orgName != null || orgUrl != null )
979             {
980                 Organization org = new Organization();
981                 org.setName( orgName );
982                 org.setUrl( orgUrl );
983                 versionMetadata.setOrganization( org );
984             }
985
986             boolean done = false;
987             int i = 0;
988             while ( !done )
989             {
990                 String licenseName = properties.getProperty( "license." + i + ".name" );
991                 String licenseUrl = properties.getProperty( "license." + i + ".url" );
992                 if ( licenseName != null || licenseUrl != null )
993                 {
994                     License license = new License();
995                     license.setName( licenseName );
996                     license.setUrl( licenseUrl );
997                     versionMetadata.addLicense( license );
998                 }
999                 else
1000                 {
1001                     done = true;
1002                 }
1003                 i++;
1004             }
1005
1006             done = false;
1007             i = 0;
1008             while ( !done )
1009             {
1010                 String mailingListName = properties.getProperty( "mailingList." + i + ".name" );
1011                 if ( mailingListName != null )
1012                 {
1013                     MailingList mailingList = new MailingList();
1014                     mailingList.setName( mailingListName );
1015                     mailingList.setMainArchiveUrl( properties.getProperty( "mailingList." + i + ".archive" ) );
1016                     String p = properties.getProperty( "mailingList." + i + ".otherArchives" );
1017                     if ( p != null && p.length() > 0 )
1018                     {
1019                         mailingList.setOtherArchives( Arrays.asList( p.split( "," ) ) );
1020                     }
1021                     else
1022                     {
1023                         mailingList.setOtherArchives( Collections.<String>emptyList() );
1024                     }
1025                     mailingList.setPostAddress( properties.getProperty( "mailingList." + i + ".post" ) );
1026                     mailingList.setSubscribeAddress( properties.getProperty( "mailingList." + i + ".subscribe" ) );
1027                     mailingList.setUnsubscribeAddress( properties.getProperty( "mailingList." + i + ".unsubscribe" ) );
1028                     versionMetadata.addMailingList( mailingList );
1029                 }
1030                 else
1031                 {
1032                     done = true;
1033                 }
1034                 i++;
1035             }
1036
1037             done = false;
1038             i = 0;
1039             while ( !done )
1040             {
1041                 String dependencyArtifactId = properties.getProperty( "dependency." + i + ".artifactId" );
1042                 if ( dependencyArtifactId != null )
1043                 {
1044                     Dependency dependency = new Dependency();
1045                     dependency.setArtifactId( dependencyArtifactId );
1046                     dependency.setGroupId( properties.getProperty( "dependency." + i + ".groupId" ) );
1047                     dependency.setClassifier( properties.getProperty( "dependency." + i + ".classifier" ) );
1048                     dependency.setOptional(
1049                         Boolean.valueOf( properties.getProperty( "dependency." + i + ".optional" ) ) );
1050                     dependency.setScope( properties.getProperty( "dependency." + i + ".scope" ) );
1051                     dependency.setSystemPath( properties.getProperty( "dependency." + i + ".systemPath" ) );
1052                     dependency.setType( properties.getProperty( "dependency." + i + ".type" ) );
1053                     dependency.setVersion( properties.getProperty( "dependency." + i + ".version" ) );
1054                     dependency.setOptional(
1055                         Boolean.valueOf( properties.getProperty( "dependency." + i + ".optional" ) ) );
1056                     versionMetadata.addDependency( dependency );
1057                 }
1058                 else
1059                 {
1060                     done = true;
1061                 }
1062                 i++;
1063             }
1064
1065             String facetIds = properties.getProperty( "facetIds", "" );
1066             if ( facetIds.length() > 0 )
1067             {
1068                 for ( String facetId : facetIds.split( "," ) )
1069                 {
1070                     MetadataFacetFactory factory = metadataFacetFactories.get( facetId );
1071                     if ( factory == null )
1072                     {
1073                         log.error( "Attempted to load unknown project version metadata facet: {}", facetId );
1074                     }
1075                     else
1076                     {
1077                         MetadataFacet facet = factory.createMetadataFacet();
1078                         Map<String, String> map = new HashMap<>();
1079                         for ( Object key : new ArrayList( properties.keySet() ) )
1080                         {
1081                             String property = (String) key;
1082                             if ( property.startsWith( facet.getFacetId() ) )
1083                             {
1084                                 map.put( property.substring( facet.getFacetId().length() + 1 ),
1085                                          properties.getProperty( property ) );
1086                             }
1087                         }
1088                         facet.fromProperties( map );
1089                         versionMetadata.addFacet( facet );
1090                     }
1091                 }
1092             }
1093
1094             updateProjectVersionFacets( versionMetadata, properties );
1095         }
1096         return versionMetadata;
1097     }
1098
1099     @Override
1100     public Collection<String> getArtifactVersions( String repoId, String namespace, String projectId,
1101                                                    String projectVersion )
1102     {
1103         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion );
1104
1105         Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY );
1106
1107         Set<String> versions = new HashSet<String>();
1108         for ( Map.Entry entry : properties.entrySet() )
1109         {
1110             String name = (String) entry.getKey();
1111             if ( name.startsWith( "artifact:version:" ) )
1112             {
1113                 versions.add( (String) entry.getValue() );
1114             }
1115         }
1116         return versions;
1117     }
1118
1119     @Override
1120     public Collection<ProjectVersionReference> getProjectReferences( String repoId, String namespace, String projectId,
1121                                                                      String projectVersion )
1122     {
1123         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion );
1124
1125         Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY );
1126         int numberOfRefs = Integer.parseInt( properties.getProperty( "ref:lastReferenceNum", "-1" ) ) + 1;
1127
1128         List<ProjectVersionReference> references = new ArrayList<>();
1129         for ( int i = 0; i < numberOfRefs; i++ )
1130         {
1131             ProjectVersionReference reference = new ProjectVersionReference();
1132             reference.setProjectId( properties.getProperty( "ref:reference." + i + ".projectId" ) );
1133             reference.setNamespace( properties.getProperty( "ref:reference." + i + ".namespace" ) );
1134             reference.setProjectVersion( properties.getProperty( "ref:reference." + i + ".projectVersion" ) );
1135             reference.setReferenceType( ProjectVersionReference.ReferenceType.valueOf(
1136                 properties.getProperty( "ref:reference." + i + ".referenceType" ) ) );
1137             references.add( reference );
1138         }
1139         return references;
1140     }
1141
1142     @Override
1143     public Collection<String> getRootNamespaces( String repoId )
1144     {
1145         return getNamespaces( repoId, null );
1146     }
1147
1148     @Override
1149     public Collection<String> getNamespaces( String repoId, String baseNamespace )
1150     {
1151         List<String> allNamespaces = new ArrayList<>();
1152         File directory = getDirectory( repoId );
1153         File[] files = directory.listFiles();
1154         if ( files != null )
1155         {
1156             for ( File namespace : files )
1157             {
1158                 if ( new File( namespace, NAMESPACE_METADATA_KEY + ".properties" ).exists() )
1159                 {
1160                     allNamespaces.add( namespace.getName() );
1161                 }
1162             }
1163         }
1164
1165         Set<String> namespaces = new LinkedHashSet<>();
1166         int fromIndex = baseNamespace != null ? baseNamespace.length() + 1 : 0;
1167         for ( String namespace : allNamespaces )
1168         {
1169             if ( baseNamespace == null || namespace.startsWith( baseNamespace + "." ) )
1170             {
1171                 int i = namespace.indexOf( '.', fromIndex );
1172                 if ( i >= 0 )
1173                 {
1174                     namespaces.add( namespace.substring( fromIndex, i ) );
1175                 }
1176                 else
1177                 {
1178                     namespaces.add( namespace.substring( fromIndex ) );
1179                 }
1180             }
1181         }
1182         return new ArrayList<>( namespaces );
1183     }
1184
1185     @Override
1186     public Collection<String> getProjects( String repoId, String namespace )
1187     {
1188         List<String> projects = new ArrayList<>();
1189         File directory = new File( getDirectory( repoId ), namespace );
1190         File[] files = directory.listFiles();
1191         if ( files != null )
1192         {
1193             for ( File project : files )
1194             {
1195                 if ( new File( project, PROJECT_METADATA_KEY + ".properties" ).exists() )
1196                 {
1197                     projects.add( project.getName() );
1198                 }
1199             }
1200         }
1201         return projects;
1202     }
1203
1204     @Override
1205     public Collection<String> getProjectVersions( String repoId, String namespace, String projectId )
1206     {
1207         List<String> projectVersions = new ArrayList<>();
1208         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId );
1209         File[] files = directory.listFiles();
1210         if ( files != null )
1211         {
1212             for ( File projectVersion : files )
1213             {
1214                 if ( new File( projectVersion, PROJECT_VERSION_METADATA_KEY + ".properties" ).exists() )
1215                 {
1216                     projectVersions.add( projectVersion.getName() );
1217                 }
1218             }
1219         }
1220         return projectVersions;
1221     }
1222
1223     @Override
1224     public void removeProject( String repositoryId, String namespace, String projectId )
1225         throws MetadataRepositoryException
1226     {
1227         File directory = new File( getDirectory( repositoryId ), namespace + "/" + projectId );
1228         try
1229         {
1230             if ( directory.exists() )
1231             {
1232                 FileUtils.deleteDirectory( directory );
1233             }
1234         }
1235         catch ( IOException e )
1236         {
1237             throw new MetadataRepositoryException( e.getMessage(), e );
1238         }
1239     }
1240
1241     @Override
1242     public void removeProjectVersion( String repoId, String namespace, String projectId, String projectVersion )
1243         throws MetadataRepositoryException
1244     {
1245         File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion );
1246         if ( directory.exists() )
1247         {
1248             try
1249             {
1250                 FileUtils.deleteDirectory( directory );
1251             }
1252             catch ( IOException e )
1253             {
1254                 throw new MetadataRepositoryException( e.getMessage(), e );
1255             }
1256         }
1257     }
1258
1259     private void writeProperties( Properties properties, File directory, String propertiesKey )
1260         throws IOException
1261     {
1262         directory.mkdirs();
1263         try (OutputStream os = Files.newOutputStream( new File( directory, propertiesKey + ".properties" ).toPath() ))
1264         {
1265             properties.store( os, null );
1266         }
1267     }
1268
1269     private static class ArtifactComparator
1270         implements Comparator<ArtifactMetadata>
1271     {
1272         @Override
1273         public int compare( ArtifactMetadata artifact1, ArtifactMetadata artifact2 )
1274         {
1275             if ( artifact1.getWhenGathered() == artifact2.getWhenGathered() )
1276             {
1277                 return 0;
1278             }
1279             if ( artifact1.getWhenGathered() == null )
1280             {
1281                 return 1;
1282             }
1283             if ( artifact2.getWhenGathered() == null )
1284             {
1285                 return -1;
1286             }
1287             return artifact1.getWhenGathered().compareTo( artifact2.getWhenGathered() );
1288         }
1289     }
1290
1291     @Override
1292     public List<ArtifactMetadata> getArtifacts( String repoId )
1293     {
1294         List<ArtifactMetadata> artifacts = new ArrayList<>();
1295         for ( String ns : getRootNamespaces( repoId ) )
1296         {
1297             getArtifacts( artifacts, repoId, ns );
1298         }
1299         return artifacts;
1300     }
1301
1302     private void getArtifacts( List<ArtifactMetadata> artifacts, String repoId, String ns )
1303     {
1304         for ( String namespace : getNamespaces( repoId, ns ) )
1305         {
1306             getArtifacts( artifacts, repoId, ns + "." + namespace );
1307         }
1308
1309         for ( String project : getProjects( repoId, ns ) )
1310         {
1311             for ( String version : getProjectVersions( repoId, ns, project ) )
1312             {
1313                 for ( ArtifactMetadata artifact : getArtifacts( repoId, ns, project, version ) )
1314                 {
1315                     artifacts.add( artifact );
1316                 }
1317             }
1318         }
1319     }
1320
1321     @Override
1322     public List<ArtifactMetadata> searchArtifacts( String text, String repositoryId, boolean exact )
1323         throws MetadataRepositoryException
1324     {
1325         throw new UnsupportedOperationException( "searchArtifacts not yet implemented in File backend" );
1326     }
1327
1328     @Override
1329     public List<ArtifactMetadata> searchArtifacts( String key, String text, String repositoryId, boolean exact )
1330         throws MetadataRepositoryException
1331     {
1332         throw new UnsupportedOperationException( "searchArtifacts not yet implemented in File backend" );
1333     }
1334 }