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