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