]> source.dussan.org Git - archiva.git/blob
013bfbab951977f6ecac411df5e24a297f7d03c4
[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.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Calendar;
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.Date;
48 import java.util.HashMap;
49 import java.util.LinkedHashSet;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Set;
53 import javax.jcr.LoginException;
54 import javax.jcr.Node;
55 import javax.jcr.NodeIterator;
56 import javax.jcr.PathNotFoundException;
57 import javax.jcr.Property;
58 import javax.jcr.Repository;
59 import javax.jcr.RepositoryException;
60 import javax.jcr.Session;
61 import javax.jcr.SimpleCredentials;
62 import javax.jcr.ValueFactory;
63 import javax.jcr.Workspace;
64 import javax.jcr.nodetype.NodeTypeManager;
65 import javax.jcr.nodetype.NodeTypeTemplate;
66 import javax.jcr.query.Query;
67 import javax.jcr.query.QueryResult;
68
69 /**
70  * @plexus.component role="org.apache.archiva.metadata.repository.MetadataRepository"
71  * @todo below: revise storage format for project version metadata
72  * @todo revise reference storage
73  */
74 public class JcrMetadataRepository
75     implements MetadataRepository
76 {
77     private static final String JCR_LAST_MODIFIED = "jcr:lastModified";
78
79     private static final String ARTIFACT_NODE_TYPE = "archiva:artifact";
80
81     private static final String FACET_NODE_TYPE = "archiva:facet";
82
83     private static final String QUERY_ARTIFACTS = "SELECT * FROM [" + ARTIFACT_NODE_TYPE + "]";
84
85     /**
86      * @plexus.requirement role="org.apache.archiva.metadata.model.MetadataFacetFactory"
87      */
88     private Map<String, MetadataFacetFactory> metadataFacetFactories;
89
90     private static final Logger log = LoggerFactory.getLogger( JcrMetadataRepository.class );
91
92     /**
93      * @plexus.requirement
94      */
95     private Repository repository;
96
97     private Session session;
98
99     public JcrMetadataRepository()
100     {
101     }
102
103     public void login()
104     {
105         // TODO: need to close this at the end - do we need to add it in the API?
106
107         try
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             Workspace workspace = session.getWorkspace();
113             workspace.getNamespaceRegistry().registerNamespace( "archiva", "http://archiva.apache.org/jcr/" );
114
115             NodeTypeManager nodeTypeManager = workspace.getNodeTypeManager();
116             registerMixinNodeType( nodeTypeManager, ARTIFACT_NODE_TYPE );
117             registerMixinNodeType( nodeTypeManager, FACET_NODE_TYPE );
118         }
119         catch ( LoginException e )
120         {
121             // TODO
122             throw new RuntimeException( e );
123         }
124         catch ( RepositoryException e )
125         {
126             // TODO
127             throw new RuntimeException( e );
128         }
129     }
130
131     private static void registerMixinNodeType( NodeTypeManager nodeTypeManager, String name )
132         throws RepositoryException
133     {
134         NodeTypeTemplate nodeType = nodeTypeManager.createNodeTypeTemplate();
135         nodeType.setMixin( true );
136         nodeType.setName( name );
137         nodeTypeManager.registerNodeType( nodeType, false );
138     }
139
140     public void updateProject( String repositoryId, ProjectMetadata project )
141         throws MetadataRepositoryException
142     {
143         updateProject( repositoryId, project.getNamespace(), project.getId() );
144     }
145
146     private void updateProject( String repositoryId, String namespace, String projectId )
147         throws MetadataRepositoryException
148     {
149         updateNamespace( repositoryId, namespace );
150
151         try
152         {
153             getOrAddProjectNode( repositoryId, namespace, projectId );
154         }
155         catch ( RepositoryException e )
156         {
157             throw new MetadataRepositoryException( e.getMessage(), e );
158         }
159     }
160
161     public void updateArtifact( String repositoryId, String namespace, String projectId, String projectVersion,
162                                 ArtifactMetadata artifactMeta )
163         throws MetadataRepositoryException
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             throw new MetadataRepositoryException( e.getMessage(), e );
208         }
209     }
210
211     public void updateProjectVersion( String repositoryId, String namespace, String projectId,
212                                       ProjectVersionMetadata versionMetadata )
213         throws MetadataRepositoryException
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             throw new MetadataRepositoryException( e.getMessage(), e );
300         }
301     }
302
303     public void updateProjectReference( String repositoryId, String namespace, String projectId, String projectVersion,
304                                         ProjectVersionReference reference )
305         throws MetadataRepositoryException
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             throw new MetadataRepositoryException( e.getMessage(), e );
323         }
324     }
325
326     public void updateNamespace( String repositoryId, String namespace )
327         throws MetadataRepositoryException
328     {
329         try
330         {
331             Node node = getOrAddNamespaceNode( repositoryId, namespace );
332             node.setProperty( "namespace", namespace );
333         }
334         catch ( RepositoryException e )
335         {
336             throw new MetadataRepositoryException( e.getMessage(), e );
337         }
338     }
339
340     public List<String> getMetadataFacets( String repositoryId, String facetId )
341         throws MetadataRepositoryException
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             throw new MetadataRepositoryException( e.getMessage(), e );
362         }
363         return facets;
364     }
365
366     private void recurse( List<String> facets, String prefix, Node node )
367         throws RepositoryException
368     {
369         for ( Node n : JcrUtils.getChildNodes( node ) )
370         {
371             String name = prefix + "/" + n.getName();
372             if ( n.hasNodes() )
373             {
374                 recurse( facets, name, n );
375             }
376             else
377             {
378                 // strip leading / first
379                 facets.add( name.substring( 1 ) );
380             }
381         }
382     }
383
384     public MetadataFacet getMetadataFacet( String repositoryId, String facetId, String name )
385         throws MetadataRepositoryException
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             throw new MetadataRepositoryException( e.getMessage(), e );
416         }
417         return metadataFacet;
418     }
419
420     public void addMetadataFacet( String repositoryId, MetadataFacet metadataFacet )
421         throws MetadataRepositoryException
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             throw new MetadataRepositoryException( e.getMessage(), e );
441         }
442     }
443
444     public void removeMetadataFacets( String repositoryId, String facetId )
445         throws MetadataRepositoryException
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             throw new MetadataRepositoryException( e.getMessage(), e );
459         }
460     }
461
462     public void removeMetadataFacet( String repositoryId, String facetId, String name )
463         throws MetadataRepositoryException
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             throw new MetadataRepositoryException( e.getMessage(), e );
485         }
486     }
487
488     public List<ArtifactMetadata> getArtifactsByDateRange( String repoId, Date startTime, Date endTime )
489         throws MetadataRepositoryException
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             throw new MetadataRepositoryException( e.getMessage(), e );
529         }
530         return artifacts;
531     }
532
533     public Collection<String> getRepositories()
534         throws MetadataRepositoryException
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             throw new MetadataRepositoryException( e.getMessage(), e );
561         }
562         return repositories;
563     }
564
565     public List<ArtifactMetadata> getArtifactsByChecksum( String repositoryId, String checksum )
566         throws MetadataRepositoryException
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             throw new MetadataRepositoryException( e.getMessage(), e );
588         }
589         return artifacts;
590     }
591
592     public void removeArtifact( String repositoryId, String namespace, String projectId, String projectVersion,
593                                 String id )
594         throws MetadataRepositoryException
595     {
596         try
597         {
598             Node root = session.getRootNode();
599             String path = getArtifactPath( repositoryId, namespace, projectId, projectVersion, id );
600             if ( root.hasNode( path ) )
601             {
602                 root.getNode( path ).remove();
603             }
604         }
605         catch ( RepositoryException e )
606         {
607             throw new MetadataRepositoryException( e.getMessage(), e );
608         }
609     }
610
611     public void removeRepository( String repositoryId )
612         throws MetadataRepositoryException
613     {
614         try
615         {
616             Node root = session.getRootNode();
617             String path = getRepositoryPath( repositoryId );
618             if ( root.hasNode( path ) )
619             {
620                 root.getNode( path ).remove();
621             }
622         }
623         catch ( RepositoryException e )
624         {
625             throw new MetadataRepositoryException( e.getMessage(), e );
626         }
627     }
628
629     public List<ArtifactMetadata> getArtifacts( String repositoryId )
630         throws MetadataRepositoryException
631     {
632         List<ArtifactMetadata> artifacts;
633
634         String q = QUERY_ARTIFACTS;
635
636         try
637         {
638             Query query = session.getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
639             QueryResult result = query.execute();
640
641             artifacts = new ArrayList<ArtifactMetadata>();
642             for ( Node n : JcrUtils.getNodes( result ) )
643             {
644                 artifacts.add( getArtifactFromNode( repositoryId, n ) );
645             }
646         }
647         catch ( RepositoryException e )
648         {
649             throw new MetadataRepositoryException( e.getMessage(), e );
650         }
651         return artifacts;
652     }
653
654     public ProjectMetadata getProject( String repositoryId, String namespace, String projectId )
655         throws MetadataResolutionException
656     {
657         ProjectMetadata metadata = null;
658
659         try
660         {
661             Node root = session.getRootNode();
662
663             // basically just checking it exists
664             String path = getProjectPath( repositoryId, namespace, projectId );
665             if ( root.hasNode( path ) )
666             {
667                 metadata = new ProjectMetadata();
668                 metadata.setId( projectId );
669                 metadata.setNamespace( namespace );
670             }
671         }
672         catch ( RepositoryException e )
673         {
674             throw new MetadataResolutionException( e.getMessage(), e );
675         }
676
677         return metadata;
678     }
679
680     public ProjectVersionMetadata getProjectVersion( String repositoryId, String namespace, String projectId,
681                                                      String projectVersion )
682         throws MetadataResolutionException
683     {
684         ProjectVersionMetadata versionMetadata;
685
686         try
687         {
688             Node root = session.getRootNode();
689
690             String path = getProjectVersionPath( repositoryId, namespace, projectId, projectVersion );
691             if ( !root.hasNode( path ) )
692             {
693                 return null;
694             }
695
696             Node node = root.getNode( path );
697
698             versionMetadata = new ProjectVersionMetadata();
699             versionMetadata.setId( projectVersion );
700             versionMetadata.setName( getPropertyString( node, "name" ) );
701             versionMetadata.setDescription( getPropertyString( node, "description" ) );
702             versionMetadata.setUrl( getPropertyString( node, "url" ) );
703             versionMetadata.setIncomplete( node.hasProperty( "incomplete" ) && node.getProperty(
704                 "incomplete" ).getBoolean() );
705
706             // TODO: decide how to treat these in the content repo
707             String scmConnection = getPropertyString( node, "scm.connection" );
708             String scmDeveloperConnection = getPropertyString( node, "scm.developerConnection" );
709             String scmUrl = getPropertyString( node, "scm.url" );
710             if ( scmConnection != null || scmDeveloperConnection != null || scmUrl != null )
711             {
712                 Scm scm = new Scm();
713                 scm.setConnection( scmConnection );
714                 scm.setDeveloperConnection( scmDeveloperConnection );
715                 scm.setUrl( scmUrl );
716                 versionMetadata.setScm( scm );
717             }
718
719             String ciSystem = getPropertyString( node, "ci.system" );
720             String ciUrl = getPropertyString( node, "ci.url" );
721             if ( ciSystem != null || ciUrl != null )
722             {
723                 CiManagement ci = new CiManagement();
724                 ci.setSystem( ciSystem );
725                 ci.setUrl( ciUrl );
726                 versionMetadata.setCiManagement( ci );
727             }
728
729             String issueSystem = getPropertyString( node, "issue.system" );
730             String issueUrl = getPropertyString( node, "issue.url" );
731             if ( issueSystem != null || issueUrl != null )
732             {
733                 IssueManagement issueManagement = new IssueManagement();
734                 issueManagement.setSystem( issueSystem );
735                 issueManagement.setUrl( issueUrl );
736                 versionMetadata.setIssueManagement( issueManagement );
737             }
738
739             String orgName = getPropertyString( node, "org.name" );
740             String orgUrl = getPropertyString( node, "org.url" );
741             if ( orgName != null || orgUrl != null )
742             {
743                 Organization org = new Organization();
744                 org.setName( orgName );
745                 org.setUrl( orgUrl );
746                 versionMetadata.setOrganization( org );
747             }
748
749             boolean done = false;
750             int i = 0;
751             while ( !done )
752             {
753                 String licenseName = getPropertyString( node, "license." + i + ".name" );
754                 String licenseUrl = getPropertyString( node, "license." + i + ".url" );
755                 if ( licenseName != null || licenseUrl != null )
756                 {
757                     License license = new License();
758                     license.setName( licenseName );
759                     license.setUrl( licenseUrl );
760                     versionMetadata.addLicense( license );
761                 }
762                 else
763                 {
764                     done = true;
765                 }
766                 i++;
767             }
768
769             done = false;
770             i = 0;
771             while ( !done )
772             {
773                 String mailingListName = getPropertyString( node, "mailingList." + i + ".name" );
774                 if ( mailingListName != null )
775                 {
776                     MailingList mailingList = new MailingList();
777                     mailingList.setName( mailingListName );
778                     mailingList.setMainArchiveUrl( getPropertyString( node, "mailingList." + i + ".archive" ) );
779                     String n = "mailingList." + i + ".otherArchives";
780                     if ( node.hasProperty( n ) )
781                     {
782                         mailingList.setOtherArchives( Arrays.asList( getPropertyString( node, n ).split( "," ) ) );
783                     }
784                     else
785                     {
786                         mailingList.setOtherArchives( Collections.<String>emptyList() );
787                     }
788                     mailingList.setPostAddress( getPropertyString( node, "mailingList." + i + ".post" ) );
789                     mailingList.setSubscribeAddress( getPropertyString( node, "mailingList." + i + ".subscribe" ) );
790                     mailingList.setUnsubscribeAddress( getPropertyString( node, "mailingList." + i + ".unsubscribe" ) );
791                     versionMetadata.addMailingList( mailingList );
792                 }
793                 else
794                 {
795                     done = true;
796                 }
797                 i++;
798             }
799
800             done = false;
801             i = 0;
802             while ( !done )
803             {
804                 String dependencyArtifactId = getPropertyString( node, "dependency." + i + ".artifactId" );
805                 if ( dependencyArtifactId != null )
806                 {
807                     Dependency dependency = new Dependency();
808                     dependency.setArtifactId( dependencyArtifactId );
809                     dependency.setGroupId( getPropertyString( node, "dependency." + i + ".groupId" ) );
810                     dependency.setClassifier( getPropertyString( node, "dependency." + i + ".classifier" ) );
811                     dependency.setOptional( Boolean.valueOf( getPropertyString( node,
812                                                                                 "dependency." + i + ".optional" ) ) );
813                     dependency.setScope( getPropertyString( node, "dependency." + i + ".scope" ) );
814                     dependency.setSystemPath( getPropertyString( node, "dependency." + i + ".systemPath" ) );
815                     dependency.setType( getPropertyString( node, "dependency." + i + ".type" ) );
816                     dependency.setVersion( getPropertyString( node, "dependency." + i + ".version" ) );
817                     versionMetadata.addDependency( dependency );
818                 }
819                 else
820                 {
821                     done = true;
822                 }
823                 i++;
824             }
825
826             for ( Node n : JcrUtils.getChildNodes( node ) )
827             {
828                 if ( n.isNodeType( FACET_NODE_TYPE ) )
829                 {
830                     String name = n.getName();
831                     MetadataFacetFactory factory = metadataFacetFactories.get( name );
832                     if ( factory == null )
833                     {
834                         log.error( "Attempted to load unknown project version metadata facet: " + name );
835                     }
836                     else
837                     {
838                         MetadataFacet facet = factory.createMetadataFacet();
839                         Map<String, String> map = new HashMap<String, String>();
840                         for ( Property property : JcrUtils.getProperties( n ) )
841                         {
842                             String p = property.getName();
843                             if ( !p.startsWith( "jcr:" ) )
844                             {
845                                 map.put( p, property.getString() );
846                             }
847                         }
848                         facet.fromProperties( map );
849                         versionMetadata.addFacet( facet );
850                     }
851                 }
852             }
853         }
854         catch ( RepositoryException e )
855         {
856             throw new MetadataResolutionException( e.getMessage(), e );
857         }
858
859         return versionMetadata;
860     }
861
862     public Collection<String> getArtifactVersions( String repositoryId, String namespace, String projectId,
863                                                    String projectVersion )
864         throws MetadataResolutionException
865     {
866         Set<String> versions = new LinkedHashSet<String>();
867
868         try
869         {
870             Node root = session.getRootNode();
871
872             Node node = root.getNode( getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ) );
873
874             for ( Node n : JcrUtils.getChildNodes( node ) )
875             {
876                 versions.add( n.getProperty( "version" ).getString() );
877             }
878         }
879         catch ( PathNotFoundException e )
880         {
881             // ignore repo not found for now
882         }
883         catch ( RepositoryException e )
884         {
885             throw new MetadataResolutionException( e.getMessage(), e );
886         }
887
888         return versions;
889     }
890
891     public Collection<ProjectVersionReference> getProjectReferences( String repositoryId, String namespace,
892                                                                      String projectId, String projectVersion )
893         throws MetadataResolutionException
894     {
895         List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
896
897         try
898         {
899             Node root = session.getRootNode();
900
901             String path = getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ) + "/references";
902             if ( root.hasNode( path ) )
903             {
904                 Node node = root.getNode( path );
905
906                 NodeIterator i = node.getNodes();
907                 while ( i.hasNext() )
908                 {
909                     Node ns = i.nextNode();
910
911                     NodeIterator j = ns.getNodes();
912
913                     while ( j.hasNext() )
914                     {
915                         Node project = j.nextNode();
916
917                         NodeIterator k = project.getNodes();
918
919                         while ( k.hasNext() )
920                         {
921                             Node version = k.nextNode();
922
923                             ProjectVersionReference ref = new ProjectVersionReference();
924                             ref.setNamespace( ns.getName() );
925                             ref.setProjectId( project.getName() );
926                             ref.setProjectVersion( version.getName() );
927                             String type = version.getProperty( "type" ).getString();
928                             ref.setReferenceType( ProjectVersionReference.ReferenceType.valueOf( type ) );
929                             references.add( ref );
930                         }
931                     }
932                 }
933             }
934         }
935         catch ( RepositoryException e )
936         {
937             throw new MetadataResolutionException( e.getMessage(), e );
938         }
939
940         return references;
941     }
942
943     public Collection<String> getRootNamespaces( String repositoryId )
944         throws MetadataResolutionException
945     {
946         return getNamespaces( repositoryId, null );
947     }
948
949     public Collection<String> getNamespaces( String repositoryId, String baseNamespace )
950         throws MetadataResolutionException
951     {
952         String path = baseNamespace != null
953             ? getNamespacePath( repositoryId, baseNamespace )
954             : getRepositoryContentPath( repositoryId );
955
956         return getNodeNames( path );
957     }
958
959     public Collection<String> getProjects( String repositoryId, String namespace )
960         throws MetadataResolutionException
961     {
962         return getNodeNames( getNamespacePath( repositoryId, namespace ) );
963     }
964
965     public Collection<String> getProjectVersions( String repositoryId, String namespace, String projectId )
966         throws MetadataResolutionException
967     {
968         return getNodeNames( getProjectPath( repositoryId, namespace, projectId ) );
969     }
970
971     public Collection<ArtifactMetadata> getArtifacts( String repositoryId, String namespace, String projectId,
972                                                       String projectVersion )
973         throws MetadataResolutionException
974     {
975         List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
976
977         try
978         {
979             Node root = session.getRootNode();
980             String path = getProjectVersionPath( repositoryId, namespace, projectId, projectVersion );
981
982             if ( root.hasNode( path ) )
983             {
984                 Node node = root.getNode( path );
985
986                 for ( Node n : JcrUtils.getChildNodes( node ) )
987                 {
988                     artifacts.add( getArtifactFromNode( repositoryId, n ) );
989                 }
990             }
991         }
992         catch ( RepositoryException e )
993         {
994             throw new MetadataResolutionException( e.getMessage(), e );
995         }
996
997         return artifacts;
998     }
999
1000     void close()
1001     {
1002         try
1003         {
1004             // TODO: this shouldn't be here! Repository may need a context
1005             session.save();
1006         }
1007         catch ( RepositoryException e )
1008         {
1009             // TODO
1010             throw new RuntimeException( e );
1011         }
1012         session.logout();
1013     }
1014
1015     public void setMetadataFacetFactories( Map<String, MetadataFacetFactory> metadataFacetFactories )
1016     {
1017         this.metadataFacetFactories = metadataFacetFactories;
1018
1019         // TODO: check if actually called by normal injection
1020
1021         // TODO: consider using namespaces for facets instead of the current approach:
1022 //        for ( String facetId : metadataFacetFactories.keySet() )
1023 //        {
1024 //            session.getWorkspace().getNamespaceRegistry().registerNamespace( facetId, facetId );
1025 //        }
1026     }
1027
1028     private ArtifactMetadata getArtifactFromNode( String repositoryId, Node artifactNode )
1029         throws RepositoryException
1030     {
1031         String id = artifactNode.getName();
1032
1033         ArtifactMetadata artifact = new ArtifactMetadata();
1034         artifact.setId( id );
1035         artifact.setRepositoryId( repositoryId );
1036
1037         Node projectVersionNode = artifactNode.getParent();
1038         Node projectNode = projectVersionNode.getParent();
1039         Node namespaceNode = projectNode.getParent();
1040
1041         artifact.setNamespace( namespaceNode.getProperty( "namespace" ).getString() );
1042         artifact.setProject( projectNode.getName() );
1043         artifact.setProjectVersion( projectVersionNode.getName() );
1044         artifact.setVersion( artifactNode.hasProperty( "version" )
1045                                  ? artifactNode.getProperty( "version" ).getString()
1046                                  : projectVersionNode.getName() );
1047
1048         if ( artifactNode.hasProperty( JCR_LAST_MODIFIED ) )
1049         {
1050             artifact.setFileLastModified( artifactNode.getProperty( JCR_LAST_MODIFIED ).getDate().getTimeInMillis() );
1051         }
1052
1053         if ( artifactNode.hasProperty( "whenGathered" ) )
1054         {
1055             artifact.setWhenGathered( artifactNode.getProperty( "whenGathered" ).getDate().getTime() );
1056         }
1057
1058         if ( artifactNode.hasProperty( "size" ) )
1059         {
1060             artifact.setSize( artifactNode.getProperty( "size" ).getLong() );
1061         }
1062
1063         if ( artifactNode.hasProperty( "md5" ) )
1064         {
1065             artifact.setMd5( artifactNode.getProperty( "md5" ).getString() );
1066         }
1067
1068         if ( artifactNode.hasProperty( "sha1" ) )
1069         {
1070             artifact.setSha1( artifactNode.getProperty( "sha1" ).getString() );
1071         }
1072
1073         for ( Node n : JcrUtils.getChildNodes( artifactNode ) )
1074         {
1075             if ( n.isNodeType( FACET_NODE_TYPE ) )
1076             {
1077                 String name = n.getName();
1078                 MetadataFacetFactory factory = metadataFacetFactories.get( name );
1079                 if ( factory == null )
1080                 {
1081                     log.error( "Attempted to load unknown project version metadata facet: " + name );
1082                 }
1083                 else
1084                 {
1085                     MetadataFacet facet = factory.createMetadataFacet();
1086                     Map<String, String> map = new HashMap<String, String>();
1087                     for ( Property p : JcrUtils.getProperties( n ) )
1088                     {
1089                         String property = p.getName();
1090                         if ( !property.startsWith( "jcr:" ) )
1091                         {
1092                             map.put( property, p.getString() );
1093                         }
1094                     }
1095                     facet.fromProperties( map );
1096                     artifact.addFacet( facet );
1097                 }
1098             }
1099         }
1100         return artifact;
1101     }
1102
1103     private static String getPropertyString( Node node, String name )
1104         throws RepositoryException
1105     {
1106         return node.hasProperty( name ) ? node.getProperty( name ).getString() : null;
1107     }
1108
1109     private Collection<String> getNodeNames( String path )
1110         throws MetadataResolutionException
1111     {
1112         List<String> names = new ArrayList<String>();
1113
1114         try
1115         {
1116             Node root = session.getRootNode();
1117
1118             Node repository = root.getNode( path );
1119
1120             NodeIterator nodes = repository.getNodes();
1121             while ( nodes.hasNext() )
1122             {
1123                 Node node = nodes.nextNode();
1124                 names.add( node.getName() );
1125             }
1126         }
1127         catch ( PathNotFoundException e )
1128         {
1129             // ignore repo not found for now
1130         }
1131         catch ( RepositoryException e )
1132         {
1133             throw new MetadataResolutionException( e.getMessage(), e );
1134         }
1135
1136         return names;
1137     }
1138
1139     private static String getRepositoryPath( String repositoryId )
1140     {
1141         return "repositories/" + repositoryId;
1142     }
1143
1144     private static String getRepositoryContentPath( String repositoryId )
1145     {
1146         return getRepositoryPath( repositoryId ) + "/content/";
1147     }
1148
1149     private static String getFacetPath( String repositoryId, String facetId )
1150     {
1151         return getRepositoryPath( repositoryId ) + "/facets/" + facetId;
1152     }
1153
1154     private static String getNamespacePath( String repositoryId, String namespace )
1155     {
1156         return getRepositoryContentPath( repositoryId ) + namespace.replace( '.', '/' );
1157     }
1158
1159     private static String getProjectPath( String repositoryId, String namespace, String projectId )
1160     {
1161         return getNamespacePath( repositoryId, namespace ) + "/" + projectId;
1162     }
1163
1164     private static String getProjectVersionPath( String repositoryId, String namespace, String projectId,
1165                                                  String projectVersion )
1166     {
1167         return getProjectPath( repositoryId, namespace, projectId ) + "/" + projectVersion;
1168     }
1169
1170     private static String getArtifactPath( String repositoryId, String namespace, String projectId,
1171                                            String projectVersion, String id )
1172     {
1173         return getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ) + "/" + id;
1174     }
1175
1176     private Node getOrAddNodeByPath( Node baseNode, String name )
1177         throws RepositoryException
1178     {
1179         Node node = baseNode;
1180         for ( String n : name.split( "/" ) )
1181         {
1182             node = JcrUtils.getOrAddNode( node, n );
1183         }
1184         return node;
1185     }
1186
1187     private static String getFacetPath( String repositoryId, String facetId, String name )
1188     {
1189         return getFacetPath( repositoryId, facetId ) + "/" + name;
1190     }
1191
1192     private Node getOrAddRepositoryNode( String repositoryId )
1193         throws RepositoryException
1194     {
1195         Node root = session.getRootNode();
1196         Node node = JcrUtils.getOrAddNode( root, "repositories" );
1197         node = JcrUtils.getOrAddNode( node, repositoryId );
1198         return node;
1199     }
1200
1201     private Node getOrAddRepositoryContentNode( String repositoryId )
1202         throws RepositoryException
1203     {
1204         Node node = getOrAddRepositoryNode( repositoryId );
1205         return JcrUtils.getOrAddNode( node, "content" );
1206     }
1207
1208     private Node getOrAddNamespaceNode( String repositoryId, String namespace )
1209         throws RepositoryException
1210     {
1211         Node repo = getOrAddRepositoryContentNode( repositoryId );
1212         return getOrAddNodeByPath( repo, namespace.replace( '.', '/' ) );
1213     }
1214
1215     private Node getOrAddProjectNode( String repositoryId, String namespace, String projectId )
1216         throws RepositoryException
1217     {
1218         Node namespaceNode = getOrAddNamespaceNode( repositoryId, namespace );
1219         return JcrUtils.getOrAddNode( namespaceNode, projectId );
1220     }
1221
1222     private Node getOrAddProjectVersionNode( String repositoryId, String namespace, String projectId,
1223                                              String projectVersion )
1224         throws RepositoryException
1225     {
1226         Node projectNode = getOrAddProjectNode( repositoryId, namespace, projectId );
1227         return JcrUtils.getOrAddNode( projectNode, projectVersion );
1228     }
1229
1230     private Node getOrAddArtifactNode( String repositoryId, String namespace, String projectId, String projectVersion,
1231                                        String id )
1232         throws RepositoryException
1233     {
1234         Node versionNode = getOrAddProjectVersionNode( repositoryId, namespace, projectId, projectVersion );
1235         Node node = JcrUtils.getOrAddNode( versionNode, id );
1236         node.addMixin( ARTIFACT_NODE_TYPE );
1237         return node;
1238     }
1239
1240     private static Calendar createCalendar( Date time )
1241     {
1242         Calendar cal = Calendar.getInstance();
1243         cal.setTime( time );
1244         return cal;
1245     }
1246
1247     private String join( Collection<String> ids )
1248     {
1249         if ( ids != null && !ids.isEmpty() )
1250         {
1251             StringBuilder s = new StringBuilder();
1252             for ( String id : ids )
1253             {
1254                 s.append( id );
1255                 s.append( "," );
1256             }
1257             return s.substring( 0, s.length() - 1 );
1258         }
1259         return null;
1260     }
1261
1262     public Session getJcrSession()
1263     {
1264         return session;
1265     }
1266 }