`
ahuaxuan
  • 浏览: 640579 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

深入浅出jackrabbit之十三 查询之AST和QT

阅读更多
简介:在前面的文章中,ahuaxuan已经指出jackrabbit查询的主体流程,主体流程只能帮助大家稍微了解一下jackrabbit的query module,要深入理解还得深入探索主体流程中的每一步。在这篇文章中ahuaxuan将着重描述AST的生成和QT(querynode tree)的生成。


回顾上一篇文章,我们可以知道jackrabbit中的查询手段之一:xpath。下面我们还拿上一篇文章的xpath示例:
String queryPath2 =//hello/test3 [jcr:contains(@name, 'aa') and jcr:contains(@content, 'Maven') or @data1 = 'ahuaxuan'] order by jcr:score() descending, @name ascending
这句话的意思是:得到在world节点中包含Maven这个字符串的节点,并且按照jcr:score()降序,name这个property升序。和sql语句非常类似。

我们知道jackrabbit是通过lucene来执行查询任务,那么lucene是怎么知道这个语句中包含什么信息的呢。

要得到这个答案,我们不得不捡起我们的基础学科,编译原理。编译的目的是把一种语言翻译成另外一种语言,比如java的源代码需要编译成字节码,这个就是通过编译技术实现,很遗憾,字节码并不是机器语言,不能直接执行,所以字节码会被再次编译,生成机器语言。运行过程中,这些编译之后的代码也有可能再次被重新优化编译。所以天天使用java的同志们,还是要了解一下编译的作用。

有点偏题了,那么现在我们的目的是什么,就是把这个xpath语言翻译成另外一种语言。翻译成java中的数据结构,然后用户可以通过开发处理这些数据结构的代码来解析和使用这些数据结构,这里讲到的数据结构就是QT(其实QT也是一个语法树,只是这颗语法树能够让程序直接识别)。

在jackrabbit中,使用了javacc来生成AST,关于javacc的使用请google之,它的词法文件在源码中可以找到,XPath.jj。

AST在生成之后,是怎么转换成QT的呢?要回答这个问题,我们必须要理解一个设计模式,vistor模式

因为在AST转换成QT,以及从QT转成lucene的query对象时都使用了vistor,在asm中也是使用vistor,emma中也是使用vistor,看来它很火,口说无凭,有图为证:


从上图中可以看到在处理SimpleNode的时候使用了XPathVisitor,而在处理QueryNode的时候使用QueryNodeVistor.

一个简单的vistor模式如下:
package org.apache.jackrabbit.core.query.xpath;

public interface XPathVisitor
{
  public Object visit(SimpleNode node, Object data);
}

public class SimpleNode implements Node {
    protected Node parent;
    protected Node[] children;
    protected int id;
    protected XPath parser;

    /**
     * Accept the visitor. *
     */
    public Object jjtAccept(XPathVisitor visitor, Object data) {
        return visitor.visit(this, data);
    }

    /**
     * Accept the visitor. *
     */
    public Object childrenAccept(XPathVisitor visitor, Object data) {
        if (children != null) {
            for (int i = 0; i < children.length; ++i) {
                data = children[i].jjtAccept(visitor, data);
            }
        }
        return data;
    }
}


从这两个简单的类的关系中,我们可以看到如下事实:SimpleNode类接受一个Vistor,然后在vistor的vist方法中,会处理该SimpleNode,换句话说,一个domain对象会拿另外一个process对象来处理自己。

另外一个值得注意的是SimpleNode其实就是AST上的节点。下面一个主要工作就是处理AST上的节点,并且生成QueryNode Tree。

在visitor模式中,处理数据结构的主要逻辑在是visit方法,那么下面我们就来看看XPathVisitor实现类XPathQueryBuilder的visit方法,该方法中集中了AST转成QT的关键逻辑。代码如下:
public Object visit(SimpleNode node, Object data) {
        QueryNode queryNode = (QueryNode) data;
        switch (node.getId()) {
            case JJTXPATH2:
                queryNode = createPathQueryNode(node);
                break;
            case JJTROOT:
            case JJTROOTDESCENDANTS:
                if (queryNode instanceof PathQueryNode) {
                    ((PathQueryNode) queryNode).setAbsolute(true);
                } else {
                    exceptions.add(new InvalidQueryException(
                            "Unsupported root level query node: " + queryNode));
                }
                break;
            case JJTSTEPEXPR:
                ………………………..
        return queryNode;
    }


一段很长的代码,这段代码的主要作用是将AST,也就是SimpleNode组成的tree转换成QueryNodeTree, 原来节点的类型在simplenode中是以id来表示,而在转换之后的QueryNodeTree中,则是以QueryNode的类型来决定。

综上所述,在这个部分中,通过vistor的模式,不停的自顶向下遍历AST上的节点,同时根据节点的类型来生成QT上的节点。

这样AST遍历完成之后,我们也得到了我们需要的QT,接下来就是这么使用QT的问题了。
我们来仔细审视一下QT的结构,如下图所示(该QT对应的xpath语句是://hello/test3
[jcr:contains(@name, 'aa') and jcr:contains(@content, 'Maven') or @data1 = 'ahuaxuan'] order by jcr:score() descending, @name ascending
):



从上图看出,一条xpath语句最终被解析成一颗树,而这颗树代表着查询的条件,排序信息等等。
最重要的一个节点QueryRootNode,它连同它的children代表着一条xpath语句,这个时候,xpath语句的语义已经被清晰的划分开。接下来,就是QueryNode的Visitor上场了。我们来看看它的Visitor:
public interface QueryNodeVisitor {

    Object visit(QueryRootNode node, Object data);

    Object visit(OrQueryNode node, Object data);

    Object visit(AndQueryNode node, Object data);

    Object visit(NotQueryNode node, Object data);

    Object visit(ExactQueryNode node, Object data);

    Object visit(NodeTypeQueryNode node, Object data);

    Object visit(TextsearchQueryNode node, Object data);

    Object visit(PathQueryNode node, Object data);

    Object visit(LocationStepQueryNode node, Object data);

    Object visit(RelationQueryNode node, Object data);

    Object visit(OrderQueryNode node, Object data);

    Object visit(DerefQueryNode node, Object data);

    Object visit(PropertyFunctionQueryNode node, Object data);
}

它的实现类中需要定义每一种QueryNode的处理逻辑,显然和SimpleNode的XPathVisitor有一点不一样,因为在XPathVisitor中,是通过switch来决定如何处理node的,而这里变成了多个方法来处理,看上去更加直观,只是如果再加一个QueryNode的子类,那么接口就需要改动(从这一点来看vistor比较适合数据结构不易变更的场景,比如asm中,字节码结构一般是不会变更的,所以用visitor模式是比较适合的),不过总的来说,SimpleNode和XpathVisitor的关系就如同QueryNode和QueryNodeVisitor一样,连续两个关键步骤都使用了visitor模式。

看过接口之后,就是看它的主实现类,LuceneQueryBuilder,这个类相当重要,所有的query对象就是在这个类中构造出来的,很显然,构造query对象所需要的条件就藏在QT中。我们只要稍稍的查看一个方法,就可以得到证据:
public Object visit(QueryRootNode node, Object data) {
        BooleanQuery root = new BooleanQuery();

        Query wrapped = root;
        if (node.getLocationNode() != null) {
            wrapped = (Query) node.getLocationNode().accept(this, root);
        }

        return wrapped;
    }

显然,从这段代码里,开始了便利QT的旅程,因为QueryRootNode是QT的root,所以这里也是自顶向下遍历,比如,在创建了root之后,接着去处理QT上的LocationNode(也就是PathQueryNode节点),于是方法就跳到了:
public Object visit(PathQueryNode node, Object data) {
        Query context = null;
        LocationStepQueryNode[] steps = node.getPathSteps();
        if (steps.length > 0) {
            if (node.isAbsolute() && !steps[0].getIncludeDescendants()) {
                // eat up first step
                Name nameTest = steps[0].getNameTest();
                if (nameTest == null) {
                    // this is equivalent to the root node
                    context = new TermQuery(new Term(FieldNames.PARENT, ""));
                } else if (nameTest.getLocalName().length() == 0) {
                    // root node
                    context = new TermQuery(new Term(FieldNames.PARENT, ""));
                } else {
                    // then this is a node != the root node
                    // will never match anything!
                    String name = "";
                    try {
                        name = resolver.getJCRName(nameTest);
                    } catch (NamespaceException e) {
                        exceptions.add(e);
                    }
                    BooleanQuery and = new BooleanQuery();
                    and.add(new TermQuery(new Term(FieldNames.PARENT, "")), Occur.MUST);
                    and.add(new TermQuery(new Term(FieldNames.LABEL, name)), Occur.MUST);
                    context = and;
                }
                LocationStepQueryNode[] tmp = new LocationStepQueryNode[steps.length - 1];
                System.arraycopy(steps, 1, tmp, 0, steps.length - 1);
                steps = tmp;
            } else {
                // path is 1) relative or 2) descendant-or-self
                // use root node as context
                context = new TermQuery(new Term(FieldNames.PARENT, ""));
            }
        } else {
            exceptions.add(new InvalidQueryException("Number of location steps must be > 0"));
        }
        // loop over steps
        for (int i = 0; i < steps.length; i++) {
            context = (Query) steps[i].accept(this, context);
        }
        if (data instanceof BooleanQuery) {
            BooleanQuery constraint = (BooleanQuery) data;
            if (constraint.getClauses().length > 0) {
                constraint.add(context, Occur.MUST);
                context = constraint;
            }
        }
        return context;
    }


如此下去,整个QT会被遍历,并生成lucene的query对象,但是如果有同学也去读源码,那么可能会发现在处理Order这个语义时,visitor直接跳过了:
public Object visit(OrderQueryNode node, Object data) {
        return data;
    }


没错,它是故意的,因为order的语义在jackrabbit里被独立拿来出来,生成需要排序的field。

生成lucene的query对象之后,就可以名正言顺的调用下面这段代码了:
searcher.search(query, new Sort(sortFields))

其中的sortFields是在queryimpl的execute方法里独立拿出来,并生成了sortfields:
OrderQueryNode orderNode = root.getOrderNode();

        OrderQueryNode.OrderSpec[] orderSpecs;
        if (orderNode != null) {
            orderSpecs = orderNode.getOrderSpecs();
        } else {
            orderSpecs = new OrderQueryNode.OrderSpec[0];
        }
        Name[] orderProperties = new Name[orderSpecs.length];
        boolean[] ascSpecs = new boolean[orderSpecs.length];
        for (int i = 0; i < orderSpecs.length; i++) {
            orderProperties[i] = orderSpecs[i].getProperty();
            ascSpecs[i] = orderSpecs[i].isAscending();
        }


那么说到这里,jackrabbit在查询时候的一些关键流程就告一段落了,唯一不足的是本文只是描述了一条xpath语句从字符串到QT,再到lucene的query对象这样一个概要流程,在下一篇文章中,我们将会来着重看一下lucene是怎么使用这颗QT的。

to be continue

  • 大小: 74.5 KB
  • 大小: 47.1 KB
2
0
分享到:
评论
1 楼 tom&jerry 2014-07-01  
大神,请教一个问题,按名称排序为何无效,用的2.4.3
XPATH:
//element(*,nt:file) order by @name ascending
SQL:
SELECT * from nt:file order by jcr:name asc

两个都无效。请大神指教。

相关推荐

    深入浅出 jackrabbit 1

    《深入浅出 Jackrabbit 1》 Jackrabbit 是一个开源的、实现了 Java Content Repository (JCR) API 的内容管理系统,它允许程序通过统一的方式访问、存储和管理各种数据,包括文本、图像、视频等多媒体信息。这篇...

    JackRabbit 学习参考资料总汇

    JackRabbit学习参考资料总汇涉及了深入浅出的JackRabbit内容仓库API的学习,内容涉及多个专题,整个学习资料是PDF文档格式。从标签来看,这份资料主要涉及JackRabbit以及JCR(Java Content Repository)的内容仓库...

    jackrabbit最全入门教程

    这个“jackrabbit最全入门教程”将会带你深入理解这个强大的内容管理解决方案。 首先,我们需要了解什么是JCR。JCR提供了一种统一的方式来访问和管理数字内容,无论这些内容是文档、图像、视频还是其他形式的数据。...

    Apache Jackrabbit入门

    Apache Jackrabbit 是一个开源的Java Content Repository (JCR)实现,它是Content Management Systems (CMS)的核心技术之一。JCR是Java Specification Request (JSR) 170和JSR 283定义的标准,旨在提供一个统一的...

    Jackrabbit入门实例

    Apache Jackrabbit是一个开源的、实现了Java Content Repository (JCR) API的内容管理系统,它允许开发者创建、管理和查询结构化和非结构化的数据。JCR是Java社区制定的一个标准,旨在为各种应用程序提供统一的数据...

    jackrabbit教程

    6. **Query(查询)**: Jackrabbit 支持基于 SQL-like 查询语言 JCR-SQL2 和XPath 的查询。`QueryManager` 可以创建、执行和管理查询,返回 `QueryResult` 对象,从中可以获取结果集。 7. **UUID(唯一标识符)**: ...

    Jackrabbit API

    通过阅读这些文档,开发者可以学习如何初始化Repository,创建和管理Session,操作Nodes和Properties,执行查询,以及如何利用Jackrabbit提供的其他高级特性。 在实际应用中,Apache Jackrabbit常被用作企业级内容...

    jackrabbit内容仓库的实例(两个工程)

    - 其次,通过官方Demo学习基本的API用法,如节点操作和查询。 - 接着,深入研究IBM的示例,理解更复杂的功能如版本控制和权限管理。 - 最后,尝试自己构建一个简单的应用场景,如搭建一个小型文档管理系统。 6. ...

    jackrabbit

    ### Jackrabbit 在项目实施中的常见问题与解决方案 #### 一、Jackrabbit简介 Jackrabbit 是一个完全用 Java 编写的 JCR(Java Content Repository)实现,它可以作为一个独立的服务运行,也可以嵌入到更大的应用...

    jackrabbit2.6

    它允许开发人员创建、修改和查询存储在内容仓库中的数据,这些数据可以是文档、图片、视频等各种形式的数字资产。 2. **内容管理**:Apache Jackrabbit 提供了一套全面的内容管理功能,包括版本控制、权限管理、...

    查看jackrabbit仓库的小工具

    标题中的“查看jackrabbit仓库的小工具”指的是一个用于观察和管理Apache Jackrabbit仓库的实用程序。Jackrabbit是Java Content Repository (JCR) API的一个开源实现,它提供了一个内容管理系统(CMS)的基础框架,...

    jackrabbit-standalone-1.5.6.jar jackrabbit 开发包

    jackrabbit 1.5.6 jar

    Jackrabbit Oak 是一个可扩展的高性能分层内容 存储库,旨在用作现代世界级的基础 网站和其他要求苛刻的内容应用程序

    最后,Jackrabbit Oak的持续发展和活跃的社区也是其优势之一。通过不断改进和修复问题,项目确保了软件的稳定性和可靠性,同时也提供了丰富的文档和示例,帮助开发者快速上手。 综上所述,Jackrabbit Oak是一个强大...

    jackrabbit-webdav-2.3.2.src.zip

    通过深入理解和使用"jackrabbit-webdav-2.3.2.src.zip"中的源代码,开发者不仅可以学习WebDAV协议的工作原理,还能了解如何在Android环境中实现高效稳定的WebDAV客户端功能。此外,对于想要对Jackrabbit进行定制化...

    jackrabbit-standalone

    jackrabbit-standalone-1.6.5.jar是webDav的支持jar包。

    jackrabbit-webdav-2.7.1.zip

    4. SolrJ:Solr的Java客户端,简化了与Solr服务器的交互,如索引构建和查询操作。 5. Suller:SolrJ的扩展,提供了额外的功能和更好的文档支持,便于开发者集成Solr。 6. 开源软件:开源项目的优点包括透明度、社区...

    jackrabbit-webdav-2.1.0.jar

    jackrabbit-webdav-2.1.0.jar 具体用法可以网上查找

    jackrabbit-jcr-commons-2.5.0.zip

    Jackrabbit JCR Commons库不仅仅包括sysprop和sysenv,还可能包含其他有用的功能,如节点类型注册、查询工具、数据转换器等。这些工具可以帮助开发者更高效地进行内容存储和检索,进行版本控制,以及处理复杂的JCR...

Global site tag (gtag.js) - Google Analytics