]> source.dussan.org Git - archiva.git/blob
d3aabfc73a612cf7ea1dbd4916b9eaeb29b5b830
[archiva.git] /
1 package org.apache.archiva.metadata.repository.jcr;
2
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied.  See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21
22 import org.apache.archiva.metadata.model.ArtifactMetadata;
23 import org.apache.archiva.metadata.model.CiManagement;
24 import org.apache.archiva.metadata.model.Dependency;
25 import org.apache.archiva.metadata.model.IssueManagement;
26 import org.apache.archiva.metadata.model.License;
27 import org.apache.archiva.metadata.model.MailingList;
28 import org.apache.archiva.metadata.model.MetadataFacet;
29 import org.apache.archiva.metadata.model.MetadataFacetFactory;
30 import org.apache.archiva.metadata.model.Organization;
31 import org.apache.archiva.metadata.model.ProjectMetadata;
32 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
33 import org.apache.archiva.metadata.model.ProjectVersionReference;
34 import org.apache.archiva.metadata.model.Scm;
35 import org.apache.archiva.metadata.repository.MetadataRepository;
36 import org.apache.archiva.metadata.repository.MetadataResolutionException;
37 import org.apache.jackrabbit.commons.JcrUtils;
38 import org.apache.jackrabbit.core.TransientRepository;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import java.io.File;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Calendar;
46 import java.util.Collection;
47 import java.util.Collections;
48 import java.util.Date;
49 import java.util.HashMap;
50 import java.util.LinkedHashSet;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Set;
54 import javax.jcr.LoginException;
55 import javax.jcr.Node;
56 import javax.jcr.NodeIterator;
57 import javax.jcr.PathNotFoundException;
58 import javax.jcr.Property;
59 import javax.jcr.PropertyIterator;
60 import javax.jcr.Repository;
61 import javax.jcr.RepositoryException;
62 import javax.jcr.Session;
63 import javax.jcr.SimpleCredentials;
64 import javax.jcr.ValueFactory;
65 import javax.jcr.Workspace;
66 import javax.jcr.nodetype.NodeTypeManager;
67 import javax.jcr.nodetype.NodeTypeTemplate;
68 import javax.jcr.query.Query;
69 import javax.jcr.query.QueryResult;
70
71 /**
72  * @plexus.component role="org.apache.archiva.metadata.repository.MetadataRepository"
73  * @todo path construction should be centralised
74  * @todo review all methods for alternate implementations (use of queries)
75  * @todo below: exception handling
76  * @todo below: revise storage format for project version metadata
77  */
78 public class JcrMetadataRepository
79     implements MetadataRepository
80 {
81     /**
82      * @plexus.requirement role="org.apache.archiva.metadata.model.MetadataFacetFactory"
83      */
84     private Map<String, MetadataFacetFactory> metadataFacetFactories;
85
86     private static final Logger log = LoggerFactory.getLogger( JcrMetadataRepository.class );
87
88     private static Repository repository;
89
90     private Session session;
91
92     public JcrMetadataRepository()
93     {
94         // TODO: need to close this at the end - do we need to add it in the API?
95
96         try
97         {
98             // TODO: push this in from the test, and make it possible from the webapp
99             if ( repository == null )
100             {
101                 repository = new TransientRepository( new File( "src/test/repository.xml" ), new File( "target/jcr" ) );
102             }
103             // TODO: shouldn't do this in constructor since it's a singleton
104             session = repository.login( new SimpleCredentials( "username", "password".toCharArray() ) );
105
106             Workspace workspace = session.getWorkspace();
107             workspace.getNamespaceRegistry().registerNamespace( "archiva", "http://archiva.apache.org/jcr" );
108
109             NodeTypeManager nodeTypeManager = workspace.getNodeTypeManager();
110             NodeTypeTemplate nodeType = nodeTypeManager.createNodeTypeTemplate();
111             nodeType.setMixin( true );
112             nodeType.setName( "archiva:artifact" );
113             nodeTypeManager.registerNodeType( nodeType, false );
114         }
115         catch ( LoginException e )
116         {
117             // TODO
118             throw new RuntimeException( e );
119         }
120         catch ( RepositoryException e )
121         {
122             // TODO
123             throw new RuntimeException( e );
124         }
125     }
126
127     public void updateProject( String repositoryId, ProjectMetadata project )
128     {
129         updateProject( repositoryId, project.getNamespace(), project.getId() );
130     }
131
132     private void updateProject( String repositoryId, String namespace, String projectId )
133     {
134         updateNamespace( repositoryId, namespace );
135
136         try
137         {
138             getOrAddProjectNode( repositoryId, namespace, projectId );
139         }
140         catch ( RepositoryException e )
141         {
142             // TODO
143             throw new RuntimeException( e );
144         }
145     }
146
147     public void updateArtifact( String repositoryId, String namespace, String projectId, String projectVersion,
148                                 ArtifactMetadata artifactMeta )
149     {
150         updateNamespace( repositoryId, namespace );
151
152         try
153         {
154             Node node = getOrAddArtifactNode( repositoryId, namespace, projectId, projectVersion,
155                                               artifactMeta.getId() );
156
157             Calendar cal = Calendar.getInstance();
158             cal.setTime( artifactMeta.getFileLastModified() );
159             node.setProperty( "jcr:lastModified", cal );
160
161             cal = Calendar.getInstance();
162             cal.setTime( artifactMeta.getWhenGathered() );
163             node.setProperty( "whenGathered", cal );
164
165             node.setProperty( "size", artifactMeta.getSize() );
166             node.setProperty( "md5", artifactMeta.getMd5() );
167             node.setProperty( "sha1", artifactMeta.getSha1() );
168
169             node.setProperty( "version", artifactMeta.getVersion() );
170
171             // TODO: namespaced properties instead?
172             Node facetNode = JcrUtils.getOrAddNode( node, "facets" );
173             for ( MetadataFacet facet : artifactMeta.getFacetList() )
174             {
175                 // TODO: need to clear it?
176                 Node n = JcrUtils.getOrAddNode( facetNode, facet.getFacetId() );
177
178                 for ( Map.Entry<String, String> entry : facet.toProperties().entrySet() )
179                 {
180                     n.setProperty( entry.getKey(), entry.getValue() );
181                 }
182             }
183             session.save();
184         }
185         catch ( RepositoryException e )
186         {
187             // TODO
188             throw new RuntimeException( e );
189         }
190     }
191
192     public void updateProjectVersion( String repositoryId, String namespace, String projectId,
193                                       ProjectVersionMetadata versionMetadata )
194     {
195         updateProject( repositoryId, namespace, projectId );
196
197         try
198         {
199             Node versionNode = getOrAddProjectVersionNode( repositoryId, namespace, projectId,
200                                                            versionMetadata.getId() );
201
202             versionNode.setProperty( "name", versionMetadata.getName() );
203             versionNode.setProperty( "description", versionMetadata.getDescription() );
204             versionNode.setProperty( "url", versionMetadata.getUrl() );
205             versionNode.setProperty( "incomplete", versionMetadata.isIncomplete() );
206
207             // TODO: decide how to treat these in the content repo
208             if ( versionMetadata.getScm() != null )
209             {
210                 versionNode.setProperty( "scm.connection", versionMetadata.getScm().getConnection() );
211                 versionNode.setProperty( "scm.developerConnection", versionMetadata.getScm().getDeveloperConnection() );
212                 versionNode.setProperty( "scm.url", versionMetadata.getScm().getUrl() );
213             }
214             if ( versionMetadata.getCiManagement() != null )
215             {
216                 versionNode.setProperty( "ci.system", versionMetadata.getCiManagement().getSystem() );
217                 versionNode.setProperty( "ci.url", versionMetadata.getCiManagement().getUrl() );
218             }
219             if ( versionMetadata.getIssueManagement() != null )
220             {
221                 versionNode.setProperty( "issue.system", versionMetadata.getIssueManagement().getSystem() );
222                 versionNode.setProperty( "issue.url", versionMetadata.getIssueManagement().getUrl() );
223             }
224             if ( versionMetadata.getOrganization() != null )
225             {
226                 versionNode.setProperty( "org.name", versionMetadata.getOrganization().getName() );
227                 versionNode.setProperty( "org.url", versionMetadata.getOrganization().getUrl() );
228             }
229             int i = 0;
230             for ( License license : versionMetadata.getLicenses() )
231             {
232                 versionNode.setProperty( "license." + i + ".name", license.getName() );
233                 versionNode.setProperty( "license." + i + ".url", license.getUrl() );
234                 i++;
235             }
236             i = 0;
237             for ( MailingList mailingList : versionMetadata.getMailingLists() )
238             {
239                 versionNode.setProperty( "mailingList." + i + ".archive", mailingList.getMainArchiveUrl() );
240                 versionNode.setProperty( "mailingList." + i + ".name", mailingList.getName() );
241                 versionNode.setProperty( "mailingList." + i + ".post", mailingList.getPostAddress() );
242                 versionNode.setProperty( "mailingList." + i + ".unsubscribe", mailingList.getUnsubscribeAddress() );
243                 versionNode.setProperty( "mailingList." + i + ".subscribe", mailingList.getSubscribeAddress() );
244                 versionNode.setProperty( "mailingList." + i + ".otherArchives", join(
245                     mailingList.getOtherArchives() ) );
246                 i++;
247             }
248             i = 0;
249             for ( Dependency dependency : versionMetadata.getDependencies() )
250             {
251                 versionNode.setProperty( "dependency." + i + ".classifier", dependency.getClassifier() );
252                 versionNode.setProperty( "dependency." + i + ".scope", dependency.getScope() );
253                 versionNode.setProperty( "dependency." + i + ".systemPath", dependency.getSystemPath() );
254                 versionNode.setProperty( "dependency." + i + ".artifactId", dependency.getArtifactId() );
255                 versionNode.setProperty( "dependency." + i + ".groupId", dependency.getGroupId() );
256                 versionNode.setProperty( "dependency." + i + ".version", dependency.getVersion() );
257                 versionNode.setProperty( "dependency." + i + ".type", dependency.getType() );
258                 i++;
259             }
260
261             // TODO: namespaced properties instead?
262             Node facetNode = JcrUtils.getOrAddNode( versionNode, "facets" );
263             for ( MetadataFacet facet : versionMetadata.getFacetList() )
264             {
265                 // TODO: shouldn't need to recreate, just update
266                 if ( facetNode.hasNode( facet.getFacetId() ) )
267                 {
268                     facetNode.getNode( facet.getFacetId() ).remove();
269                 }
270                 Node n = facetNode.addNode( facet.getFacetId() );
271
272                 for ( Map.Entry<String, String> entry : facet.toProperties().entrySet() )
273                 {
274                     n.setProperty( entry.getKey(), entry.getValue() );
275                 }
276             }
277         }
278         catch ( RepositoryException e )
279         {
280             // TODO
281             throw new RuntimeException( e );
282         }
283     }
284
285     public void updateProjectReference( String repositoryId, String namespace, String projectId, String projectVersion,
286                                         ProjectVersionReference reference )
287     {
288         // TODO: try weak reference?
289         // TODO: is this tree the right way up? It differs from the content model
290         try
291         {
292             Node node = getOrAddRepositoryContentNode( repositoryId );
293             node = JcrUtils.getOrAddNode( node, namespace );
294             node = JcrUtils.getOrAddNode( node, projectId );
295             node = JcrUtils.getOrAddNode( node, projectVersion );
296             node = JcrUtils.getOrAddNode( node, "references" );
297             node = JcrUtils.getOrAddNode( node, reference.getNamespace() );
298             node = JcrUtils.getOrAddNode( node, reference.getProjectId() );
299             node = JcrUtils.getOrAddNode( node, reference.getProjectVersion() );
300             node.setProperty( "type", reference.getReferenceType().toString() );
301         }
302         catch ( RepositoryException e )
303         {
304             // TODO
305             throw new RuntimeException( e );
306         }
307     }
308
309     public void updateNamespace( String repositoryId, String namespace )
310     {
311         try
312         {
313             // TODO: currently flat
314             Node node = getOrAddNamespaceNode( repositoryId, namespace );
315             node.setProperty( "namespace", namespace );
316         }
317         catch ( RepositoryException e )
318         {
319             // TODO
320             throw new RuntimeException( e );
321         }
322     }
323
324     public List<String> getMetadataFacets( String repositoryId, String facetId )
325     {
326         List<String> facets = new ArrayList<String>();
327
328         try
329         {
330             // no need to construct node-by-node here, as we'll find in the next instance, the facet names have / and
331             // are paths themselves
332             Node node = session.getRootNode().getNode( getFacetPath( repositoryId, facetId ) );
333
334             // TODO: could we simply query all nodes with no children? Or perhaps a specific nodetype?
335             //   Might be better to review the purpose of this function - why is the list of paths helpful?
336             recurse( facets, "", node );
337         }
338         catch ( PathNotFoundException e )
339         {
340             // ignored - the facet doesn't exist, so return the empty list
341         }
342         catch ( RepositoryException e )
343         {
344             // TODO
345             throw new RuntimeException( e );
346         }
347         return facets;
348     }
349
350     private void recurse( List<String> facets, String prefix, Node node )
351         throws RepositoryException
352     {
353         for ( Node n : JcrUtils.getChildNodes( node ) )
354         {
355             String name = prefix + "/" + n.getName();
356             if ( n.hasNodes() )
357             {
358                 recurse( facets, name, n );
359             }
360             else
361             {
362                 // strip leading / first
363                 facets.add( name.substring( 1 ) );
364             }
365         }
366     }
367
368     public MetadataFacet getMetadataFacet( String repositoryId, String facetId, String name )
369     {
370         MetadataFacet metadataFacet = null;
371         try
372         {
373             Node root = session.getRootNode();
374             Node node = root.getNode( getFacetPath( repositoryId, facetId, name ) );
375
376             MetadataFacetFactory metadataFacetFactory = metadataFacetFactories.get( facetId );
377             if ( metadataFacetFactory != null )
378             {
379                 metadataFacet = metadataFacetFactory.createMetadataFacet( repositoryId, name );
380                 Map<String, String> map = new HashMap<String, String>();
381                 for ( Property property : JcrUtils.getProperties( node ) )
382                 {
383                     String p = property.getName();
384                     if ( !p.startsWith( "jcr:" ) )
385                     {
386                         map.put( p, property.getString() );
387                     }
388                 }
389                 metadataFacet.fromProperties( map );
390             }
391         }
392         catch ( PathNotFoundException e )
393         {
394             // ignored - the facet doesn't exist, so return null
395         }
396         catch ( RepositoryException e )
397         {
398             // TODO
399             throw new RuntimeException( e );
400         }
401         return metadataFacet;
402     }
403
404     public void addMetadataFacet( String repositoryId, MetadataFacet metadataFacet )
405     {
406         try
407         {
408             Node repo = getOrAddRepositoryNode( repositoryId );
409             Node facets = JcrUtils.getOrAddNode( repo, "facets" );
410
411             String id = metadataFacet.getFacetId();
412             Node facetNode = JcrUtils.getOrAddNode( facets, id );
413
414             Node node = getOrCreatePath( facetNode, metadataFacet.getName() );
415
416             for ( Map.Entry<String, String> entry : metadataFacet.toProperties().entrySet() )
417             {
418                 node.setProperty( entry.getKey(), entry.getValue() );
419             }
420         }
421         catch ( RepositoryException e )
422         {
423             // TODO
424             throw new RuntimeException( e );
425         }
426     }
427
428     private Node getOrCreatePath( Node baseNode, String name )
429         throws RepositoryException
430     {
431         Node node = baseNode;
432         for ( String n : name.split( "/" ) )
433         {
434             node = JcrUtils.getOrAddNode( node, n );
435         }
436         return node;
437     }
438
439     public void removeMetadataFacets( String repositoryId, String facetId )
440     {
441         try
442         {
443             Node root = session.getRootNode();
444             String path = getFacetPath( repositoryId, facetId );
445             // TODO: exception if missing?
446             if ( root.hasNode( path ) )
447             {
448                 root.getNode( path ).remove();
449             }
450         }
451         catch ( RepositoryException e )
452         {
453             // TODO
454             throw new RuntimeException( e );
455         }
456     }
457
458     public void removeMetadataFacet( String repositoryId, String facetId, String name )
459     {
460         try
461         {
462             Node root = session.getRootNode();
463             String path = getFacetPath( repositoryId, facetId, name );
464             // TODO: exception if missing?
465             if ( root.hasNode( path ) )
466             {
467                 Node node = root.getNode( path );
468                 do
469                 {
470                     Node parent = node.getParent();
471                     node.remove();
472                     node = parent;
473                 }
474                 while ( !node.hasNodes() );
475             }
476         }
477         catch ( RepositoryException e )
478         {
479             // TODO
480             throw new RuntimeException( e );
481         }
482     }
483
484     public List<ArtifactMetadata> getArtifactsByDateRange( String repoId, Date startTime, Date endTime )
485     {
486         List<ArtifactMetadata> artifacts;
487
488         String q = "SELECT * FROM [archiva:artifact]";
489
490         String clause = " WHERE";
491         if ( startTime != null )
492         {
493             q += clause + " [whenGathered] >= $start";
494             clause = " AND";
495         }
496         if ( endTime != null )
497         {
498             q += clause + " [whenGathered] <= $end";
499         }
500
501         try
502         {
503             Query query = session.getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
504             ValueFactory valueFactory = session.getValueFactory();
505             if ( startTime != null )
506             {
507                 query.bindValue( "start", valueFactory.createValue( createCalendar( startTime ) ) );
508             }
509             if ( endTime != null )
510             {
511                 query.bindValue( "end", valueFactory.createValue( createCalendar( endTime ) ) );
512             }
513             QueryResult result = query.execute();
514
515             artifacts = new ArrayList<ArtifactMetadata>();
516             for ( Node n : JcrUtils.getNodes( result ) )
517             {
518                 artifacts.add( getArtifactFromNode( repoId, n ) );
519             }
520         }
521         catch ( RepositoryException e )
522         {
523             // TODO
524             throw new RuntimeException( e );
525         }
526         return artifacts;
527     }
528
529     public Collection<String> getRepositories()
530     {
531         List<String> repositories;
532
533         try
534         {
535             Node root = session.getRootNode();
536             if ( root.hasNode( "repositories" ) )
537             {
538                 Node node = root.getNode( "repositories" );
539
540                 repositories = new ArrayList<String>();
541                 NodeIterator i = node.getNodes();
542                 while ( i.hasNext() )
543                 {
544                     Node n = i.nextNode();
545                     repositories.add( n.getName() );
546                 }
547             }
548             else
549             {
550                 repositories = Collections.emptyList();
551             }
552         }
553         catch ( RepositoryException e )
554         {
555             // TODO
556             throw new RuntimeException( e );
557         }
558         return repositories;
559     }
560
561     public List<ArtifactMetadata> getArtifactsByChecksum( String repositoryId, String checksum )
562     {
563         // TODO: this is quite slow - if we are to persist with this repository implementation we should build an index
564         //  of this information (eg. in Lucene, as before)
565         // alternatively, we could build a referential tree in the content repository, however it would need some levels
566         // of depth to avoid being too broad to be useful (eg. /repository/checksums/a/ab/abcdef1234567)
567
568         List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
569         for ( String ns : getRootNamespaces( repositoryId ) )
570         {
571             getArtifactsByChecksum( artifacts, repositoryId, ns, checksum );
572         }
573         return artifacts;
574     }
575
576     private void getArtifactsByChecksum( List<ArtifactMetadata> artifacts, String repositoryId, String ns,
577                                          String checksum )
578     {
579         for ( String namespace : getNamespaces( repositoryId, ns ) )
580         {
581             getArtifactsByChecksum( artifacts, repositoryId, ns + "." + namespace, checksum );
582         }
583
584         for ( String project : getProjects( repositoryId, ns ) )
585         {
586             for ( String version : getProjectVersions( repositoryId, ns, project ) )
587             {
588                 for ( ArtifactMetadata artifact : getArtifacts( repositoryId, ns, project, version ) )
589                 {
590                     if ( checksum.equals( artifact.getMd5() ) || checksum.equals( artifact.getSha1() ) )
591                     {
592                         artifacts.add( artifact );
593                     }
594                 }
595             }
596         }
597     }
598
599     public void deleteArtifact( String repositoryId, String namespace, String projectId, String projectVersion,
600                                 String id )
601     {
602         try
603         {
604             Node root = session.getRootNode();
605             String path =
606                 "repositories/" + repositoryId + "/content/" + namespace + "/" + projectId + "/" + projectVersion +
607                     "/" + id;
608             // TODO: exception if missing?
609             if ( root.hasNode( path ) )
610             {
611                 root.getNode( path ).remove();
612             }
613         }
614         catch ( RepositoryException e )
615         {
616             // TODO
617             throw new RuntimeException( e );
618         }
619     }
620
621     public void deleteRepository( String repositoryId )
622     {
623         try
624         {
625             Node root = session.getRootNode();
626             String path = "repositories/" + repositoryId;
627             // TODO: exception if missing?
628             if ( root.hasNode( path ) )
629             {
630                 root.getNode( path ).remove();
631             }
632         }
633         catch ( RepositoryException e )
634         {
635             // TODO
636             throw new RuntimeException( e );
637         }
638     }
639
640     public List<ArtifactMetadata> getArtifacts( String repositoryId )
641     {
642         // TODO: query faster?
643         List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
644         for ( String ns : getRootNamespaces( repositoryId ) )
645         {
646             getArtifacts( artifacts, repositoryId, ns );
647         }
648         return artifacts;
649     }
650
651     private void getArtifacts( List<ArtifactMetadata> artifacts, String repoId, String ns )
652     {
653         for ( String namespace : getNamespaces( repoId, ns ) )
654         {
655             getArtifacts( artifacts, repoId, ns + "." + namespace );
656         }
657
658         for ( String project : getProjects( repoId, ns ) )
659         {
660             for ( String version : getProjectVersions( repoId, ns, project ) )
661             {
662                 for ( ArtifactMetadata artifact : getArtifacts( repoId, ns, project, version ) )
663                 {
664                     artifacts.add( artifact );
665                 }
666             }
667         }
668     }
669
670     public ProjectMetadata getProject( String repositoryId, String namespace, String projectId )
671     {
672         ProjectMetadata metadata = null;
673
674         try
675         {
676             Node root = session.getRootNode();
677
678             // basically just checking it exists
679             String path = "repositories/" + repositoryId + "/content/" + namespace + "/" + projectId;
680             if ( root.hasNode( path ) )
681             {
682                 metadata = new ProjectMetadata();
683                 metadata.setId( projectId );
684                 metadata.setNamespace( namespace );
685             }
686         }
687         catch ( RepositoryException e )
688         {
689             // TODO
690             throw new RuntimeException( e );
691         }
692
693         return metadata;
694     }
695
696     public ProjectVersionMetadata getProjectVersion( String repositoryId, String namespace, String projectId,
697                                                      String projectVersion )
698         throws MetadataResolutionException
699     {
700         ProjectVersionMetadata versionMetadata;
701
702         try
703         {
704             Node root = session.getRootNode();
705
706             String path =
707                 "repositories/" + repositoryId + "/content/" + namespace + "/" + projectId + "/" + projectVersion;
708             if ( !root.hasNode( path ) )
709             {
710                 return null;
711             }
712
713             Node node = root.getNode( path );
714
715             versionMetadata = new ProjectVersionMetadata();
716             versionMetadata.setId( projectVersion );
717             versionMetadata.setName( getPropertyString( node, "name" ) );
718             versionMetadata.setDescription( getPropertyString( node, "description" ) );
719             versionMetadata.setUrl( getPropertyString( node, "url" ) );
720             versionMetadata.setIncomplete( node.hasProperty( "incomplete" ) && node.getProperty(
721                 "incomplete" ).getBoolean() );
722
723             // TODO: decide how to treat these in the content repo
724             String scmConnection = getPropertyString( node, "scm.connection" );
725             String scmDeveloperConnection = getPropertyString( node, "scm.developerConnection" );
726             String scmUrl = getPropertyString( node, "scm.url" );
727             if ( scmConnection != null || scmDeveloperConnection != null || scmUrl != null )
728             {
729                 Scm scm = new Scm();
730                 scm.setConnection( scmConnection );
731                 scm.setDeveloperConnection( scmDeveloperConnection );
732                 scm.setUrl( scmUrl );
733                 versionMetadata.setScm( scm );
734             }
735
736             String ciSystem = getPropertyString( node, "ci.system" );
737             String ciUrl = getPropertyString( node, "ci.url" );
738             if ( ciSystem != null || ciUrl != null )
739             {
740                 CiManagement ci = new CiManagement();
741                 ci.setSystem( ciSystem );
742                 ci.setUrl( ciUrl );
743                 versionMetadata.setCiManagement( ci );
744             }
745
746             String issueSystem = getPropertyString( node, "issue.system" );
747             String issueUrl = getPropertyString( node, "issue.url" );
748             if ( issueSystem != null || issueUrl != null )
749             {
750                 IssueManagement issueManagement = new IssueManagement();
751                 issueManagement.setSystem( issueSystem );
752                 issueManagement.setUrl( issueUrl );
753                 versionMetadata.setIssueManagement( issueManagement );
754             }
755
756             String orgName = getPropertyString( node, "org.name" );
757             String orgUrl = getPropertyString( node, "org.url" );
758             if ( orgName != null || orgUrl != null )
759             {
760                 Organization org = new Organization();
761                 org.setName( orgName );
762                 org.setUrl( orgUrl );
763                 versionMetadata.setOrganization( org );
764             }
765
766             boolean done = false;
767             int i = 0;
768             while ( !done )
769             {
770                 String licenseName = getPropertyString( node, "license." + i + ".name" );
771                 String licenseUrl = getPropertyString( node, "license." + i + ".url" );
772                 if ( licenseName != null || licenseUrl != null )
773                 {
774                     License license = new License();
775                     license.setName( licenseName );
776                     license.setUrl( licenseUrl );
777                     versionMetadata.addLicense( license );
778                 }
779                 else
780                 {
781                     done = true;
782                 }
783                 i++;
784             }
785
786             done = false;
787             i = 0;
788             while ( !done )
789             {
790                 String mailingListName = getPropertyString( node, "mailingList." + i + ".name" );
791                 if ( mailingListName != null )
792                 {
793                     MailingList mailingList = new MailingList();
794                     mailingList.setName( mailingListName );
795                     mailingList.setMainArchiveUrl( getPropertyString( node, "mailingList." + i + ".archive" ) );
796                     String n = "mailingList." + i + ".otherArchives";
797                     if ( node.hasProperty( n ) )
798                     {
799                         mailingList.setOtherArchives( Arrays.asList( getPropertyString( node, n ).split( "," ) ) );
800                     }
801                     else
802                     {
803                         mailingList.setOtherArchives( Collections.<String>emptyList() );
804                     }
805                     mailingList.setPostAddress( getPropertyString( node, "mailingList." + i + ".post" ) );
806                     mailingList.setSubscribeAddress( getPropertyString( node, "mailingList." + i + ".subscribe" ) );
807                     mailingList.setUnsubscribeAddress( getPropertyString( node, "mailingList." + i + ".unsubscribe" ) );
808                     versionMetadata.addMailingList( mailingList );
809                 }
810                 else
811                 {
812                     done = true;
813                 }
814                 i++;
815             }
816
817             done = false;
818             i = 0;
819             while ( !done )
820             {
821                 String dependencyArtifactId = getPropertyString( node, "dependency." + i + ".artifactId" );
822                 if ( dependencyArtifactId != null )
823                 {
824                     Dependency dependency = new Dependency();
825                     dependency.setArtifactId( dependencyArtifactId );
826                     dependency.setGroupId( getPropertyString( node, "dependency." + i + ".groupId" ) );
827                     dependency.setClassifier( getPropertyString( node, "dependency." + i + ".classifier" ) );
828                     dependency.setOptional( Boolean.valueOf( getPropertyString( node,
829                                                                                 "dependency." + i + ".optional" ) ) );
830                     dependency.setScope( getPropertyString( node, "dependency." + i + ".scope" ) );
831                     dependency.setSystemPath( getPropertyString( node, "dependency." + i + ".systemPath" ) );
832                     dependency.setType( getPropertyString( node, "dependency." + i + ".type" ) );
833                     dependency.setVersion( getPropertyString( node, "dependency." + i + ".version" ) );
834                     versionMetadata.addDependency( dependency );
835                 }
836                 else
837                 {
838                     done = true;
839                 }
840                 i++;
841             }
842
843             if ( node.hasNode( "facets" ) )
844             {
845                 NodeIterator j = node.getNode( "facets" ).getNodes();
846
847                 while ( j.hasNext() )
848                 {
849                     Node facetNode = j.nextNode();
850
851                     MetadataFacetFactory factory = metadataFacetFactories.get( facetNode.getName() );
852                     if ( factory == null )
853                     {
854                         log.error( "Attempted to load unknown project version metadata facet: " + facetNode.getName() );
855                     }
856                     else
857                     {
858                         MetadataFacet facet = factory.createMetadataFacet();
859                         Map<String, String> map = new HashMap<String, String>();
860                         PropertyIterator iterator = facetNode.getProperties();
861                         while ( iterator.hasNext() )
862                         {
863                             Property property = iterator.nextProperty();
864                             String p = property.getName();
865                             if ( !p.startsWith( "jcr:" ) )
866                             {
867                                 map.put( p, property.getString() );
868                             }
869                         }
870                         facet.fromProperties( map );
871                         versionMetadata.addFacet( facet );
872                     }
873                 }
874             }
875         }
876         catch ( RepositoryException e )
877         {
878             // TODO
879             throw new RuntimeException( e );
880         }
881
882         return versionMetadata;
883     }
884
885     private static String getPropertyString( Node node, String name )
886         throws RepositoryException
887     {
888         return node.hasProperty( name ) ? node.getProperty( name ).getString() : null;
889     }
890
891     public Collection<String> getArtifactVersions( String repositoryId, String namespace, String projectId,
892                                                    String projectVersion )
893     {
894         Set<String> versions = new LinkedHashSet<String>();
895
896         try
897         {
898             Node root = session.getRootNode();
899
900             Node node = root.getNode(
901                 "repositories/" + repositoryId + "/content/" + namespace + "/" + projectId + "/" + projectVersion );
902
903             NodeIterator iterator = node.getNodes();
904             while ( iterator.hasNext() )
905             {
906                 Node n = iterator.nextNode();
907
908                 versions.add( n.getProperty( "version" ).getString() );
909             }
910         }
911         catch ( PathNotFoundException e )
912         {
913             // ignore repo not found for now
914             // TODO: throw specific exception if repo doesn't exist
915         }
916         catch ( RepositoryException e )
917         {
918             // TODO
919             throw new RuntimeException( e );
920         }
921
922         return versions;
923     }
924
925     public Collection<ProjectVersionReference> getProjectReferences( String repositoryId, String namespace,
926                                                                      String projectId, String projectVersion )
927     {
928         List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
929
930         try
931         {
932             Node root = session.getRootNode();
933
934             String path =
935                 "repositories/" + repositoryId + "/content/" + namespace + "/" + projectId + "/" + projectVersion +
936                     "/references";
937             if ( root.hasNode( path ) )
938             {
939                 Node node = root.getNode( path );
940
941                 // TODO: use query by reference type
942                 NodeIterator i = node.getNodes();
943                 while ( i.hasNext() )
944                 {
945                     Node ns = i.nextNode();
946
947                     NodeIterator j = ns.getNodes();
948
949                     while ( j.hasNext() )
950                     {
951                         Node project = j.nextNode();
952
953                         NodeIterator k = project.getNodes();
954
955                         while ( k.hasNext() )
956                         {
957                             Node version = k.nextNode();
958
959                             ProjectVersionReference ref = new ProjectVersionReference();
960                             ref.setNamespace( ns.getName() );
961                             ref.setProjectId( project.getName() );
962                             ref.setProjectVersion( version.getName() );
963                             String type = version.getProperty( "type" ).getString();
964                             ref.setReferenceType( ProjectVersionReference.ReferenceType.valueOf( type ) );
965                             references.add( ref );
966                         }
967                     }
968                 }
969             }
970         }
971         catch ( RepositoryException e )
972         {
973             // TODO
974             throw new RuntimeException( e );
975         }
976
977         return references;
978     }
979
980     public Collection<String> getRootNamespaces( String repositoryId )
981     {
982         return getNamespaces( repositoryId, null );
983     }
984
985     private Collection<String> getNodeNames( String path )
986     {
987         List<String> names = new ArrayList<String>();
988
989         try
990         {
991             Node root = session.getRootNode();
992
993             Node repository = root.getNode( path );
994
995             NodeIterator nodes = repository.getNodes();
996             while ( nodes.hasNext() )
997             {
998                 Node node = nodes.nextNode();
999                 names.add( node.getName() );
1000             }
1001         }
1002         catch ( PathNotFoundException e )
1003         {
1004             // ignore repo not found for now
1005             // TODO: throw specific exception if repo doesn't exist
1006         }
1007         catch ( RepositoryException e )
1008         {
1009             // TODO
1010             throw new RuntimeException( e );
1011         }
1012
1013         return names;
1014     }
1015
1016     public Collection<String> getNamespaces( String repositoryId, String baseNamespace )
1017     {
1018         // TODO: could be simpler with pathed namespaces, rely on namespace property
1019         Collection<String> allNamespaces = getNodeNames( "repositories/" + repositoryId + "/content" );
1020
1021         Set<String> namespaces = new LinkedHashSet<String>();
1022         int fromIndex = baseNamespace != null ? baseNamespace.length() + 1 : 0;
1023         for ( String namespace : allNamespaces )
1024         {
1025             if ( baseNamespace == null || namespace.startsWith( baseNamespace + "." ) )
1026             {
1027                 int i = namespace.indexOf( '.', fromIndex );
1028                 if ( i >= 0 )
1029                 {
1030                     namespaces.add( namespace.substring( fromIndex, i ) );
1031                 }
1032                 else
1033                 {
1034                     namespaces.add( namespace.substring( fromIndex ) );
1035                 }
1036             }
1037         }
1038         return new ArrayList<String>( namespaces );
1039     }
1040
1041     public Collection<String> getProjects( String repositoryId, String namespace )
1042     {
1043         // TODO: could be simpler with pathed namespaces, rely on namespace property
1044         return getNodeNames( "repositories/" + repositoryId + "/content/" + namespace );
1045     }
1046
1047     public Collection<String> getProjectVersions( String repositoryId, String namespace, String projectId )
1048     {
1049         // TODO: could be simpler with pathed namespaces, rely on namespace property
1050         return getNodeNames( "repositories/" + repositoryId + "/content/" + namespace + "/" + projectId );
1051     }
1052
1053     public Collection<ArtifactMetadata> getArtifacts( String repositoryId, String namespace, String projectId,
1054                                                       String projectVersion )
1055     {
1056         List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
1057
1058         try
1059         {
1060             Node root = session.getRootNode();
1061             String path =
1062                 "repositories/" + repositoryId + "/content/" + namespace + "/" + projectId + "/" + projectVersion;
1063
1064             if ( root.hasNode( path ) )
1065             {
1066                 Node node = root.getNode( path );
1067
1068                 NodeIterator iterator = node.getNodes();
1069                 while ( iterator.hasNext() )
1070                 {
1071                     Node artifactNode = iterator.nextNode();
1072
1073                     ArtifactMetadata artifact = getArtifactFromNode( repositoryId, artifactNode );
1074                     artifacts.add( artifact );
1075                 }
1076             }
1077         }
1078         catch ( RepositoryException e )
1079         {
1080             // TODO
1081             throw new RuntimeException( e );
1082         }
1083
1084         return artifacts;
1085     }
1086
1087     private ArtifactMetadata getArtifactFromNode( String repositoryId, Node artifactNode )
1088         throws RepositoryException
1089     {
1090         String id = artifactNode.getName();
1091
1092         ArtifactMetadata artifact = new ArtifactMetadata();
1093         artifact.setId( id );
1094         artifact.setRepositoryId( repositoryId );
1095
1096         Node projectVersionNode = artifactNode.getParent();
1097         Node projectNode = projectVersionNode.getParent();
1098         Node namespaceNode = projectNode.getParent();
1099
1100         artifact.setNamespace( namespaceNode.getProperty( "namespace" ).getString() );
1101         artifact.setProject( projectNode.getName() );
1102         artifact.setProjectVersion( projectVersionNode.getName() );
1103         artifact.setVersion( artifactNode.hasProperty( "version" )
1104                                  ? artifactNode.getProperty( "version" ).getString()
1105                                  : projectVersionNode.getName() );
1106
1107         if ( artifactNode.hasProperty( "jcr:lastModified" ) )
1108         {
1109             artifact.setFileLastModified( artifactNode.getProperty( "jcr:lastModified" ).getDate().getTimeInMillis() );
1110         }
1111
1112         if ( artifactNode.hasProperty( "whenGathered" ) )
1113         {
1114             artifact.setWhenGathered( artifactNode.getProperty( "whenGathered" ).getDate().getTime() );
1115         }
1116
1117         if ( artifactNode.hasProperty( "size" ) )
1118         {
1119             artifact.setSize( artifactNode.getProperty( "size" ).getLong() );
1120         }
1121
1122         if ( artifactNode.hasProperty( "md5" ) )
1123         {
1124             artifact.setMd5( artifactNode.getProperty( "md5" ).getString() );
1125         }
1126
1127         if ( artifactNode.hasProperty( "sha1" ) )
1128         {
1129             artifact.setSha1( artifactNode.getProperty( "sha1" ).getString() );
1130         }
1131
1132         if ( artifactNode.hasNode( "facets" ) )
1133         {
1134             NodeIterator j = artifactNode.getNode( "facets" ).getNodes();
1135
1136             while ( j.hasNext() )
1137             {
1138                 Node facetNode = j.nextNode();
1139
1140                 MetadataFacetFactory factory = metadataFacetFactories.get( facetNode.getName() );
1141                 if ( factory == null )
1142                 {
1143                     log.error( "Attempted to load unknown project version metadata facet: " + facetNode.getName() );
1144                 }
1145                 else
1146                 {
1147                     MetadataFacet facet = factory.createMetadataFacet();
1148                     Map<String, String> map = new HashMap<String, String>();
1149                     PropertyIterator i = facetNode.getProperties();
1150                     while ( i.hasNext() )
1151                     {
1152                         Property p = i.nextProperty();
1153                         String property = p.getName();
1154                         map.put( property, p.getString() );
1155                     }
1156                     facet.fromProperties( map );
1157                     artifact.addFacet( facet );
1158                 }
1159             }
1160         }
1161         return artifact;
1162     }
1163
1164     void close()
1165     {
1166         try
1167         {
1168             // TODO: this shouldn't be here! Repository may need a context
1169             session.save();
1170         }
1171         catch ( RepositoryException e )
1172         {
1173             // TODO
1174             throw new RuntimeException( e );
1175         }
1176         session.logout();
1177     }
1178
1179     public void setMetadataFacetFactories( Map<String, MetadataFacetFactory> metadataFacetFactories )
1180     {
1181         this.metadataFacetFactories = metadataFacetFactories;
1182
1183         // TODO: check if actually called by normal injection
1184
1185 //        for ( String facetId : metadataFacetFactories.keySet() )
1186 //        {
1187 //            // TODO: second arg should be a better URL for the namespace
1188 //            session.getWorkspace().getNamespaceRegistry().registerNamespace( facetId, facetId );
1189 //        }
1190     }
1191
1192     private static String getFacetPath( String repositoryId, String facetId )
1193     {
1194         return "repositories/" + repositoryId + "/facets/" + facetId;
1195     }
1196
1197     private static String getFacetPath( String repositoryId, String facetId, String name )
1198     {
1199         return getFacetPath( repositoryId, facetId ) + "/" + name;
1200     }
1201
1202     private Node getOrAddRepositoryNode( String repositoryId )
1203         throws RepositoryException
1204     {
1205         Node root = session.getRootNode();
1206         Node node = JcrUtils.getOrAddNode( root, "repositories" );
1207         node = JcrUtils.getOrAddNode( node, repositoryId );
1208         return node;
1209     }
1210
1211     private Node getOrAddRepositoryContentNode( String repositoryId )
1212         throws RepositoryException
1213     {
1214         Node node = getOrAddRepositoryNode( repositoryId );
1215         return JcrUtils.getOrAddNode( node, "content" );
1216     }
1217
1218     private Node getOrAddNamespaceNode( String repositoryId, String namespace )
1219         throws RepositoryException
1220     {
1221         Node repo = getOrAddRepositoryContentNode( repositoryId );
1222         return JcrUtils.getOrAddNode( repo, namespace );
1223     }
1224
1225     private Node getOrAddProjectNode( String repositoryId, String namespace, String projectId )
1226         throws RepositoryException
1227     {
1228         Node namespaceNode = getOrAddNamespaceNode( repositoryId, namespace );
1229         return JcrUtils.getOrAddNode( namespaceNode, projectId );
1230     }
1231
1232     private Node getOrAddProjectVersionNode( String repositoryId, String namespace, String projectId,
1233                                              String projectVersion )
1234         throws RepositoryException
1235     {
1236         Node projectNode = getOrAddProjectNode( repositoryId, namespace, projectId );
1237         return JcrUtils.getOrAddNode( projectNode, projectVersion );
1238     }
1239
1240     private Node getOrAddArtifactNode( String repositoryId, String namespace, String projectId, String projectVersion,
1241                                        String id )
1242         throws RepositoryException
1243     {
1244         Node versionNode = getOrAddProjectVersionNode( repositoryId, namespace, projectId, projectVersion );
1245         Node node = JcrUtils.getOrAddNode( versionNode, id );
1246         node.addMixin( "archiva:artifact" );
1247         return node;
1248     }
1249
1250     private static Calendar createCalendar( Date time )
1251     {
1252         Calendar cal = Calendar.getInstance();
1253         cal.setTime( time );
1254         return cal;
1255     }
1256
1257     private String join( Collection<String> ids )
1258     {
1259         if ( ids != null && !ids.isEmpty() )
1260         {
1261             StringBuilder s = new StringBuilder();
1262             for ( String id : ids )
1263             {
1264                 s.append( id );
1265                 s.append( "," );
1266             }
1267             return s.substring( 0, s.length() - 1 );
1268         }
1269         return null;
1270     }
1271
1272     public Session getJcrSession()
1273     {
1274         return session;
1275     }
1276 }