This project has retired. For details please refer to its Attic page.
Source code
001package org.apache.archiva.metadata.repository.jcr;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.archiva.metadata.model.ArtifactMetadata;
023import org.apache.archiva.metadata.model.CiManagement;
024import org.apache.archiva.metadata.model.Dependency;
025import org.apache.archiva.metadata.model.IssueManagement;
026import org.apache.archiva.metadata.model.License;
027import org.apache.archiva.metadata.model.MailingList;
028import org.apache.archiva.metadata.model.MetadataFacet;
029import org.apache.archiva.metadata.model.MetadataFacetFactory;
030import org.apache.archiva.metadata.model.Organization;
031import org.apache.archiva.metadata.model.ProjectMetadata;
032import org.apache.archiva.metadata.model.ProjectVersionMetadata;
033import org.apache.archiva.metadata.model.ProjectVersionReference;
034import org.apache.archiva.metadata.model.Scm;
035import org.apache.archiva.metadata.repository.MetadataRepository;
036import org.apache.archiva.metadata.repository.MetadataRepositoryException;
037import org.apache.archiva.metadata.repository.MetadataResolutionException;
038import org.apache.commons.lang.StringUtils;
039import org.apache.jackrabbit.commons.JcrUtils;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043import com.google.common.collect.ImmutableMap;
044
045import javax.jcr.InvalidItemStateException;
046import javax.jcr.NamespaceRegistry;
047import javax.jcr.Node;
048import javax.jcr.NodeIterator;
049import javax.jcr.PathNotFoundException;
050import javax.jcr.Property;
051import javax.jcr.Repository;
052import javax.jcr.RepositoryException;
053import javax.jcr.Session;
054import javax.jcr.SimpleCredentials;
055import javax.jcr.ValueFactory;
056import javax.jcr.Workspace;
057import javax.jcr.nodetype.NodeTypeManager;
058import javax.jcr.nodetype.NodeTypeTemplate;
059import javax.jcr.query.Query;
060import javax.jcr.query.QueryResult;
061import javax.jcr.query.Row;
062import javax.jcr.query.RowIterator;
063
064import java.util.ArrayList;
065import java.util.Arrays;
066import java.util.Calendar;
067import java.util.Collection;
068import java.util.Collections;
069import java.util.Date;
070import java.util.HashMap;
071import java.util.Iterator;
072import java.util.LinkedHashSet;
073import java.util.List;
074import java.util.Map;
075import java.util.Map.Entry;
076import java.util.Set;
077
078/**
079 * TODO below: revise storage format for project version metadata
080 * TODO revise reference storage
081 */
082public class JcrMetadataRepository
083    implements MetadataRepository
084{
085
086    private static final String JCR_LAST_MODIFIED = "jcr:lastModified";
087
088    static final String NAMESPACE_NODE_TYPE = "archiva:namespace";
089
090    static final String PROJECT_NODE_TYPE = "archiva:project";
091
092    static final String PROJECT_VERSION_NODE_TYPE = "archiva:projectVersion";
093
094    static final String ARTIFACT_NODE_TYPE = "archiva:artifact";
095
096    static final String FACET_NODE_TYPE = "archiva:facet";
097
098    private static final String DEPENDENCY_NODE_TYPE = "archiva:dependency";
099
100    private final Map<String, MetadataFacetFactory> metadataFacetFactories;
101
102    private Logger log = LoggerFactory.getLogger( JcrMetadataRepository.class );
103
104    private Repository repository;
105
106    private Session jcrSession;
107
108    public JcrMetadataRepository( Map<String, MetadataFacetFactory> metadataFacetFactories, Repository repository )
109        throws RepositoryException
110    {
111        this.metadataFacetFactories = metadataFacetFactories;
112        this.repository = repository;
113    }
114
115
116    static void initialize( Session session )
117        throws RepositoryException
118    {
119
120        // TODO: consider using namespaces for facets instead of the current approach:
121        // (if used, check if actually called by normal injection)
122//        for ( String facetId : metadataFacetFactories.keySet() )
123//        {
124//            session.getWorkspace().getNamespaceRegistry().registerNamespace( facetId, facetId );
125//        }
126        Workspace workspace = session.getWorkspace();
127        NamespaceRegistry registry = workspace.getNamespaceRegistry();
128
129        if ( !Arrays.asList( registry.getPrefixes() ).contains( "archiva" ) )
130        {
131            registry.registerNamespace( "archiva", "http://archiva.apache.org/jcr/" );
132        }
133
134        NodeTypeManager nodeTypeManager = workspace.getNodeTypeManager();
135        registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.NAMESPACE_NODE_TYPE );
136        registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.PROJECT_NODE_TYPE );
137        registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.PROJECT_VERSION_NODE_TYPE );
138        registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.ARTIFACT_NODE_TYPE );
139        registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.FACET_NODE_TYPE );
140        registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.DEPENDENCY_NODE_TYPE );
141
142    }
143
144    private static void registerMixinNodeType( NodeTypeManager nodeTypeManager, String name )
145        throws RepositoryException
146    {
147        NodeTypeTemplate nodeType = nodeTypeManager.createNodeTypeTemplate();
148        nodeType.setMixin( true );
149        nodeType.setName( name );
150
151        // for now just don't re-create - but in future if we change the definition, make sure to remove first as an
152        // upgrade path
153        if ( !nodeTypeManager.hasNodeType( name ) )
154        {
155            nodeTypeManager.registerNodeType( nodeType, false );
156        }
157    }
158
159    @Override
160    public void updateProject( String repositoryId, ProjectMetadata project )
161        throws MetadataRepositoryException
162    {
163        updateProject( repositoryId, project.getNamespace(), project.getId() );
164    }
165
166    private void updateProject( String repositoryId, String namespace, String projectId )
167        throws MetadataRepositoryException
168    {
169        updateNamespace( repositoryId, namespace );
170
171        try
172        {
173            getOrAddProjectNode( repositoryId, namespace, projectId );
174        }
175        catch ( RepositoryException e )
176        {
177            throw new MetadataRepositoryException( e.getMessage(), e );
178        }
179    }
180
181    @Override
182    public void updateArtifact( String repositoryId, String namespace, String projectId, String projectVersion,
183                                ArtifactMetadata artifactMeta )
184        throws MetadataRepositoryException
185    {
186        updateNamespace( repositoryId, namespace );
187
188        try
189        {
190            Node node =
191                getOrAddArtifactNode( repositoryId, namespace, projectId, projectVersion, artifactMeta.getId() );
192
193            Calendar cal = Calendar.getInstance();
194            cal.setTime( artifactMeta.getFileLastModified() );
195            node.setProperty( JCR_LAST_MODIFIED, cal );
196
197            cal = Calendar.getInstance();
198            cal.setTime( artifactMeta.getWhenGathered() );
199            node.setProperty( "whenGathered", cal );
200
201            node.setProperty( "size", artifactMeta.getSize() );
202            node.setProperty( "md5", artifactMeta.getMd5() );
203            node.setProperty( "sha1", artifactMeta.getSha1() );
204
205            node.setProperty( "version", artifactMeta.getVersion() );
206
207            // iterate over available facets to update/add/remove from the artifactMetadata
208            for ( String facetId : metadataFacetFactories.keySet() )
209            {
210                MetadataFacet metadataFacet = artifactMeta.getFacet( facetId );
211                if ( metadataFacet == null )
212                {
213                    continue;
214                }
215                if ( node.hasNode( facetId ) )
216                {
217                    node.getNode( facetId ).remove();
218                }
219                if ( metadataFacet != null )
220                {
221                    // recreate, to ensure properties are removed
222                    Node n = node.addNode( facetId );
223                    n.addMixin( FACET_NODE_TYPE );
224
225                    for ( Map.Entry<String, String> entry : metadataFacet.toProperties().entrySet() )
226                    {
227                        n.setProperty( entry.getKey(), entry.getValue() );
228                    }
229                }
230            }
231        }
232        catch ( RepositoryException e )
233        {
234            throw new MetadataRepositoryException( e.getMessage(), e );
235        }
236    }
237
238    @Override
239    public void updateProjectVersion( String repositoryId, String namespace, String projectId,
240                                      ProjectVersionMetadata versionMetadata )
241        throws MetadataRepositoryException
242    {
243        updateProject( repositoryId, namespace, projectId );
244
245        try
246        {
247            Node versionNode =
248                getOrAddProjectVersionNode( repositoryId, namespace, projectId, versionMetadata.getId() );
249
250            versionNode.setProperty( "name", versionMetadata.getName() );
251            versionNode.setProperty( "description", versionMetadata.getDescription() );
252            versionNode.setProperty( "url", versionMetadata.getUrl() );
253            versionNode.setProperty( "incomplete", versionMetadata.isIncomplete() );
254
255            // FIXME: decide how to treat these in the content repo
256            if ( versionMetadata.getScm() != null )
257            {
258                versionNode.setProperty( "scm.connection", versionMetadata.getScm().getConnection() );
259                versionNode.setProperty( "scm.developerConnection", versionMetadata.getScm().getDeveloperConnection() );
260                versionNode.setProperty( "scm.url", versionMetadata.getScm().getUrl() );
261            }
262            if ( versionMetadata.getCiManagement() != null )
263            {
264                versionNode.setProperty( "ci.system", versionMetadata.getCiManagement().getSystem() );
265                versionNode.setProperty( "ci.url", versionMetadata.getCiManagement().getUrl() );
266            }
267            if ( versionMetadata.getIssueManagement() != null )
268            {
269                versionNode.setProperty( "issue.system", versionMetadata.getIssueManagement().getSystem() );
270                versionNode.setProperty( "issue.url", versionMetadata.getIssueManagement().getUrl() );
271            }
272            if ( versionMetadata.getOrganization() != null )
273            {
274                versionNode.setProperty( "org.name", versionMetadata.getOrganization().getName() );
275                versionNode.setProperty( "org.url", versionMetadata.getOrganization().getUrl() );
276            }
277            int i = 0;
278            for ( License license : versionMetadata.getLicenses() )
279            {
280                versionNode.setProperty( "license." + i + ".name", license.getName() );
281                versionNode.setProperty( "license." + i + ".url", license.getUrl() );
282                i++;
283            }
284            i = 0;
285            for ( MailingList mailingList : versionMetadata.getMailingLists() )
286            {
287                versionNode.setProperty( "mailingList." + i + ".archive", mailingList.getMainArchiveUrl() );
288                versionNode.setProperty( "mailingList." + i + ".name", mailingList.getName() );
289                versionNode.setProperty( "mailingList." + i + ".post", mailingList.getPostAddress() );
290                versionNode.setProperty( "mailingList." + i + ".unsubscribe", mailingList.getUnsubscribeAddress() );
291                versionNode.setProperty( "mailingList." + i + ".subscribe", mailingList.getSubscribeAddress() );
292                versionNode.setProperty( "mailingList." + i + ".otherArchives",
293                                         join( mailingList.getOtherArchives() ) );
294                i++;
295            }
296
297            if ( !versionMetadata.getDependencies().isEmpty() )
298            {
299                Node dependenciesNode = JcrUtils.getOrAddNode( versionNode, "dependencies" );
300
301                for ( Dependency dependency : versionMetadata.getDependencies() )
302                {
303                    // Note that we deliberately don't alter the namespace path - not enough dependencies for
304                    // number of nodes at a given depth to be an issue. Similarly, we don't add subnodes for each
305                    // component of the ID as that creates extra depth and causes a great cost in space and memory
306
307                    // FIXME: change group ID to namespace
308                    // FIXME: change to artifact's ID - this is constructed by the Maven 2 format for now.
309                    //        This won't support types where the extension doesn't match the type.
310                    //        (see also Maven2RepositoryStorage#readProjectVersionMetadata construction of POM)
311                    String id =
312                        dependency.getGroupId() + ";" + dependency.getArtifactId() + "-" + dependency.getVersion();
313                    if ( dependency.getClassifier() != null )
314                    {
315                        id += "-" + dependency.getClassifier();
316                    }
317                    id += "." + dependency.getType();
318
319                    Node n = JcrUtils.getOrAddNode( dependenciesNode, id );
320                    n.addMixin( DEPENDENCY_NODE_TYPE );
321
322                    // FIXME: remove temp code just to make it keep working
323                    n.setProperty( "groupId", dependency.getGroupId() );
324                    n.setProperty( "artifactId", dependency.getArtifactId() );
325                    n.setProperty( "version", dependency.getVersion() );
326                    n.setProperty( "type", dependency.getType() );
327                    n.setProperty( "classifier", dependency.getClassifier() );
328                    n.setProperty( "scope", dependency.getScope() );
329                    n.setProperty( "systemPath", dependency.getSystemPath() );
330                    n.setProperty( "optional", dependency.isOptional() );
331
332                    // node has no native content at this time, just facets
333                    // no need to list a type as it's implied by the path. Parents are Maven specific.
334
335                    // FIXME: add scope, systemPath, type, version, classifier & maven2 specific IDs as a facet
336                    //        (should also have been added to the Dependency)
337
338                    // TODO: add a property that is a weak reference to the originating artifact, creating it if
339                    //       necessary (without adding the archiva:artifact mixin so that it doesn't get listed as an
340                    //       artifact, which gives a different meaning to "incomplete" which is a known local project
341                    //       that doesn't have metadata yet but has artifacts). (Though we may want to give it the
342                    //       artifact mixin and another property to identify all non-local artifacts for the closure
343                    //       reports)
344                }
345            }
346
347            for ( MetadataFacet facet : versionMetadata.getFacetList() )
348            {
349                // recreate, to ensure properties are removed
350                if ( versionNode.hasNode( facet.getFacetId() ) )
351                {
352                    versionNode.getNode( facet.getFacetId() ).remove();
353                }
354                Node n = versionNode.addNode( facet.getFacetId() );
355                n.addMixin( FACET_NODE_TYPE );
356
357                for ( Map.Entry<String, String> entry : facet.toProperties().entrySet() )
358                {
359                    n.setProperty( entry.getKey(), entry.getValue() );
360                }
361            }
362        }
363        catch ( RepositoryException e )
364        {
365            throw new MetadataRepositoryException( e.getMessage(), e );
366        }
367    }
368
369    @Override
370    public void updateNamespace( String repositoryId, String namespace )
371        throws MetadataRepositoryException
372    {
373        try
374        {
375            Node node = getOrAddNamespaceNode( repositoryId, namespace );
376            node.setProperty( "namespace", namespace );
377        }
378        catch ( RepositoryException e )
379        {
380            throw new MetadataRepositoryException( e.getMessage(), e );
381        }
382    }
383
384    @Override
385    public void removeProject( String repositoryId, String namespace, String projectId )
386        throws MetadataRepositoryException
387    {
388        try
389        {
390            Node root = getJcrSession().getRootNode();
391            String namespacePath = getNamespacePath( repositoryId, namespace );
392
393            if ( root.hasNode( namespacePath ) )
394            {
395                Iterator<Node> nodeIterator = JcrUtils.getChildNodes( root.getNode( namespacePath ) ).iterator();
396                while ( nodeIterator.hasNext() )
397                {
398                    Node node = nodeIterator.next();
399                    if ( node.isNodeType( PROJECT_NODE_TYPE ) && projectId.equals( node.getName() ) )
400                    {
401                        node.remove();
402                    }
403                }
404
405            }
406        }
407        catch ( RepositoryException e )
408        {
409            throw new MetadataRepositoryException( e.getMessage(), e );
410        }
411
412    }
413
414
415    @Override
416    public boolean hasMetadataFacet( String repositoryId, String facetId )
417        throws MetadataRepositoryException
418    {
419        try
420        {
421            Node node = getJcrSession().getRootNode().getNode( getFacetPath( repositoryId, facetId ) );
422            return node.getNodes().hasNext();
423        }
424        catch ( PathNotFoundException e )
425        {
426            // ignored - the facet doesn't exist, so return false
427            return false;
428        }
429        catch ( RepositoryException e )
430        {
431            throw new MetadataRepositoryException( e.getMessage(), e );
432        }
433    }
434
435    @Override
436    public List<String> getMetadataFacets( String repositoryId, String facetId )
437        throws MetadataRepositoryException
438    {
439        List<String> facets = new ArrayList<>();
440
441        try
442        {
443            // no need to construct node-by-node here, as we'll find in the next instance, the facet names have / and
444            // are paths themselves
445            Node node = getJcrSession().getRootNode().getNode( getFacetPath( repositoryId, facetId ) );
446
447            // TODO: this is a bit awkward. Might be better to review the purpose of this function - why is the list of
448            //   paths helpful?
449            recurse( facets, "", node );
450        }
451        catch ( PathNotFoundException e )
452        {
453            // ignored - the facet doesn't exist, so return the empty list
454        }
455        catch ( RepositoryException e )
456        {
457            throw new MetadataRepositoryException( e.getMessage(), e );
458        }
459        return facets;
460    }
461
462    private void recurse( List<String> facets, String prefix, Node node )
463        throws RepositoryException
464    {
465        for ( Node n : JcrUtils.getChildNodes( node ) )
466        {
467            String name = prefix + "/" + n.getName();
468            if ( n.hasNodes() )
469            {
470                recurse( facets, name, n );
471            }
472            else
473            {
474                // strip leading / first
475                facets.add( name.substring( 1 ) );
476            }
477        }
478    }
479
480    @Override
481    public MetadataFacet getMetadataFacet( String repositoryId, String facetId, String name )
482        throws MetadataRepositoryException
483    {
484        MetadataFacet metadataFacet = null;
485        try
486        {
487            Node root = getJcrSession().getRootNode();
488            Node node = root.getNode( getFacetPath( repositoryId, facetId, name ) );
489
490            if ( metadataFacetFactories == null )
491            {
492                return metadataFacet;
493            }
494
495            MetadataFacetFactory metadataFacetFactory = metadataFacetFactories.get( facetId );
496            if ( metadataFacetFactory != null )
497            {
498                metadataFacet = metadataFacetFactory.createMetadataFacet( repositoryId, name );
499                Map<String, String> map = new HashMap<>();
500                for ( Property property : JcrUtils.getProperties( node ) )
501                {
502                    String p = property.getName();
503                    if ( !p.startsWith( "jcr:" ) )
504                    {
505                        map.put( p, property.getString() );
506                    }
507                }
508                metadataFacet.fromProperties( map );
509            }
510        }
511        catch ( PathNotFoundException e )
512        {
513            // ignored - the facet doesn't exist, so return null
514        }
515        catch ( RepositoryException e )
516        {
517            throw new MetadataRepositoryException( e.getMessage(), e );
518        }
519        return metadataFacet;
520    }
521
522    @Override
523    public void addMetadataFacet( String repositoryId, MetadataFacet metadataFacet )
524        throws MetadataRepositoryException
525    {
526        try
527        {
528            Node repo = getOrAddRepositoryNode( repositoryId );
529            Node facets = JcrUtils.getOrAddNode( repo, "facets" );
530
531            String id = metadataFacet.getFacetId();
532            Node facetNode = JcrUtils.getOrAddNode( facets, id );
533
534            Node node = getOrAddNodeByPath( facetNode, metadataFacet.getName() );
535
536            for ( Map.Entry<String, String> entry : metadataFacet.toProperties().entrySet() )
537            {
538                node.setProperty( entry.getKey(), entry.getValue() );
539            }
540        }
541        catch ( RepositoryException e )
542        {
543            throw new MetadataRepositoryException( e.getMessage(), e );
544        }
545    }
546
547    @Override
548    public void removeNamespace( String repositoryId, String projectId )
549        throws MetadataRepositoryException
550    {
551        try
552        {
553            Node root = getJcrSession().getRootNode();
554            String path = getNamespacePath( repositoryId, projectId );
555            if ( root.hasNode( path ) )
556            {
557                Node node = root.getNode( path );
558                if ( node.isNodeType( NAMESPACE_NODE_TYPE ) )
559                {
560                    node.remove();
561                }
562            }
563        }
564        catch ( RepositoryException e )
565        {
566            throw new MetadataRepositoryException( e.getMessage(), e );
567        }
568    }
569
570    @Override
571    public void removeMetadataFacets( String repositoryId, String facetId )
572        throws MetadataRepositoryException
573    {
574        try
575        {
576            Node root = getJcrSession().getRootNode();
577            String path = getFacetPath( repositoryId, facetId );
578            if ( root.hasNode( path ) )
579            {
580                root.getNode( path ).remove();
581            }
582        }
583        catch ( RepositoryException e )
584        {
585            throw new MetadataRepositoryException( e.getMessage(), e );
586        }
587    }
588
589    @Override
590    public void removeMetadataFacet( String repositoryId, String facetId, String name )
591        throws MetadataRepositoryException
592    {
593        try
594        {
595            Node root = getJcrSession().getRootNode();
596            String path = getFacetPath( repositoryId, facetId, name );
597            if ( root.hasNode( path ) )
598            {
599                Node node = root.getNode( path );
600                do
601                {
602                    // also remove empty container nodes
603                    Node parent = node.getParent();
604                    node.remove();
605                    node = parent;
606                }
607                while ( !node.hasNodes() );
608            }
609        }
610        catch ( RepositoryException e )
611        {
612            throw new MetadataRepositoryException( e.getMessage(), e );
613        }
614    }
615
616    @Override
617    public List<ArtifactMetadata> getArtifactsByDateRange( String repoId, Date startTime, Date endTime )
618        throws MetadataRepositoryException
619    {
620        List<ArtifactMetadata> artifacts;
621
622        String q = getArtifactQuery( repoId );
623
624        if ( startTime != null )
625        {
626            q += " AND [whenGathered] >= $start";
627        }
628        if ( endTime != null )
629        {
630            q += " AND [whenGathered] <= $end";
631        }
632
633        try
634        {
635            Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
636            ValueFactory valueFactory = getJcrSession().getValueFactory();
637            if ( startTime != null )
638            {
639                query.bindValue( "start", valueFactory.createValue( createCalendar( startTime ) ) );
640            }
641            if ( endTime != null )
642            {
643                query.bindValue( "end", valueFactory.createValue( createCalendar( endTime ) ) );
644            }
645            QueryResult result = query.execute();
646
647            artifacts = new ArrayList<>();
648            for ( Node n : JcrUtils.getNodes( result ) )
649            {
650                artifacts.add( getArtifactFromNode( repoId, n ) );
651            }
652        }
653        catch ( RepositoryException e )
654        {
655            throw new MetadataRepositoryException( e.getMessage(), e );
656        }
657        return artifacts;
658    }
659
660    @Override
661    public Collection<String> getRepositories()
662        throws MetadataRepositoryException
663    {
664        List<String> repositories;
665
666        try
667        {
668            Node root = getJcrSession().getRootNode();
669            if ( root.hasNode( "repositories" ) )
670            {
671                Node node = root.getNode( "repositories" );
672
673                repositories = new ArrayList<>();
674                NodeIterator i = node.getNodes();
675                while ( i.hasNext() )
676                {
677                    Node n = i.nextNode();
678                    repositories.add( n.getName() );
679                }
680            }
681            else
682            {
683                repositories = Collections.emptyList();
684            }
685        }
686        catch ( RepositoryException e )
687        {
688            throw new MetadataRepositoryException( e.getMessage(), e );
689        }
690        return repositories;
691    }
692
693    @Override
694    public List<ArtifactMetadata> getArtifactsByChecksum( String repositoryId, String checksum )
695        throws MetadataRepositoryException
696    {
697        List<ArtifactMetadata> artifacts;
698
699        String q = getArtifactQuery( repositoryId ) + " AND ([sha1] = $checksum OR [md5] = $checksum)";
700
701        try
702        {
703            Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
704            ValueFactory valueFactory = getJcrSession().getValueFactory();
705            query.bindValue( "checksum", valueFactory.createValue( checksum ) );
706            QueryResult result = query.execute();
707
708            artifacts = new ArrayList<>();
709            for ( Node n : JcrUtils.getNodes( result ) )
710            {
711                artifacts.add( getArtifactFromNode( repositoryId, n ) );
712            }
713        }
714        catch ( RepositoryException e )
715        {
716            throw new MetadataRepositoryException( e.getMessage(), e );
717        }
718        return artifacts;
719    }
720
721    private List<ArtifactMetadata> runJcrQuery( String repositoryId, String q, Map<String, String> bindings )
722        throws MetadataRepositoryException
723    {
724        List<ArtifactMetadata> artifacts;
725        if ( repositoryId != null )
726        {
727            q += " AND ISDESCENDANTNODE(artifact,'/" + getRepositoryContentPath( repositoryId ) + "')";
728        }
729
730        log.info( "Running JCR Query: {}", q );
731
732        try
733        {
734            Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
735            ValueFactory valueFactory = getJcrSession().getValueFactory();
736            for ( Entry<String, String> entry : bindings.entrySet() )
737            {
738                query.bindValue( entry.getKey(), valueFactory.createValue( entry.getValue() ) );
739            }
740            long start = Calendar.getInstance().getTimeInMillis();
741            QueryResult result = query.execute();
742            long end = Calendar.getInstance().getTimeInMillis();
743            log.info( "JCR Query ran in {} milliseconds: {}", end - start , q );
744
745            artifacts = new ArrayList<>();
746            RowIterator rows = result.getRows();
747            while ( rows.hasNext() )
748            {
749                Row row = rows.nextRow();
750                Node node = row.getNode( "artifact" );
751                artifacts.add( getArtifactFromNode( repositoryId, node ) );
752            }
753        }
754        catch ( RepositoryException e )
755        {
756            throw new MetadataRepositoryException( e.getMessage(), e );
757        }
758        return artifacts;
759    }
760
761    @Override
762    public List<ArtifactMetadata> getArtifactsByProjectVersionMetadata( String key, String value, String repositoryId )
763        throws MetadataRepositoryException
764    {
765        String q =
766            "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE + "] AS projectVersion INNER JOIN [" + ARTIFACT_NODE_TYPE
767                + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) INNER JOIN [" + FACET_NODE_TYPE
768                + "] AS facet ON ISCHILDNODE(facet, projectVersion) WHERE ([facet].[" + key + "] = $value)";
769
770        return runJcrQuery( repositoryId, q, ImmutableMap.of( "value", value ) );
771    }
772
773
774    @Override
775    public List<ArtifactMetadata> getArtifactsByMetadata( String key, String value, String repositoryId )
776        throws MetadataRepositoryException
777    {
778        String q =
779            "SELECT * FROM [" + ARTIFACT_NODE_TYPE + "] AS artifact INNER JOIN [" + FACET_NODE_TYPE
780                + "] AS facet ON ISCHILDNODE(facet, artifact) WHERE ([facet].[" + key + "] = $value)";
781
782        return runJcrQuery( repositoryId, q, ImmutableMap.of( "value", value ) );
783    }
784
785
786    @Override
787    public List<ArtifactMetadata> getArtifactsByProperty( String key, String value, String repositoryId )
788        throws MetadataRepositoryException
789    {
790        String q =
791            "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE + "] AS projectVersion INNER JOIN [" + ARTIFACT_NODE_TYPE
792                + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) WHERE ([projectVersion].[" + key
793                + "] = $value)";
794
795        return runJcrQuery( repositoryId, q, ImmutableMap.of( "value", value ) );
796    }
797
798
799    @Override
800    public void removeRepository( String repositoryId )
801        throws MetadataRepositoryException
802    {
803        try
804        {
805            Node root = getJcrSession().getRootNode();
806            String path = getRepositoryPath( repositoryId );
807            if ( root.hasNode( path ) )
808            {
809                root.getNode( path ).remove();
810            }
811        }
812        catch ( RepositoryException e )
813        {
814            throw new MetadataRepositoryException( e.getMessage(), e );
815        }
816    }
817
818    @Override
819    public List<ArtifactMetadata> getArtifacts( String repositoryId )
820        throws MetadataRepositoryException
821    {
822        List<ArtifactMetadata> artifacts;
823
824        String q = getArtifactQuery( repositoryId );
825
826        try
827        {
828            Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
829            QueryResult result = query.execute();
830
831            artifacts = new ArrayList<>();
832            for ( Node n : JcrUtils.getNodes( result ) )
833            {
834                if ( n.isNodeType( ARTIFACT_NODE_TYPE ) )
835                {
836                    artifacts.add( getArtifactFromNode( repositoryId, n ) );
837                }
838            }
839        }
840        catch ( RepositoryException e )
841        {
842            throw new MetadataRepositoryException( e.getMessage(), e );
843        }
844        return artifacts;
845    }
846
847    private static String getArtifactQuery( String repositoryId )
848    {
849        return "SELECT * FROM [" + ARTIFACT_NODE_TYPE + "] AS artifact WHERE ISDESCENDANTNODE(artifact,'/" +
850            getRepositoryContentPath( repositoryId ) + "')";
851    }
852
853    @Override
854    public ProjectMetadata getProject( String repositoryId, String namespace, String projectId )
855        throws MetadataResolutionException
856    {
857        ProjectMetadata metadata = null;
858
859        try
860        {
861            Node root = getJcrSession().getRootNode();
862
863            // basically just checking it exists
864            String path = getProjectPath( repositoryId, namespace, projectId );
865            if ( root.hasNode( path ) )
866            {
867                metadata = new ProjectMetadata();
868                metadata.setId( projectId );
869                metadata.setNamespace( namespace );
870            }
871        }
872        catch ( RepositoryException e )
873        {
874            throw new MetadataResolutionException( e.getMessage(), e );
875        }
876
877        return metadata;
878    }
879
880    @Override
881    public ProjectVersionMetadata getProjectVersion( String repositoryId, String namespace, String projectId,
882                                                     String projectVersion )
883        throws MetadataResolutionException
884    {
885        ProjectVersionMetadata versionMetadata;
886
887        try
888        {
889            Node root = getJcrSession().getRootNode();
890
891            String path = getProjectVersionPath( repositoryId, namespace, projectId, projectVersion );
892            if ( !root.hasNode( path ) )
893            {
894                return null;
895            }
896
897            Node node = root.getNode( path );
898
899            versionMetadata = new ProjectVersionMetadata();
900            versionMetadata.setId( projectVersion );
901            versionMetadata.setName( getPropertyString( node, "name" ) );
902            versionMetadata.setDescription( getPropertyString( node, "description" ) );
903            versionMetadata.setUrl( getPropertyString( node, "url" ) );
904            versionMetadata.setIncomplete(
905                node.hasProperty( "incomplete" ) && node.getProperty( "incomplete" ).getBoolean() );
906
907            // FIXME: decide how to treat these in the content repo
908            String scmConnection = getPropertyString( node, "scm.connection" );
909            String scmDeveloperConnection = getPropertyString( node, "scm.developerConnection" );
910            String scmUrl = getPropertyString( node, "scm.url" );
911            if ( scmConnection != null || scmDeveloperConnection != null || scmUrl != null )
912            {
913                Scm scm = new Scm();
914                scm.setConnection( scmConnection );
915                scm.setDeveloperConnection( scmDeveloperConnection );
916                scm.setUrl( scmUrl );
917                versionMetadata.setScm( scm );
918            }
919
920            String ciSystem = getPropertyString( node, "ci.system" );
921            String ciUrl = getPropertyString( node, "ci.url" );
922            if ( ciSystem != null || ciUrl != null )
923            {
924                CiManagement ci = new CiManagement();
925                ci.setSystem( ciSystem );
926                ci.setUrl( ciUrl );
927                versionMetadata.setCiManagement( ci );
928            }
929
930            String issueSystem = getPropertyString( node, "issue.system" );
931            String issueUrl = getPropertyString( node, "issue.url" );
932            if ( issueSystem != null || issueUrl != null )
933            {
934                IssueManagement issueManagement = new IssueManagement();
935                issueManagement.setSystem( issueSystem );
936                issueManagement.setUrl( issueUrl );
937                versionMetadata.setIssueManagement( issueManagement );
938            }
939
940            String orgName = getPropertyString( node, "org.name" );
941            String orgUrl = getPropertyString( node, "org.url" );
942            if ( orgName != null || orgUrl != null )
943            {
944                Organization org = new Organization();
945                org.setName( orgName );
946                org.setUrl( orgUrl );
947                versionMetadata.setOrganization( org );
948            }
949
950            boolean done = false;
951            int i = 0;
952            while ( !done )
953            {
954                String licenseName = getPropertyString( node, "license." + i + ".name" );
955                String licenseUrl = getPropertyString( node, "license." + i + ".url" );
956                if ( licenseName != null || licenseUrl != null )
957                {
958                    License license = new License();
959                    license.setName( licenseName );
960                    license.setUrl( licenseUrl );
961                    versionMetadata.addLicense( license );
962                }
963                else
964                {
965                    done = true;
966                }
967                i++;
968            }
969
970            done = false;
971            i = 0;
972            while ( !done )
973            {
974                String mailingListName = getPropertyString( node, "mailingList." + i + ".name" );
975                if ( mailingListName != null )
976                {
977                    MailingList mailingList = new MailingList();
978                    mailingList.setName( mailingListName );
979                    mailingList.setMainArchiveUrl( getPropertyString( node, "mailingList." + i + ".archive" ) );
980                    String n = "mailingList." + i + ".otherArchives";
981                    if ( node.hasProperty( n ) )
982                    {
983                        mailingList.setOtherArchives( Arrays.asList( getPropertyString( node, n ).split( "," ) ) );
984                    }
985                    else
986                    {
987                        mailingList.setOtherArchives( Collections.<String>emptyList() );
988                    }
989                    mailingList.setPostAddress( getPropertyString( node, "mailingList." + i + ".post" ) );
990                    mailingList.setSubscribeAddress( getPropertyString( node, "mailingList." + i + ".subscribe" ) );
991                    mailingList.setUnsubscribeAddress( getPropertyString( node, "mailingList." + i + ".unsubscribe" ) );
992                    versionMetadata.addMailingList( mailingList );
993                }
994                else
995                {
996                    done = true;
997                }
998                i++;
999            }
1000
1001            if ( node.hasNode( "dependencies" ) )
1002            {
1003                Node dependenciesNode = node.getNode( "dependencies" );
1004                for ( Node n : JcrUtils.getChildNodes( dependenciesNode ) )
1005                {
1006                    if ( n.isNodeType( DEPENDENCY_NODE_TYPE ) )
1007                    {
1008                        Dependency dependency = new Dependency();
1009                        // FIXME: correct these properties
1010                        dependency.setArtifactId( getPropertyString( n, "artifactId" ) );
1011                        dependency.setGroupId( getPropertyString( n, "groupId" ) );
1012                        dependency.setClassifier( getPropertyString( n, "classifier" ) );
1013                        dependency.setOptional( Boolean.valueOf( getPropertyString( n, "optional" ) ) );
1014                        dependency.setScope( getPropertyString( n, "scope" ) );
1015                        dependency.setSystemPath( getPropertyString( n, "systemPath" ) );
1016                        dependency.setType( getPropertyString( n, "type" ) );
1017                        dependency.setVersion( getPropertyString( n, "version" ) );
1018                        versionMetadata.addDependency( dependency );
1019                    }
1020                }
1021            }
1022
1023            for ( Node n : JcrUtils.getChildNodes( node ) )
1024            {
1025                if ( n.isNodeType( FACET_NODE_TYPE ) )
1026                {
1027                    String name = n.getName();
1028                    MetadataFacetFactory factory = metadataFacetFactories.get( name );
1029                    if ( factory == null )
1030                    {
1031                        log.error( "Attempted to load unknown project version metadata facet: {}", name );
1032                    }
1033                    else
1034                    {
1035                        MetadataFacet facet = factory.createMetadataFacet();
1036                        Map<String, String> map = new HashMap<>();
1037                        for ( Property property : JcrUtils.getProperties( n ) )
1038                        {
1039                            String p = property.getName();
1040                            if ( !p.startsWith( "jcr:" ) )
1041                            {
1042                                map.put( p, property.getString() );
1043                            }
1044                        }
1045                        facet.fromProperties( map );
1046                        versionMetadata.addFacet( facet );
1047                    }
1048                }
1049            }
1050        }
1051        catch ( RepositoryException e )
1052        {
1053            throw new MetadataResolutionException( e.getMessage(), e );
1054        }
1055
1056        return versionMetadata;
1057    }
1058
1059    @Override
1060    public Collection<String> getArtifactVersions( String repositoryId, String namespace, String projectId,
1061                                                   String projectVersion )
1062        throws MetadataResolutionException
1063    {
1064        Set<String> versions = new LinkedHashSet<String>();
1065
1066        try
1067        {
1068            Node root = getJcrSession().getRootNode();
1069
1070            Node node = root.getNode( getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ) );
1071
1072            for ( Node n : JcrUtils.getChildNodes( node ) )
1073            {
1074                versions.add( n.getProperty( "version" ).getString() );
1075            }
1076        }
1077        catch ( PathNotFoundException e )
1078        {
1079            // ignore repo not found for now
1080        }
1081        catch ( RepositoryException e )
1082        {
1083            throw new MetadataResolutionException( e.getMessage(), e );
1084        }
1085
1086        return versions;
1087    }
1088
1089    @Override
1090    public Collection<ProjectVersionReference> getProjectReferences( String repositoryId, String namespace,
1091                                                                     String projectId, String projectVersion )
1092        throws MetadataResolutionException
1093    {
1094
1095        List<ProjectVersionReference> references = new ArrayList<>();
1096
1097        // TODO: bind variables instead
1098        String q = "SELECT * FROM [archiva:dependency] WHERE ISDESCENDANTNODE([/repositories/" + repositoryId +
1099            "/content]) AND [groupId]='" + namespace + "' AND [artifactId]='" + projectId + "'";
1100        if ( projectVersion != null )
1101        {
1102            q += " AND [version]='" + projectVersion + "'";
1103        }
1104        try
1105        {
1106            Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
1107            QueryResult result = query.execute();
1108
1109            for ( Node n : JcrUtils.getNodes( result ) )
1110            {
1111                n = n.getParent(); // dependencies grouping element
1112
1113                n = n.getParent(); // project version
1114                String usedByProjectVersion = n.getName();
1115
1116                n = n.getParent(); // project
1117                String usedByProject = n.getName();
1118
1119                n = n.getParent(); // namespace
1120                String usedByNamespace = n.getProperty( "namespace" ).getString();
1121
1122                ProjectVersionReference ref = new ProjectVersionReference();
1123                ref.setNamespace( usedByNamespace );
1124                ref.setProjectId( usedByProject );
1125                ref.setProjectVersion( usedByProjectVersion );
1126                ref.setReferenceType( ProjectVersionReference.ReferenceType.DEPENDENCY );
1127                references.add( ref );
1128            }
1129        }
1130        catch ( RepositoryException e )
1131        {
1132            throw new MetadataResolutionException( e.getMessage(), e );
1133        }
1134
1135        return references;
1136    }
1137
1138    @Override
1139    public Collection<String> getRootNamespaces( String repositoryId )
1140        throws MetadataResolutionException
1141    {
1142        return getNamespaces( repositoryId, null );
1143    }
1144
1145    @Override
1146    public Collection<String> getNamespaces( String repositoryId, String baseNamespace )
1147        throws MetadataResolutionException
1148    {
1149        String path = baseNamespace != null
1150            ? getNamespacePath( repositoryId, baseNamespace )
1151            : getRepositoryContentPath( repositoryId );
1152
1153        return getNodeNames( path, NAMESPACE_NODE_TYPE );
1154    }
1155
1156    @Override
1157    public Collection<String> getProjects( String repositoryId, String namespace )
1158        throws MetadataResolutionException
1159    {
1160        return getNodeNames( getNamespacePath( repositoryId, namespace ), PROJECT_NODE_TYPE );
1161    }
1162
1163    @Override
1164    public Collection<String> getProjectVersions( String repositoryId, String namespace, String projectId )
1165        throws MetadataResolutionException
1166    {
1167        return getNodeNames( getProjectPath( repositoryId, namespace, projectId ), PROJECT_VERSION_NODE_TYPE );
1168    }
1169
1170    @Override
1171    public void removeArtifact( ArtifactMetadata artifactMetadata, String baseVersion )
1172        throws MetadataRepositoryException
1173    {
1174
1175        String repositoryId = artifactMetadata.getRepositoryId();
1176
1177        try
1178        {
1179            Node root = getJcrSession().getRootNode();
1180            String path =
1181                getProjectVersionPath( repositoryId, artifactMetadata.getNamespace(), artifactMetadata.getProject(),
1182                                       baseVersion );
1183
1184            if ( root.hasNode( path ) )
1185            {
1186                Node node = root.getNode( path );
1187
1188                for ( Node n : JcrUtils.getChildNodes( node ) )
1189                {
1190                    if ( n.isNodeType( ARTIFACT_NODE_TYPE ) )
1191                    {
1192                        if ( n.hasProperty( "version" ) )
1193                        {
1194                            String version = n.getProperty( "version" ).getString();
1195                            if ( StringUtils.equals( version, artifactMetadata.getVersion() ) )
1196                            {
1197                                n.remove();
1198                            }
1199                        }
1200
1201                    }
1202                }
1203            }
1204        }
1205        catch ( RepositoryException e )
1206        {
1207            throw new MetadataRepositoryException( e.getMessage(), e );
1208        }
1209
1210
1211    }
1212
1213
1214    @Override
1215    public void removeProjectVersion( String repoId, String namespace, String projectId, String projectVersion )
1216        throws MetadataRepositoryException
1217    {
1218        try
1219        {
1220
1221            String path = getProjectPath( repoId, namespace, projectId );
1222            Node root = getJcrSession().getRootNode();
1223
1224            Node nodeAtPath = root.getNode( path );
1225
1226            for ( Node node : JcrUtils.getChildNodes( nodeAtPath ) )
1227            {
1228                if ( node.isNodeType( PROJECT_VERSION_NODE_TYPE ) && StringUtils.equals( projectVersion,
1229                                                                                         node.getName() ) )
1230                {
1231                    node.remove();
1232                }
1233            }
1234        }
1235        catch ( RepositoryException e )
1236        {
1237            throw new MetadataRepositoryException( e.getMessage(), e );
1238        }
1239    }
1240
1241    @Override
1242    public void removeArtifact( String repositoryId, String namespace, String projectId, String projectVersion,
1243                                String id )
1244        throws MetadataRepositoryException
1245    {
1246        try
1247        {
1248            Node root = getJcrSession().getRootNode();
1249            String path = getArtifactPath( repositoryId, namespace, projectId, projectVersion, id );
1250            if ( root.hasNode( path ) )
1251            {
1252                root.getNode( path ).remove();
1253            }
1254
1255            // remove version
1256
1257            path = getProjectPath( repositoryId, namespace, projectId );
1258
1259            Node nodeAtPath = root.getNode( path );
1260
1261            for ( Node node : JcrUtils.getChildNodes( nodeAtPath ) )
1262            {
1263                if ( node.isNodeType( PROJECT_VERSION_NODE_TYPE ) //
1264                    && StringUtils.equals( node.getName(), projectVersion ) )
1265                {
1266                    node.remove();
1267                }
1268            }
1269        }
1270        catch ( RepositoryException e )
1271        {
1272            throw new MetadataRepositoryException( e.getMessage(), e );
1273        }
1274    }
1275
1276    @Override
1277    public void removeArtifact( String repositoryId, String namespace, String project, String projectVersion,
1278                                MetadataFacet metadataFacet )
1279        throws MetadataRepositoryException
1280    {
1281        try
1282        {
1283            Node root = getJcrSession().getRootNode();
1284            String path = getProjectVersionPath( repositoryId, namespace, project, projectVersion );
1285
1286            if ( root.hasNode( path ) )
1287            {
1288                Node node = root.getNode( path );
1289
1290                for ( Node n : JcrUtils.getChildNodes( node ) )
1291                {
1292                    if ( n.isNodeType( ARTIFACT_NODE_TYPE ) )
1293                    {
1294                        ArtifactMetadata artifactMetadata = getArtifactFromNode( repositoryId, n );
1295                        log.debug( "artifactMetadata: {}", artifactMetadata );
1296                        MetadataFacet metadataFacetToRemove = artifactMetadata.getFacet( metadataFacet.getFacetId() );
1297                        if ( metadataFacetToRemove != null && metadataFacet.equals( metadataFacetToRemove ) )
1298                        {
1299                            n.remove();
1300                        }
1301                    }
1302                }
1303            }
1304        }
1305        catch ( RepositoryException e )
1306        {
1307            throw new MetadataRepositoryException( e.getMessage(), e );
1308        }
1309    }
1310
1311    @Override
1312    public Collection<ArtifactMetadata> getArtifacts( String repositoryId, String namespace, String projectId,
1313                                                      String projectVersion )
1314        throws MetadataResolutionException
1315    {
1316        List<ArtifactMetadata> artifacts = new ArrayList<>();
1317
1318        try
1319        {
1320            Node root = getJcrSession().getRootNode();
1321            String path = getProjectVersionPath( repositoryId, namespace, projectId, projectVersion );
1322
1323            if ( root.hasNode( path ) )
1324            {
1325                Node node = root.getNode( path );
1326
1327                for ( Node n : JcrUtils.getChildNodes( node ) )
1328                {
1329                    if ( n.isNodeType( ARTIFACT_NODE_TYPE ) )
1330                    {
1331                        artifacts.add( getArtifactFromNode( repositoryId, n ) );
1332                    }
1333                }
1334            }
1335        }
1336        catch ( RepositoryException e )
1337        {
1338            throw new MetadataResolutionException( e.getMessage(), e );
1339        }
1340
1341        return artifacts;
1342    }
1343
1344    @Override
1345    public void save()
1346    {
1347        try
1348        {
1349            getJcrSession().save();
1350        } catch ( InvalidItemStateException e ) {
1351            // olamy this might happen when deleting a repo while is under scanning
1352            log.warn( "skip InvalidItemStateException:" + e.getMessage(), e );
1353        }
1354        catch ( RepositoryException e )
1355        {
1356            throw new RuntimeException( e.getMessage(), e );
1357        }
1358    }
1359
1360    @Override
1361    public void revert()
1362    {
1363        try
1364        {
1365            getJcrSession().refresh( false );
1366        }
1367        catch ( RepositoryException e )
1368        {
1369            throw new RuntimeException( e.getMessage(), e );
1370        }
1371    }
1372
1373    @Override
1374    public boolean canObtainAccess( Class<?> aClass )
1375    {
1376        return aClass == Session.class;
1377    }
1378
1379    @Override
1380    public <T>T obtainAccess( Class<T> aClass )
1381        throws MetadataRepositoryException
1382    {
1383        if ( aClass == Session.class )
1384        {
1385            try
1386            {
1387                return (T) getJcrSession();
1388            }
1389            catch ( RepositoryException e )
1390            {
1391                log.error( e.getMessage(), e );
1392                throw new MetadataRepositoryException( e.getMessage(), e );
1393            }
1394        }
1395        throw new IllegalArgumentException(
1396            "Access using " + aClass + " is not supported on the JCR metadata storage" );
1397    }
1398
1399    @Override
1400    public void close()
1401        throws MetadataRepositoryException
1402    {
1403        if ( jcrSession != null && jcrSession.isLive() )
1404        {
1405            jcrSession.logout();
1406        }
1407    }
1408
1409
1410    /**
1411     * Exact is ignored as we can't do exact search in any property, we need a key
1412     */
1413    @Override
1414    public List<ArtifactMetadata> searchArtifacts( String text, String repositoryId, boolean exact )
1415        throws MetadataRepositoryException
1416    {
1417        return searchArtifacts( null, text, repositoryId, exact );
1418    }
1419
1420    @Override
1421    public List<ArtifactMetadata> searchArtifacts( String key, String text, String repositoryId, boolean exact )
1422        throws MetadataRepositoryException
1423    {
1424        // we can't do exact search in any property (*), we need a key
1425        boolean e = exact && key != null;
1426        String theKey = key == null ? "*" : "[" + key + "]";
1427        String projectVersionCondition =
1428            e ? "(projectVersion." + theKey + " = $value)" : "contains([projectVersion]." + theKey + ", $value)";
1429        String facetCondition = e ? "(facet." + theKey + " = $value)" : "contains([facet]." + theKey + ", $value)";
1430        String q =
1431            "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE + "] AS projectVersion LEFT OUTER JOIN ["
1432                + ARTIFACT_NODE_TYPE + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) LEFT OUTER JOIN ["
1433                + FACET_NODE_TYPE + "] AS facet ON ISCHILDNODE(facet, projectVersion) WHERE ("
1434                + projectVersionCondition + " OR " + facetCondition + ")";
1435
1436        return runJcrQuery( repositoryId, q, ImmutableMap.of( "value", text ) );
1437    }
1438
1439    private ArtifactMetadata getArtifactFromNode( String repositoryId, Node artifactNode )
1440        throws RepositoryException
1441    {
1442        String id = artifactNode.getName();
1443
1444        ArtifactMetadata artifact = new ArtifactMetadata();
1445        artifact.setId( id );
1446        artifact.setRepositoryId( repositoryId == null ? artifactNode.getAncestor(2).getName() : repositoryId );
1447
1448        Node projectVersionNode = artifactNode.getParent();
1449        Node projectNode = projectVersionNode.getParent();
1450        Node namespaceNode = projectNode.getParent();
1451
1452        artifact.setNamespace( namespaceNode.getProperty( "namespace" ).getString() );
1453        artifact.setProject( projectNode.getName() );
1454        artifact.setProjectVersion( projectVersionNode.getName() );
1455        artifact.setVersion( artifactNode.hasProperty( "version" )
1456                                 ? artifactNode.getProperty( "version" ).getString()
1457                                 : projectVersionNode.getName() );
1458
1459        if ( artifactNode.hasProperty( JCR_LAST_MODIFIED ) )
1460        {
1461            artifact.setFileLastModified( artifactNode.getProperty( JCR_LAST_MODIFIED ).getDate().getTimeInMillis() );
1462        }
1463
1464        if ( artifactNode.hasProperty( "whenGathered" ) )
1465        {
1466            artifact.setWhenGathered( artifactNode.getProperty( "whenGathered" ).getDate().getTime() );
1467        }
1468
1469        if ( artifactNode.hasProperty( "size" ) )
1470        {
1471            artifact.setSize( artifactNode.getProperty( "size" ).getLong() );
1472        }
1473
1474        if ( artifactNode.hasProperty( "md5" ) )
1475        {
1476            artifact.setMd5( artifactNode.getProperty( "md5" ).getString() );
1477        }
1478
1479        if ( artifactNode.hasProperty( "sha1" ) )
1480        {
1481            artifact.setSha1( artifactNode.getProperty( "sha1" ).getString() );
1482        }
1483
1484        for ( Node n : JcrUtils.getChildNodes( artifactNode ) )
1485        {
1486            if ( n.isNodeType( FACET_NODE_TYPE ) )
1487            {
1488                String name = n.getName();
1489                MetadataFacetFactory factory = metadataFacetFactories.get( name );
1490                if ( factory == null )
1491                {
1492                    log.error( "Attempted to load unknown project version metadata facet: " + name );
1493                }
1494                else
1495                {
1496                    MetadataFacet facet = factory.createMetadataFacet();
1497                    Map<String, String> map = new HashMap<>();
1498                    for ( Property p : JcrUtils.getProperties( n ) )
1499                    {
1500                        String property = p.getName();
1501                        if ( !property.startsWith( "jcr:" ) )
1502                        {
1503                            map.put( property, p.getString() );
1504                        }
1505                    }
1506                    facet.fromProperties( map );
1507                    artifact.addFacet( facet );
1508                }
1509            }
1510        }
1511        return artifact;
1512    }
1513
1514    private static String getPropertyString( Node node, String name )
1515        throws RepositoryException
1516    {
1517        return node.hasProperty( name ) ? node.getProperty( name ).getString() : null;
1518    }
1519
1520    private Collection<String> getNodeNames( String path, String nodeType )
1521        throws MetadataResolutionException
1522    {
1523        List<String> names = new ArrayList<>();
1524
1525        try
1526        {
1527            Node root = getJcrSession().getRootNode();
1528
1529            Node nodeAtPath = root.getNode( path );
1530
1531            for ( Node node : JcrUtils.getChildNodes( nodeAtPath ) )
1532            {
1533                if ( node.isNodeType( nodeType ) )
1534                {
1535                    names.add( node.getName() );
1536                }
1537            }
1538        }
1539        catch ( PathNotFoundException e )
1540        {
1541            // ignore repo not found for now
1542        }
1543        catch ( RepositoryException e )
1544        {
1545            throw new MetadataResolutionException( e.getMessage(), e );
1546        }
1547
1548        return names;
1549    }
1550
1551    private static String getRepositoryPath( String repositoryId )
1552    {
1553        return "repositories/" + repositoryId;
1554    }
1555
1556    private static String getRepositoryContentPath( String repositoryId )
1557    {
1558        return getRepositoryPath( repositoryId ) + "/content/";
1559    }
1560
1561    private static String getFacetPath( String repositoryId, String facetId )
1562    {
1563        return getRepositoryPath( repositoryId ) + "/facets/" + facetId;
1564    }
1565
1566    private static String getNamespacePath( String repositoryId, String namespace )
1567    {
1568        return getRepositoryContentPath( repositoryId ) + namespace.replace( '.', '/' );
1569    }
1570
1571    private static String getProjectPath( String repositoryId, String namespace, String projectId )
1572    {
1573        return getNamespacePath( repositoryId, namespace ) + "/" + projectId;
1574    }
1575
1576    private static String getProjectVersionPath( String repositoryId, String namespace, String projectId,
1577                                                 String projectVersion )
1578    {
1579        return getProjectPath( repositoryId, namespace, projectId ) + "/" + projectVersion;
1580    }
1581
1582    private static String getArtifactPath( String repositoryId, String namespace, String projectId,
1583                                           String projectVersion, String id )
1584    {
1585        return getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ) + "/" + id;
1586    }
1587
1588    private Node getOrAddNodeByPath( Node baseNode, String name )
1589        throws RepositoryException
1590    {
1591        return getOrAddNodeByPath( baseNode, name, null );
1592    }
1593
1594    private Node getOrAddNodeByPath( Node baseNode, String name, String nodeType )
1595        throws RepositoryException
1596    {
1597        Node node = baseNode;
1598        for ( String n : name.split( "/" ) )
1599        {
1600            node = JcrUtils.getOrAddNode( node, n );
1601            if ( nodeType != null )
1602            {
1603                node.addMixin( nodeType );
1604            }
1605        }
1606        return node;
1607    }
1608
1609    private static String getFacetPath( String repositoryId, String facetId, String name )
1610    {
1611        return getFacetPath( repositoryId, facetId ) + "/" + name;
1612    }
1613
1614    private Node getOrAddRepositoryNode( String repositoryId )
1615        throws RepositoryException
1616    {
1617        Node root = getJcrSession().getRootNode();
1618        Node node = JcrUtils.getOrAddNode( root, "repositories" );
1619        node = JcrUtils.getOrAddNode( node, repositoryId );
1620        return node;
1621    }
1622
1623    private Node getOrAddRepositoryContentNode( String repositoryId )
1624        throws RepositoryException
1625    {
1626        Node node = getOrAddRepositoryNode( repositoryId );
1627        return JcrUtils.getOrAddNode( node, "content" );
1628    }
1629
1630    private Node getOrAddNamespaceNode( String repositoryId, String namespace )
1631        throws RepositoryException
1632    {
1633        Node repo = getOrAddRepositoryContentNode( repositoryId );
1634        return getOrAddNodeByPath( repo, namespace.replace( '.', '/' ), NAMESPACE_NODE_TYPE );
1635    }
1636
1637    private Node getOrAddProjectNode( String repositoryId, String namespace, String projectId )
1638        throws RepositoryException
1639    {
1640        Node namespaceNode = getOrAddNamespaceNode( repositoryId, namespace );
1641        Node node = JcrUtils.getOrAddNode( namespaceNode, projectId );
1642        node.addMixin( PROJECT_NODE_TYPE );
1643        return node;
1644    }
1645
1646    private Node getOrAddProjectVersionNode( String repositoryId, String namespace, String projectId,
1647                                             String projectVersion )
1648        throws RepositoryException
1649    {
1650        Node projectNode = getOrAddProjectNode( repositoryId, namespace, projectId );
1651        Node node = JcrUtils.getOrAddNode( projectNode, projectVersion );
1652        node.addMixin( PROJECT_VERSION_NODE_TYPE );
1653        return node;
1654    }
1655
1656    private Node getOrAddArtifactNode( String repositoryId, String namespace, String projectId, String projectVersion,
1657                                       String id )
1658        throws RepositoryException
1659    {
1660        Node versionNode = getOrAddProjectVersionNode( repositoryId, namespace, projectId, projectVersion );
1661        Node node = JcrUtils.getOrAddNode( versionNode, id );
1662        node.addMixin( ARTIFACT_NODE_TYPE );
1663        return node;
1664    }
1665
1666    private static Calendar createCalendar( Date time )
1667    {
1668        Calendar cal = Calendar.getInstance();
1669        cal.setTime( time );
1670        return cal;
1671    }
1672
1673    private String join( Collection<String> ids )
1674    {
1675        if ( ids != null && !ids.isEmpty() )
1676        {
1677            StringBuilder s = new StringBuilder();
1678            for ( String id : ids )
1679            {
1680                s.append( id );
1681                s.append( "," );
1682            }
1683            return s.substring( 0, s.length() - 1 );
1684        }
1685        return null;
1686    }
1687
1688    public Session getJcrSession()
1689        throws RepositoryException
1690    {
1691        if ( this.jcrSession == null || !this.jcrSession.isLive() )
1692        {
1693            jcrSession = repository.login( new SimpleCredentials( "admin", "admin".toCharArray() ) );
1694        }
1695        return this.jcrSession;
1696    }
1697}