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