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

深入浅出 jackrabbit 2 索引概览

阅读更多
任何一个数据库都离不开一个技术----索引技术,jackrabbit作为内容仓库的开源实现亦不能例外,从前面的文章中我们已经清楚的知道jackrabbit使用lucene来进行索引任务和查询任务。而查询正是基于索引,所以在本文中,ahuaxuan将和大家一起来学习jackrabbit中建立索引的方法。

事实上,jackrabbit中建立索引的流程是比较冗长和复杂的,如同query一样,在本文中ahuaxuan将把注意里放在整体流程上,而流程的每一步可以说都是比较复杂的,这些复杂的实现细节将会放在后面的文章中阐述,本文的主要目的是了解jackrabbit中创建流程的一般过程。

首先,我们需要寻找一个入口点,一个接口,这个接口是建立索引的入口,任何人需要建立索引的功能必须要调用这个接口,那么这个接口在哪里呢,通过对源代码的类结构分析,ahuaxuan得出来以下的类图,从图中我们可以看出,建立索引的api其实是一个监听器(监听器模式来源于观察者模式,两者并没有什么本质上的不同),任何人需要建立索引只要发事件给对应的监听器,那么监听器就会执行建立索引的流程(同时请注意下图中标注出来创建索引的两个入口,一个是Update类,还有一个是ClustorNode):

上图即描述了建立索引前的主要逻辑,ObervationDispatcher类,当ClustorNode中发现其他节点有save,update,delete或者本node中有update操作,那么ObervationDispatcher将会把这种事件分发给对这类事件感兴趣的类,那么显然,对这类event最感兴趣的便是图中的SearchManager,也就是说SearchManager是索引的入口。而且从图中所示的类结构图可知,SearchManager就是一个EventListener。

既然知道了index的入口,不妨让我们来看一下SearchManager中索引的入口方法onEvent()
public void onEvent(EventIterator events) {
    	 // nodes that need to be removed from the index.
        final Set removedNodes = new HashSet();
        // nodes that need to be added to the index.
        final Map addedNodes = new HashMap();
        // property events
        List propEvents = new ArrayList();

        while (events.hasNext()) {
            EventImpl e = (EventImpl) events.nextEvent();
            if (!isExcluded(e)) {
                long type = e.getType();
                if (type == Event.NODE_ADDED) {
                    addedNodes.put(e.getChildId(), e);
                    // quick'n dirty fix for JCR-905
                    if (e.isExternal()) {
                        removedNodes.add(e.getChildId());
                    }
                } else if (type == Event.NODE_REMOVED) {
                    removedNodes.add(e.getChildId());
                } else {
                    propEvents.add(e);
                }
            }
        }

        // sort out property events
        for (int i = 0; i < propEvents.size(); i++) {
            EventImpl e = (EventImpl) propEvents.get(i);
            NodeId nodeId = e.getParentId();
            if (e.getType() == Event.PROPERTY_ADDED) {
                if (addedNodes.put(nodeId, e) == null) {
                    // only property added
                    // need to re-index
                    removedNodes.add(nodeId);
                } else {
                    // the node where this prop belongs to is also new
                }
            } else if (e.getType() == Event.PROPERTY_CHANGED) {
                // need to re-index
                addedNodes.put(nodeId, e);
                removedNodes.add(nodeId);
            } else {
                // property removed event is only generated when node still exists
                addedNodes.put(nodeId, e);
                removedNodes.add(nodeId);
            }
        }

        NodeStateIterator addedStates = new NodeStateIterator() {
//创建需要add的node iterator
            …………….
        };
        NodeIdIterator removedIds = new NodeIdIterator() {
			……………………
        };

        if (removedNodes.size() > 0 || addedNodes.size() > 0) {
            try {
                handler.updateNodes(removedIds, addedStates);
            } catch (RepositoryException e) {
                log.error("Error indexing node.", e);
            } catch (IOException e) {
                log.error("Error indexing node.", e);
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("onEvent: indexing finished in "
                    + String.valueOf(System.currentTimeMillis() - time)
                    + " ms.");
        }
    }

这段代码很简单,就是把传进来的event对象转变成两个iterator,一个是需要remove的node iterator,另外一个是需要add的node iterator。由于lucene不支持索引的更新,所以对于lucene来说,save,update,delete再本质上就是delete和save。Update被拆成了先delete后save两个环节,也就是说如果update一个节点,那么这个节点的id既会出现在removedIds里,也会出现在addedStates里。

Onevent方法的最终走向是handler.updateNodes(),所以我们也需要进入到updateNodes方法中。
跟随着代码,我们到达了updateNodes方法:
public void updateNodes(NodeIdIterator remove, NodeStateIterator add)
            throws RepositoryException, IOException {
        checkOpen();
        final Map aggregateRoots = new HashMap();
        final Set removedNodeIds = new HashSet();
        final Set addedNodeIds = new HashSet();
        index.update(new AbstractIteratorDecorator(remove) {
            public Object next() {
                NodeId nodeId = (NodeId) super.next();
                removedNodeIds.add(nodeId);
                return nodeId.getUUID();
            }
        }, new AbstractIteratorDecorator(add) {
            public Object next() {
                NodeState state = (NodeState) super.next();
                if (state == null) {
                    return null;
                }
                addedNodeIds.add(state.getNodeId());
                removedNodeIds.remove(state.getNodeId());
                Document doc = null;
                try {
						//非常重要的一个方法,但是它会在那里执行呢?
                    doc = createDocument(state, getNamespaceMappings(),
                            index.getIndexFormatVersion());
                    retrieveAggregateRoot(state, aggregateRoots);
                } catch (RepositoryException e) {
                    log.warn("Exception while creating document for node: "
                            + state.getNodeId() + ": " + e.toString());
                }
                return doc;
            }
        });
}

这段代码很简单,把原来的两个iterator作了一个decorator,也就是说在原有的iterator的功能上又添加了一些功能,代码虽然很简单,但是却很重要,尤其是在处理那些需要add的node上,在第二个decorator中,有一个next方法,这个方法包含着创建index的主要逻辑:创建属于这个node的document对象。为node创建document是索引过程中第一个最重要的环节,这意味着,我们需要掌握一个node中,哪些属性被建立索引,建了那些索引,这关乎到查询的范围,如果一个property不能被索引,那么在查询的时候就不能以它作为依据。
所以请大家记住createDocument这个方法,在后文中,我们会详细分析一个node有哪些field会被索引进文件。
所以到目前为止,我们已经得知了index流程的前面三个步骤
1.	调用index流程的两个入口
2.	监听器解析event,并生成两个iterator
3.	Decorate两个iterator,目的是override其next方法,并将在next方法中创建document。


接着,我们进入到index.update()方法,index对象是MultiIndex类的实例,对于update方法来说,无论用什么言语来描述它的重要性都不为过,因为在这个方法中,我们会看到jackrabbit建立索引的核心流程,但是我们需要知道的是,它并不包含任何细节,细节隐藏在流程的其他对象中:
//请注意方法前的synchronized,这个同步非常重要,因为他能控制一个非//常重要的流程的顺序执行。
synchronized void update(Iterator remove, Iterator add) throws IOException {
        synchronized (updateMonitor) {
            updateInProgress = true;
        }
        try {
            long transactionId = nextTransactionId++;
            executeAndLog(new Start(transactionId));

            boolean flush = false;
			//从索引中删除需要删除的node
            while (remove.hasNext()) {
                executeAndLog(new DeleteNode(transactionId, (UUID) remove.next()));
            }
//将需要增加的node的document加入到索引中去,这里的next
//方法就是上一个方法中定义的decorator中的next()方法,创建//document就是在此时
            while (add.hasNext()) {
                Document doc = (Document) add.next();
                if (doc != null) {
                    executeAndLog(new AddNode(transactionId, doc));
                    // commit volatile index if needed
                    flush |= checkVolatileCommit();
                }
            }
            executeAndLog(new Commit(transactionId));

            // flush whole index when volatile index has been commited.
            if (flush) {
                flush();
            }
        } finally {
            synchronized (updateMonitor) {
                updateInProgress = false;
                updateMonitor.notifyAll();
                if (multiReader != null) {
                    multiReader.close();
                    multiReader = null;
                }
            }
        }
    }


从代码的注释,我们可以看到这段代码的主要功能,就是把该删的删掉,该加的加进去,然后该flush就flush,最后方法退出。那么代码的主要逻辑在哪里呢?
在DeleteNode和AddNode类中,还有就是在flush方法中。

看上去很简单了,但是这只是看上去简单而已,就到目前为止,这个以上流程的主要工作还停留在如何准备索引数据:即document,接着就是短短几行代码来描述如何使用document,
不过再继续下去之前,我们得回头看看createDocument中究竟有哪些field被创建,究竟node中有哪些信息被放到索引中了呢,同时我们将会看到如何通过修改field的属性来降低高亮功能所带来的时间消耗,to be continue。



  • 大小: 73.3 KB
分享到:
评论
2 楼 ahuaxuan 2009-07-14  
lxiaodao 写道
很好的文章,分析很到位,有机会多交流。
我有个问题:
    在对节点的保存/更新/删除时候,使用观察者模式的优点到底是什么?因为我觉得如果象hibernate那样session.save(Node)操作更好。
    如果有时间,请你解一下我的困惑。
   


哦,这个是因为当一个节点被删除或者增加时有一些其他的的模块需要得到通知,所以用了观察者模式,那么需要得到通知的模块作为观察者,只要节点有变化就能收到通知,当有新的模块需要得到这些信息的时候,这要写一个观察者注册进去就行了。其中主要的一个观察者就是SearchManager,因为节点的变化它必须要能反映到索引文件中去。
而节点的crud逻辑本身并不涉及到观察者,只是最后在提交的node的改变的时候通知一下观察者。
1 楼 lxiaodao 2009-07-14  
很好的文章,分析很到位,有机会多交流。
我有个问题:
    在对节点的保存/更新/删除时候,使用观察者模式的优点到底是什么?因为我觉得如果象hibernate那样session.save(Node)操作更好。
    如果有时间,请你解一下我的困惑。
   

相关推荐

    深入浅出 jackrabbit 1

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

    jackrabbit最全入门教程

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

    JackRabbit 学习参考资料总汇

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

    jackrabbit2.6

    2. **内容管理**:Apache Jackrabbit 提供了一套全面的内容管理功能,包括版本控制、权限管理、工作流、节点类型定义等。这些功能使得组织和管理大量复杂内容变得容易,尤其适用于企业级应用。 3. **性能与可扩展性...

    Apache Jackrabbit入门

    在本文中,我们将深入探讨Apache Jackrabbit的基础知识,以及如何开始使用它。 一、JCR和Apache Jackrabbit的概念 1. JCR:JCR为存储和检索非结构化信息提供了一个模型和API。它允许开发者创建可以跨各种存储后端...

    jackrabbit

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

    Jackrabbit入门实例

    Apache Jackrabbit是一个开源的、实现了Java Content ...总之,这个"Jackrabbit入门实例"是学习和探索JCR和Jackrabbit的好起点,它涵盖了基本的操作和概念,帮助你快速上手并深入了解这个强大的内容管理系统。

    jackrabbit教程

    Apache Jackrabbit 是一个...对于开发人员来说,理解这些功能以及如何将 Jackrabbit 集成到现有应用中是深入学习的关键部分。通过实践示例代码和探索 Jackrabbit API 文档,你可以逐步掌握这个强大的内容管理系统框架。

    Jackrabbit API

    Apache Jackrabbit API 是一个强大的内容管理系统(CMS)的核心组件,它是Apache Software Foundation 开发的Java Content Repository (JCR) 的实现。JCR 是一个标准,它定义了一个用于存储、管理和检索结构化内容的...

    jackrabbit-standalone-1.5.6.jar jackrabbit 开发包

    jackrabbit 1.5.6 jar

    jackrabbit-standalone

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

    查看jackrabbit仓库的小工具

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

    jackrabbit-webdav-2.3.2.src.zip

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

    jackrabbit-webdav-2.7.1.zip

    标题中的"jackrabbit-webdav-2.7.1.zip"指的是Apache Jackrabbit的一个特定版本——2.7.1的WebDAV模块的压缩包。Apache Jackrabbit是Java内容存储库(Content Repository)的一个实现,它遵循JCR(Java Content ...

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

    这两个项目将帮助我们深入理解和快速入门Jackrabbit的使用。 1. Jackrabbit核心概念: - JCR:JSR 170定义了内容存储的标准接口,使得应用程序可以透明地访问和操作不同类型的存储系统。 - Node:在JCR中,内容被...

    jackrabbit jar包

    jackrabbit开发用jar包,jackrabbit是基于Lucene的一种站内搜索技术,它用xml文件为他的元数据,自动穿件索引,使用xpath或者xquery的查询方法。

    jazz2:·Jazz²复活:Jazz Jackrabbit 2的开源实现

    开源Jazz Jackrabbit 2重新实现由带给您介绍Jazz²Resurrection是对1998年发行的Jazz Jackrabbit 2游戏的重新实现。支持该游戏的各种版本(共享软件演示,Holiday Hare '98,The Secret Files和Christmas Chronicles...

    jackrabbit-trunk.rar

    《深入探索Jackrabbit-Trunk源码》 Jackrabbit-Trunk,这个名字源于Apache Jackrabbit项目,是这个开源内容管理系统(CMS)的核心部分。Jackrabbit是一个完全实现了JCR(Java Content Repository,Java内容仓库)...

Global site tag (gtag.js) - Google Analytics