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