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