使用 OmniFind 扩展功能搜索 Spool 文件和 IFS 流文件

本文介绍如何使用 DB2 for i 的 OmniFind Text Search Server 来为 spool file( 假脱机文件 ) 和 IFS 流文件建立索引,并对它们进行搜索。与已经被广泛使用的、基于 Web 搜索引擎的搜索相类似,此项功能提供了对 IBM i 对象的搜索能力。

Nick Lawrence, 顾问软件工程师, IBM

Nick Lawrence 的照片Nick Lawrence 从事了 12 年 DB2 for i 工作,他的工作职责主要包括针对 DB2 和 SQL/XML 的全文搜索。



Jian Li, 高级软件工程师, IBM

Jian Li 的照片Jian Li 是 CSTL 的一名高级软件工程师。他从事了大约 5 年 DB2 for i 工作。最近几年,他一直从事 OmniFind Text Search Server for DB2 for i 开发。他现在还在从事针对 IBM i 的 MySQL 存储引擎的开发。



2011 年 11 月 24 日

概述

IBM DB2 for i 的 OmniFind Text Search Server 是一个没有任何附加费用的产品,它使 IBM i 开发人员能够对存储于 DB2 的列 (column) 中的文本文件(包括富文本文件如 Microsoft Word, PDF, XML, 等等)建立索引,并对其进行搜索。例如,我有一个 CLOB 列,它存储了一些小故事,现在我想把该列中包含“big bad wolves”的所有行(row)都找出来,我可以使用内嵌的 SQL 函数来找到它们。甚至如果该列中实际包含的文字是”the big bad wolf”,该行也能出现在搜索结果中。此外,我还可以对匹配到的行按相关度进行排序,使相关度最高的文档出现在结果集的最前面。

搜索 DB2 列中的文本数据一直是一个很有用的功能,但系统上被关注的文本数据并非都存储在 DB2 中。比如,您可能需要搜索存储在一个输出队列(Output Queue)中的 spool 文件中的报告集;或者,假设您想搜索包含 PDF 数据的 IFS 流文件。幸运的是,从 IBM i 7.1 开始,就有一个解决方案允许我们索引和搜索与这些 IBM i 对象相关的文本数据。

如要获取关于 OmniFind Text Search Server 的更完整的概述,请参阅这份白皮书:(http://www.ibm.com/partnerworld/wps/servlet/ContentHandler/whitepaper/i/omnifind/search)。本文主要关注搜索 spool 文件和 IFS 流文件的新增强功能。


软件要求

若要使用本文中描述的功能,用户需要订购和安装 OmniFind Text Search Server V1R2 产品 (5733-OMF) 并安装 IBM i 7.1 PTF SI40272。一般情况下,加载该 PTF 的同时,最好在系统上安装最新的 IBM Database Group PTF for IBM i 7.1。另外,OmniFind 产品还需要在系统上安装其他几个产品。OmniFind 参考手册中列出了这些必要的软件产品。

(http://publib.boulder.ibm.com/infocenter/iseries/v7r1m0/index.jsp?topic=/rzash/omniz_sysreqs.htm)


支持的对象类型和文本属性

IBM i 的对象通常含有多个文本类型的属性。例如,一个输出队列带有一个相关的文本描述,还包含一些其中含有文本内容的 spool 文件。为了能够使 IBM 实现对不同的对象和文本信息的扩充支持,我们选择同时使用对象及其一个包含文本内容的属性来标识一个文本。例如,输出队列 NICK/QUEUE1 中的一个特定 spool 文件的数据,或者流文件 /home/nick/file1.txt 中的数据。

输出队列中的 spool 文件

含有 SNA 字符流 (SCS) 数据的 spool 文件包含 EBCDIC 编码的文本数据,用于打印机输出。SCS 格式的 spool 文件通常用于存储输出队列中的报告和作业日志,以便应用程序稍后处理它们。

尽管 spool 文件存在于输出队列中,但我们通常使用其他标准来选取它们。例如,WRKSPLF(work spool file)命令使用文件名、作业、spool 文件编号、创建系统和创建时间戳来标识 spool 文件。

OmniFind 提供了几种方法来确定要索引哪些 SCS spool 文件,这些方法是基于其它系统 API 和 CL 命令所使用的选取准则的。

SCS 格式以外的 spool 文件比较少见,也很难从中提取文本,因此 OmniFind 现在不支持它们。

关于 spool 文件的信息可以在 infocenter(http://publib.boulder.ibm.com/infocenter/iseries/v7r1m0/index.jsp?topic=/rzata/rzatakickoff.htm) 找到。

IFS 流文件

Integrated File System(IFS) 提供一套层级文件系统,跟 UNIX® 环境使用的文件系统类似。每个文件系统都有自己的独特属性,比如大小写敏感性和支持的对象类型。

OmniFind 支持索引流文件对象中的文本数据,它使用 IFS 路径来标识文件。一个流文件对象 (*STMF) 是一个可随机访问的字节序列,系统不对它们施加任何额外记录结构。流文件的一个更简单的定义就是 PC 文件或 UNIX® 文件。

包含纯文本或富文本的流文件是可以被索引和搜索的。前面我们已经提到,富文本的例子有 PDF、PowerPoint、Lotus WordPro、Microsoft Word 等。

很重要一点是要理解流文件和数据库文件或源物理文件是不同的,因为数据库和源物理文件是面向记录的,而流文件是面向字节的。目前,OmniFind 不支持使用 IFS 路径来索引流文件以外的对象类型。

IFS 给 IBM i 上的所有信息提供了一种集成结构。“IFS 文件”是一种常见的错误称谓。由于 IFS 涵盖一切文件类型,而系统上有许多种不同的文件类型,因此 “IFS 文件”没有多少意义。大部分时候使用这个称谓时,其真正含义通常是指使用 IFS 来访问的流文件。我们的新程序和文档通常同时使用术语“IFS”和“流文件”来避免混淆。

有关流文件的更多信息的更多信息可以在 infocenter(http://publib.boulder.ibm.com/infocenter/iseries/v7r1m0/index.jsp?topic=/ifs/rzaaxstmfile.htm) 找到。


存储过程和结果集

OmniFind 新增的系统管理功能和搜索功能是以 SQL 存储过程方式提供的。在设计接口上有一点是非常重要的,就是这些接口既很容易被应用程序调用,又具有足够的灵活性,能够使 IBM 可以在将来支持其他对象类型。由于许多 IBM i 开发人员都熟悉存储过程,因此 SQL 过程调用自然成为一个解决方案。

SQL 结果集为处理搜索结果提供了一种方便的机制。执行搜索时,搜索过程向应用程序返回一个包含搜索结果的 SQL 结果集。使用结果集允许 DB2 高效地替应用程序管理存储,使应用程序可以使用现有的 DB2 接口,如 JDBC、ODBC 和嵌入式 SQL,来检索和处理结果。

图 1. 搜索过程结果集
图 1. 搜索过程结果集

在开始索引和搜索之前,我们需要创建一个文本搜索 collection。文本搜索 collection 是一个 SQL schema,它包含一些表,用于跟踪已索引的对象;它还包含一些 SQL 过程,用于管理和搜索索引。

创建一个文本搜索 collection 是通过调用 SYSPROC.SYSTS_CRTCOL 存储过程来实现的。

 CALL SYSPROC.SYSTS_CRTCOL( ‘ COLLECTION_FOR_NICK ’ );

上述调用将会在系统中创建一个名为‘ COLLECTION_FOR_NICK ’的 SQL schema。这个 schema 包含与文本搜索 collection 有关的所有 DB2 对象,其中包括 catalog 和系统管理存储过程。文本搜索索引数据存储在 DB2 之外的集成文件系统中,与常规 DB2 文本搜索索引非常类似。

图 2. 文本搜索集合
图 2. 文本搜索集合

创建 collection 的存储过程有几个令人关注的选项,它们借鉴自用于创建 DB2 文本搜索索引的选项。这个存储过程的接口允许我们配置更新频率、语言、文本格式和 CCSID。这些选项的语法在 OmniFind 扩展功能用户指南 有详细描述。

举个例子,如果我想创建一个 collection 且让它在每天晚上 12 点更新一次,我可以按下面的方法创建我的文本搜索 collection:

 CALL SYSPROC.SYSTS_CRTCOL( ‘ COLLECTION_FOR_NICK ’ , 
‘ UPDATE FREQUENCEY D(*) H(0) M(0) ’ );

所有用于管理这个 collection 的存储过程都创建在这个 SQL schema 中。这样就可以向其他用户授权,以便他们能够在该 collection 上搜索,或者更新。

 SET CURRENT SCHEMA COLLECTION_FOR_NICK; 
 GRANT EXECUTE ON PROCEDURE SEARCH(VARCHAR) TO DILBERT;

设置当前 collection 的路径

由于系统管理存储过程创建在文本搜索 collection 中(创建和删除 collection 的存储过程除外,它们位于库 SYSPROC 下),因此我们可以使用 SQL 路径指定哪个文本搜索 collection 是我们的工作 collection。

 SET CURRENT PATH COLLECTION_FOR_NICK;

这样就避免了每个存储过程调用都需要指定 schema 的麻烦。为简单起见,本文中的其他例子都假定把路径设置为上面的值。


添加对象集

创建完文本搜索 collection 之后,OmniFind 需要知道哪些对象要被索引。我们用对象集来定义一组对象,更新过程将对这组对象创建索引。

OmniFind 扩展功能支持两种对象集类型:

  • 输出队列中的 spool 文件
  • 特定 IFS 目录中的 IFS 流文件

添加完对象集并不意味着对该对象集中的文本数据进行了索引更新,OmniFind 是在下一次更新操作时来更新这些对象中的文本索引的。更新索引可以是用户调用 UPDATE 存储过程来进行,或者由定时运行的更新计划来执行。

添加 spool 文件对象集

我们在创建文本搜索 collection 时还创建了 ADD_SPLF_OBJECT_SET 存储过程,以便添加 spool 文件对象集。实际上,ADD_SPLF_OBJECT_SET 存储过程有几种不同的版本,我们可以使用任意一个版本来添加要被索引的 spool 文件。

例如,下面的过程调用将把输出队列 NTL/MYOUTQ 中的所有 spool 文件添加到一个 spool 文件对象集:

 CALL ADD_SPLF_OBJECT_SET( ‘ NTL ’ , ‘ MYOUTQ ’ );

下面的调用与上面的稍有差别,它把 NTL 拥有的所有 spool 文件添加到一个对象集:

 CALL ADD_SPLF_OBJECT_SET( ‘’ , ‘’ , ‘ NTL ’ );

在上面的例子中,输出队列库和队列名称使用了空字符串,表明将对所有输出队列中的 spool 文件进行索引。一个 collection 完全可以拥有多个对象集,而且多个对象集中也可以包含相同的对象。如果使用包含所有参数的过程调用,我们可以创建出更复杂的例子来。

下面的例子把所有使用用户数据‘ MYAPP ’ , 并于 2010 年创建的 spool 文件添加到一个对象集:

 CALL ADD_SPLF_OBJECT_SET( ‘’ , 	          -- 库 
                         ‘’ , 	          -- 队列名
                         ‘’ , 	          -- 用户名
                         ‘’ ,              -- 作业名
                         ‘’ , 		  -- 作业用户
                        '', 	          -- 作业号
                        'MYAPP',          -- 用户数据
                 '2010-01-01T00:00:00',   -- 开始时间
                 '2011-01-01T00:00:00' 	  -- 结束时间
 );

ADD_SPLF_OBJECT_SET 的语法和参数在 OmniFind 扩展功能用户指南 中有完整描述。

添加 IFS 流文件对象集

ADD_IFS_STMF_OBJECT_SET 存储过程用于添加 IFS 流文件到一个对象集。假定我想为我的主目录‘ /home/ntl ’中的流文件添加到一个对象集。

 CALL ADD_IFS_STMF_OBJECT_SET( ‘ /home/ntl ’ );

如果其中有些文件是富文本文件(Word、Powerpoint、PDF 等),那么我应该在 创建 collection 时使用 FORMAT INSO 选项。

 CALL SYSPROC.SYSTS_CRTCOL( ‘ COLLECTION_FOR_NICK ’ , 
                          ‘ FORMAT INSO ’ );

Format INSO 意味着更新过程将分析文档 (INSide Out), 以确定正被索引的文档数据是那种类型。这个额外的处理会减慢索引过程,但能提供更多的灵活性。

OmniFind 并不隐式地包含对象集中的子目录,但可以将任意数量的目录添加到不同对象集中。

一个 collection 中可以同时拥有 spool 文件对象集和 IFS 流文件对象集。搜索返回的结果集中包含有对象类型信息,应用程序可以根据对象类型对结果进行过滤。


更新 collection

Collection 中的对象集只有在执行更新时才会被索引。更新过程可以是一个调用 SYSPROC.SYSTS_CRTCOL 配置的定时更新,也可以是通过调用 UPDATE 存储过程进行的手动更新。

 CALL UPDATE;

更新处理过程会确定系统上的哪些对象是新添加的对象或是已经被修改了,然后索引那些对象的文本数据。初始更新之后是增量更新,那些已经被索引且没有被修改的对象不会被再次索引。每次更新之初都会花点时间来确定哪些对象已被创建、删除或修改。

UPDATE 操作完成之后,就可以搜索 collection 了。


搜索 collection

搜索 collection 通过调用 SEARCH 存储过程来完成。搜索词的表述方式与我们大多数人熟悉的 Web 搜索语法非常相似。对于那些需要高级搜索功能的程序员来说,IBM InfoCenter 描述了一些高级功能,但基本语法是比较直观的。

 CALL SEARCH( ‘ database OR DB2 ’ );

这个过程调用将会向客户端应用程序返回一个本次搜索的结果集,相关度最高的结果出现在结果集的最前面。

如果从 IBM System i Navigator 的 Run SQL Scripts 选项中调用 SEARCH,我们可以很轻松地查看结果集,它显示为窗口底部上的一个标签。

图 3. Navigator 输出
图 3. Navigator 输出

应用程序可以通过任何支持结果集的 SQL 接口(嵌入式 SQL、JDBC 等)来访问结果集。本文中提供了一个 示例 程序,展示如何使用 JDBC 连接到数据库,执行一个搜索操作,然后使用检索到的数据定位对象。

对象信息

结果集中最令人关注的列是 OBJINFOR,它含有被索引的对象的位置。该列的数据类型为 XML,因为 XML 不仅结构灵活,而且有大量的工具和解析器可用于处理 XML 数据。

XML 是可被人直接阅读的。例如,一个匹配的 spool 文件位置信息可能如列表 1 所示。

列表 1. spool 文件对象信息
 <Spool_File 
   xmlns=”http://www.ibm.com/xmlns/prod/db2textsearch/obj1”> 
 <job_name>QPADEV000C</job_name> 
 <job_user_name>USERA</job_user_name> 
 <job_number>009907</job_number> 
 <spool_file_name>DSXSVRALS</spool_file_name> 
 <spool_file_number>1</spool_file_number> 
 <job_system_name>ZD21BP1</job_system_name> 
 <create_date>1081027</create_date> 
 <create_time>035554</create_time> 
 </Spool_File>

一个典型的 IFS 流文件位置信息可能如列表 2 所示。

列表 2. 流文件对象信息
 <Stream_File 
      xmlns=”http://www.ibm.com/xmlns/prod/db2textsearch/obj1”> 
            <file_path>/home/usera/a.xml</file_path> 
 </Stream_File>

处理结果集

结果集中可以同时包含 spool 文件位置信息行和流文件位置信息行。OmniFind 并没有要求将搜索对象局限在一种特定对象类型。

DB2 for i 并没有提供内置的 SQL XMLTABLE 表函数,以通过 XPath 表达式将 XML 数据转换为关系型数据。但是,针对宿主语言的 XML 数据解析和处理工具是非常多的。我们的 示例 演示了用 Java 处理 XML 数据的一种方法。

结果集还包含其他一些列,这些信息也很有用。

  • 修改时间 (MODIFY_TIME)
  • 对象类型 (OBJTYPE)
  • 对象属性 (OBJATTR)
  • 所在的对象库名 (CONTAINING_OBJECT_LIB)
  • 所在的对象名称 (CONTAINING_OBJECT_NAME)
  • 得分值 (SC)

这些列提供了关于搜索结果的额外信息,应用程序可以使用这些信息来过滤结果。在 OmniFind 扩展功能用户指南 中有对这些列的详细介绍


其他存储过程

我们已经介绍了基本的存储过程,但还有几个存储过程应该提一下(详见 OmniFind 扩展功能用户指南)。这些存储过程可用于:

  • 查询已被索引的对象的状态,
  • 查询索引的状态 ,
  • 查询对象集,
  • 删除对象集,
  • 从系统删除文本搜索 collection。

应用程序示例

我们的示例程序在一个文本搜索 collection 上执行一个搜索。我们选择使用 Java 和 JDBC,因为它们很流行而且是平台独立的。对其他语言和环境来说,也有类似方法可用,但我们的这种方法相对容易解释。

环境设置

在我们运行这个程序之前,必须先创建文本搜索 collection 并更新数据。我们将 collection 设置为每 15 分钟更新一次。本例中,我们想要索引 IFS 目录 /home/ntl 中的所有流文件和输出队列 NTL/MYOUTQ 中的所有 spool 文件。

  1. 创建一个文本搜索 collection。
    CALL SYSPROC.SYSTS_CRTCOL(
    ‘ COLLECTION_FOR_NICK ’ , ‘ UPDATE FREQUENCY D(*) H(*) M(0, 15, 30, 45) ’ );
  2. 设置当前 collection 的当前 Path/Schema。
    SET CURRENT SCHEMA COLLECTION_FOR_NICK;
    SET CURRENT PATH COLLECTION_FOR_NICK;
  3. 将 spool 文件对象添加到集合中。
    CALL ADD_SPLF_OBJECT_SET( ‘ NTL ’ , ‘ MYOUTQ ’ );
  4. 将 IFS 流文件对象集添加到集合中。
    CALL ADD_IFS_STMF_OBJECT_SET( ‘ /home/ntl ’ );
  5. 更新 collection。
    CALL UPDATE;

严格地讲,步骤 5 中的更新并不是必需的,因为 collection 会被定时更新。但在这里强制更新意味着我们知道进行搜索之前已经更新已经完成。

在基础设施已经完成 , 并且更新过程已经结束之后,我们就可以从 collection 中搜索关键字了。

在 Java 中调用 SEARCH 存储过程

列表 3 中的 Java 语句演示如何编写 Java 代码来调用 SEARCH 存储过程。这里的关键点是使用 JDBC 里的 java.sql.CallableStatement 类。该类用一个 Boolean 结果来指示有一个结果集返回。

列表 3. 在 Java 中调用 SEARCH 存储过程
 // Variable keywords is the words you want to search for. 
 String searchSQL = 
 "CALL COLLECTION_FOR_NICK.SEARCH('" + keywords + "')"; 

 // Create CallableStatement for calling a stored procedure. 
 // Variable connection is a java.sql.Connection instance. 
 CallableStatement cstmt = connection.prepareCall(searchSQL); 

 // The execute method returns a Boolean to indicate that a 
 // result set has been returned 
 boolean isResultSetReturned = cstmt.execute();

调用 SEARCH 存储过程后,就可以从 CallableStatement 读取结果集了。我们使用 JDBC 的 ResultSet.getString() 函数来读取每一行中的 OBJECTINFOR 列的值的值,并把它当成一个 String。用同样的方法,我们可以从头到尾遍历这个结果集,取得每个对象的位置信息的 XML 值(一个字符串)。

列表 4. 调用 SEARCH 之后读取结果集
          // Get the result set from CallableStatement object 
           ResultSet rs = cstmt.getResultSet(); 

           // The ResultSet next method is used to iterate over 
           // the rows of a ResultSet. 
           // The next method must be called once before the 
           // first data is available for viewing. As long as next 
           // returns true, there is another row of data that 
           // can be used. 
           while (rs.next()) { 
               String objectinfor = rs.getString("objectinfor"); 

              /** Process the object information using the XML string 
                 here **/ 
            }

提取 XML 对象信息

XML 对象信息包含了已索引的对象的位置信息。为处理该对象,我们需要提取这个位置数据。

DocumentBuilder 类提供了一种方法来通过一个字节流构建一个 XML 文档树。一旦利用对象信息构建完文档树之后,就可以提取特定元素的值了。

本例演示如何从一个 spool 文件的对象信息中提取元素“job_name”的值。

完整的程序列表 中包含一个过程 parseObjectInfo,演示如何编写代码来处理每个对象类型的信息并将这些信息转储到标准输出。

列表 5. 提取 XML 信息
 DocumentBuilderFactory factory = 
           DocumentBuilderFactory.newInstance(); 
 DocumentBuilder builder; 
 Document doc = null; 

 factory = DocumentBuilderFactory.newInstance(); 
 builder = factory.newDocumentBuilder(); 
 InputStream is = new 

 ByteArrayInputStream(objectinfor.getBytes()); 
 doc = builder.parse(is); 

 String job_name = 
          doc.getElementsByTagName("job_name"). 
 item(0).getTextContent();

执行搜索的完整程序

列表 6 是这个示例的完整程序。需要特别注意的是 parseObjectInfo 函数,我们创建这个函数来处理不同的已经支持的对象类型。该函数将对象的信息转储到标准输出流。只需稍作修改,这个代码就可用于执行其他任务,比如从对象中检索文本数据。

列表 6. 完整的搜索程序
 /////////////////////////////////////////////////////////////////////////////// 
 // 
 // ISVSearch example. This program uses the native JDBC driver for the 
 // Developer Kit for Java to call Omnifind stored procedure SEARCH to query 
 // specific key words 
 // 
 // Command syntax: 
 //     ISVSearch <collection name> <key words> 
 // 
 // Before calling this program, user should create collection, add 
 // object set to collection, update collecton first. These steps are 
 // used to make the collection searchable. Reference to user documentation 
 // for detail statements. 
 // 
 // This source is an example of how to invoke stored procedure SEARCH 
 // in java code and how to analyze the result. 
 // 
 /////////////////////////////////////////////////////////////////////////////// 

 // Include any Java classes that are to be used. In this application, 
 // many classes from the java.sql package are used and the 
 // java.util.Properties class is also used as part of obtaining 
 // a connection to the database. 
 // java.io and javax.xml package are used 
 // to parse the XML column of returned result set of SEARCH stored procedure 
 import java.io.*; 
 import java.sql.*; 
 import java.util.Properties; 
 import javax.xml.parsers.*; 
 import org.w3c.dom.Document; 
 import org.xml.sax.SAXException; 
 //Create a public class to encapsulate the program. 
 public class ISVSearch { 
      // The connection is a private variable of the object. 
       private Connection connection = null; 

       public static void main(String args[]) { 
          // Create an object of type ISVSearch. This 
          // is fundamental to object-oriented programming. Once 
          // an object is created, call various methods on 
          // that object to accomplish work. 
          // In this case, calling the constructor for the object 
          // creates a database connection that the other 
          // methods use to do work against the database. 
          ISVSearch isvSearch = new ISVSearch(); 

          // The search method is called next. This method 
          // processes an Omnifind search statement against the collection 
          // created before. The output of that query is output to standard 
          // out for you to view. 
          isvSearch.search(args[0], args[1]); 

          // Finally, the cleanup method is called. This method 
          // ensures that the database connection that the object has 
          // been hanging on to is closed. 
          isvSearch.cleanup(); 
      } 

      public ISVSearch() { 
          // Following statements were used to create a connection to 
          // DB2 for i 
          Properties properties = new Properties(); 
          properties.put("user", "omnifind"); 
          properties.put("password", "textsearch"); 

      try { 
          Class.forName("com.ibm.db2.jdbc.app.DB2Driver"); 
          connection = DriverManager.getConnection("jdbc:db2:*local", 
                     properties); 
      } catch (Exception e) { 
           System.out.println("Caught exception: " + e.getMessage()); 
      } 
 } 

 /** 
  * Search key words from specific collection. 
  * The result will be printed to standard output. 
  * 
  * @param collection The collection name user created 
  * @param keywords The key words user want to search 
  */ 
 public void search(String collection, String keywords) { 
      try { 
          // Constructed the SQL statement to do search 
          // The SQL statement should be like this 
          // CALL <COLLECTIONNAME>.SEARCH('keywords') 
          String searchSQL = "CALL " + collection + ".SEARCH('" + keywords 
                     + "')"; 

          // Create CallableStatement for calling a stored procedure. 
          CallableStatement cstmt = connection.prepareCall(searchSQL); 

          // The execute method returns a boolean to indicate the form 
          // of the first result 
          boolean isResultSetReturned = cstmt.execute(); 

          // Check if there is ResultSet returned 
          if (isResultSetReturned) { 
              // GEt the result set from CallableStatement object 
              ResultSet rs = cstmt.getResultSet(); 

             // The ResultSet next method is used to process the rows of a 
             // ResultSet. The next method must be called once before the 
             // first data is available for viewing. As long as next returns 
             // true, there is another row of data that can be used. 
             while (rs.next()) { 
                   // The result set returned from SEARCH has columns 
                   // OBJTYPE, OBJATTR, CONTAINING_OBJECT_LIB, 
                   // CONTAINING_OBJECT_NAME 
                   // OBJECTINFOR, MODIFY_TIME, SC 
                   String objtype = rs.getString("objtype"); 
                   String objattr = rs.getString("objattr"); 
                   String containing_object_lib = rs 
                            .getString("containing_object_lib"); 
                   String containing_object_name = rs 
                            .getString("containing_object_name"); 
                   // OBJECTINFOR is an XML column which contains all the 
                   // detail info about the indexed object. 
                   String objectinfor = rs.getString("objectinfor"); 
                   Timestamp modify_time = rs.getTimestamp("modify_time"); 
                   // Score can help user do better ordering 
                   double sc = rs.getDouble("sc"); 

                   // parseObjectInfo is used to parse XML column and output 
                   parseObjectInfo(objtype, objattr, objectinfor); 
            } 
      } 
 } catch (SQLException e) { 
                   // Display more information about any SQL exceptions that are 
                   // generated as output. 
                   System.out.println("SQLException exception: "); 
                   System.out.println("Message:....." + e.getMessage()); 
                   System.out.println("SQLState:...." + e.getSQLState()); 
                   System.out.println("Vendor Code:." + e.getErrorCode()); 
                   e.printStackTrace(); 
            } 

 } 

 /** 
  * Parse object infor XML content to text format and print it to standart 
  * output Based on different object type and attribute, there are different 
  * way to do parsing. 
  * 
  * @param objtype 
  * @param objattr 
  * @param objectinfor 
  */ 
 public static void parseObjectInfo(String objtype, String objattr, 
                String objectinfor) { 

      // DocumentBuilderFactory creates a factory instance. 
      // It will be used to create document builder then 
     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 
      // DocumentBuilder class provides function to parse XML document 
      DocumentBuilder builder; 
      // Initialize doc to null. This instance will be initialized then 
      Document doc = null; 
      try { 
           // Initialize DocumentBuilder instance 
           builder = factory.newDocumentBuilder(); 
           // Constructed InputStream instance, which will be used as a 
           // parameter while calling DocumentBuilder.parse function 
           // ByteArrayInputStream is a class implements InputStream. 
           // Since objectinfor variable is string, so use 
           // ByteArrayInputStream class to constructed a InputStream 
           // instance with bytes of objectinfor variable. 
           InputStream is = new ByteArrayInputStream(objectinfor.getBytes()); 
           // parse function is used to parse InputStream to XML Document 
           // object 
           doc = builder.parse(is); 
       } catch (Exception e) { 
           System.out.println("Caught exception: " + e.getMessage()); 
       } 

       if (objtype.equals("*OUTQ ") && 
               objattr.equals("*SPLF ")) { 
            // For the object whose object type is "*OUTQ " and object 
            // attribute is "*SPLF ", 

            // user following way to get the detail value 
            System.out.println("========================================"); 
            // Get text content for element whose tag name is "job_name" or the 
            // other tag. 
            System.out.println("Job name:"
                    + doc.getElementsByTagName("job_name").item(0) 
                             .getTextContent()); 
            System.out.println("Job user name:"
                    + doc.getElementsByTagName("job_user_name").item(0) 
                             .getTextContent()); 
            System.out.println("Job number:"
                    + doc.getElementsByTagName("job_number").item(0) 
                             .getTextContent()); 
            System.out.println("spool file name:"
                    + doc.getElementsByTagName("spool_file_name").item(0) 
                             .getTextContent()); 
            System.out.println("spool file number:"
                    + doc.getElementsByTagName("spool_file_number").item(0) 
                             .getTextContent()); 
            System.out.println("Job system name:"
                    + doc.getElementsByTagName("job_system_name").item(0) 
                             .getTextContent()); 
            // The date format CYYMMDD is defined as follows: 
            // C Century, where 0 indicates years 19xx and 1 indicates years 
            // 20xx. 
            // YY Year 
            // MM Month 
            // DD Day 
            System.out.println("create date:"
                    + doc.getElementsByTagName("create_date").item(0) 
                             .getTextContent()); 
            // The time format HHMMSS is defined as follows: 
            // HH Hour 
            // MM Minutes 
            // SS Seconds 
            System.out.println("create time:"
                    + doc.getElementsByTagName("create_time").item(0) 
                             .getTextContent()); 
 
            /* The output should like below 
 
            ======================================== 
            Job name:QPRTJOB 
            Job user name:NTL 
            Job number:066537 
            spool file name:QPJOBLOG 
            spool file number:3526 
            Job system name:RCHASRA5 
            create date:1110430 
            create time:152003 
            */ 
        } else if (objtype.equals("*STMF ") && 
                objattr.equals("*DATA ")) { 
            // For the object whose object type is "*STMF " and object 
            // attribute is "*DATA ", 
            // user following way to get the detail value 
            System.out.println("========================================"); 
            // Get text content for element whose tag name is "file_path"
            System.out.println("File path:"
                    + doc.getElementsByTagName("file_path").item(0) 
                             .getTextContent()); 
            /* The output should like below 
 
            ======================================== 
            File path:/home/user/test.txt 
            */ 
       } 
 } 

 /** 
  * The following method ensures that any JDBC resources that are still 
  * allocated are freed. 
  */ 
 public void cleanup() { 
            try { 
                if (connection != null) 
                     connection.close(); 
            } catch (Exception e) { 
                System.out.println("Caught exception: "); 
                e.printStackTrace(); 
            } 
      } 
 }

列表 7. 输出示例

注意:这里假定有一个 spool 文件和一个 IFS 流文件匹配搜索关键字。

 ======================================== 
             Job name:QPRTJOB 
             Job user name:NTL 
             Job number:066537 
             spool file name:QPJOBLOG 
             spool file number:3526 
             Job system name:RCHASRA5 
             create date:1110430 
             create time:152003 

 ======================================== 
             File path:/home/ntl/test.txt

结束语

现在您应该了解了如何使用新增的 OmniFind 扩展存储过程来执行以下操作:

  • 创建文本搜索 collection
  • 添加一个或多个 spool 文件对象集或 IFS 流文件对象集
  • 更新索引
  • 执行搜索

这些 IBM i 新增的搜索功能提供了一个解决方案,帮助您轻松地增强您的应用程序的功能;它还提供了一种简单的方法来编写一些实用工具来快速找到系统中不同的对象种类。OmniFind V1R2 包含在 IBM i 7.1 中,并不需要用户额外付费。


参考资料

V1R2 OmniFind 参考手册:http://publib.boulder.ibm.com/infocenter/iseries/v7r1m0/index.jsp?topic=/rzash/rzashkickoff.htm

OmniFind 扩展功能用户指南:http://www-03.ibm.com/systems/resources/systems_power_ibmi_omnifind_extensions_user_guide.pdf

OmniFind 白皮书:http://www.ibm.com/partnerworld/wps/servlet/ContentHandler/whitepaper/i/omnifind/search

DB2 for i 论坛:http://www.ibm.com/developerworks/forums/forum.jspa?forumID=292

DB2 for i 存储过程红皮书:http://www.redbooks.ibm.com/abstracts/sg246503.html

DB2 for i 技术更新 Wiki:https://www.ibm.com/developerworks/mydeveloperworks/wikis/home?lang=en#/wiki/IBM%20i%20Technology%20Updates/page/DB2%20for%20i%20Functional%20Enhancements

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=IBM i
ArticleID=769030
ArticleTitle=使用 OmniFind 扩展功能搜索 Spool 文件和 IFS 流文件
publish-date=11242011