DataImportHandler之三

Extending the tool with APIs

我们所展现的例子确实没有多大价值,单靠配置xml文件就满足所有的需求是不可能的。所以我们提供了一些抽象类,可以通过这些方法来提高功能。

 

Transformer

每一条从数据库中取得的数据能够被直接处理掉,或者通过它创建一个全新的域,它设置能够返回多行数据。配置文件必须像下面那样设置。

 
<entity name="foo" transformer="com.foo.Foo" ... />

DataImport - 航梦 - 火星?地球? 注意-- trasformer的值必须是一个可以使用的classname。如果class包是'org.apache.solr.handler.dataimport' ,包名可以被忽略。solr.也是可以使用的,如果这个class在solr的一个包下的话。这个规则适应所有的可插入的类,像DataSource、EntityProcessor、uator。 

类Foo必须继承抽象类org.apache.solr.hander.dataimport.Transformer.这个类只有一个抽象方法。

transformer这个属性可以有多个transformers()(比如 transformer="foo.X,foo.Y") 之间用逗号隔开。 transformers 会形成一条处理链。它们将会按照它们的排列顺序起作用。

public abstract class Transformer { public abstract Object transformRow(Map row, Context context); }

Context 是一个抽象的类,它提供上下文关系,这可能在处理数据的时候要用到。

另外,类Foo,可以选择不不实现这个抽象类,而只需要下面这个方法

public Object transformRow(Map row)

So there is no compile-time dependency on the DataImportHandler API

它的配置是灵活的。它允许用户向标签entity和field提供任意的属性。tool将会读取数据,并将它传给实现类。如果Transformer需要额外的的信息,它可以从context中取得。

正则表达式转换器

tool它提供了一个内嵌的转换器,叫做正则表达式转换器。它可以使用正则表达式从原数据中解析出我们想要的值。org.apache.solr.handler.dataimport.RegexTransformer 是它的名字. 因为它属于默认的包,所以它的包名是可以被忽略的。

例子: 

<entity name="foo" transformer="RegexTransformer" query="select full_name , emailids from foo"/> ... /> <field column="full_name"/> <field column="firstName" regex="Mr(/w*)/b.*" sourceColName="full_name"/> <field column="lastName" regex="Mr.*?/b(/w*)" sourceColName="full_name"/> <field column="mailId" splitBy="," sourceColName="emailids"/> </entity>

      

属性

RegexTransfromer只对属性中有regex或者splitBy的域起作用。所有的属性我们列在下面。

  • regex : 这是要匹配的正则表达式。regex和splitBy两者必有其一。如果没有,这个域将不会被正则表达式转换器处理。

  • sourceColName : 正则表达式起作用的列。. 如果这个这个属性不存在,那么source将等同域target。

  • splitBy : 如果正则表达式,是被用来分割一个字符串以获得多个值,那么使用这个。

  • replaceWith : 跟属性regex一起使用。相当于我们平常使用的方法new String().replaceAll(, )

这里,属性‘regex’和‘sourceColName’是转换器自定义的属性。它从resultSet中读取域‘full_name’的值,然后转换它,并将结果分别传给‘firstName’和‘lastName’。所以,尽管查询结果只返回一列“full_name”,但solr document依然可以获得额外的两个域“firstName”和‘lastName’。

域'emailids'  是一个用逗号分隔着的值。所以,我们最终可以从emailids得到一个以上的emial id。mailid 在solr中应该被定义为多值的。

脚本转换器

你可以使用javascript 或者其他的 脚本语言来写转换器,只要java支持这种脚本。在这里我们应该使用java 6.
        

<dataConfig> <script><![CDATA[ function f1(row) { row.put('message', 'Hello World!'); return row; } ]]></script> <document> <entity name="e" pk="id" transformer="script:f1" query="select * from X"> .... </entity> </document> </dataConfig>


 

  • 你可以在dataConfig结点中设置script 标签。默认的语言是javascript。你当然可以使用另外一种语言,你可以通过script标签中的属性language去设置它。(必须有java6的支持)。

  • 你可以写任意多的转换函数。每个函数必须接受一个相当于 Map的row变量,然后要返回一个row。(转换以后)

  • 通过在实体中指定 transformer=“script:”来使一个实体使用脚本函数。

  • 在上面的data-config中,对于结果中返回的实体e的每一个行,javascript函数都将被执行一次。 

  • 执行机制跟一个java的转换器是一样的。在Transformer 中有两个参数(transformRow(Map,Context ))。在javascript中,第二个参数被忽略了,但它一样是起作用的。

 

日期格式转换器

这里有一个内嵌的转换器,叫做DateFormatTransformer(日期格式转换器) ,这个在将字符型时间转换成java.util.Date的类型的时候是很有用的。

<field column="date" xpath="/RDF/item/date" dateTimeFormat="yyyy-MM-dd'T'hh:mm:ss" />
属性

日期格式转换器只对带有属性“dateTimeFormat”的域才起作用。其他属性如下所示。

  • dateTimeFormat : 转换使用的格式。这个必须服从java的SimpleDateformat。

  • sourceColName : 要使用日期转换的列。如果没有设定这个值,那么源列跟目标域的名称是一样的。

上面的域的定义在RSS例子中有使用,以转换RSS种子项中的时间格式。

数字格式转换器

能将一个字符串转换成一个数字,使用的是java中类NumberFormat。例子:

<field column="price" formatStyle="number" />

默认情况下,类Numberformat使用系统的本地格式去转换一个字符串,如果你需要指定一个不同的本地类型的话,你可以像下面这样指定。例子:

<field column="price" formatStyle="number" locale="de-DE" />
属性

数字格式转换器 只对那些带有属性“formatStyle”的域有用。

  • formatStyle : 解析这个域所需要的格式。这个属性的值必须是(number|percent|integer|currency)中的一个。可以参考 java DataImport - 航梦 - 火星?地球? NumberFormat.

  • sourceColName : 要使用数字转换的列。如果没有设定这个值,那么源列跟目标域的名称是一样的。

  • locale : 要转换的字符串所使用的国际化格式。如果没有设定这个值,它的默认值是系统的国际化格式。它的值必须是language-country。例如 en-US。

模板转换器

使用DataImportHandler中强大的模板引擎来创建或者设定一个域的值。例如:

<entity name="e" transformer="TemplateTransformer" ..> <field column="namedesc" template="hello${e.name},${eparent.surname}" /> ... </entity>

这里模板的规则跟‘query’、‘url’的规则是一样的。它主要能帮我们将多个值连到一起,或者忘域值注入其他的字符。这个转换器只对拥有属性‘template’的域起作用。

属性
  • template : 模板字符串。上面的例子中有两个占位符,‘${e.name}和${eparent.surname}’。 In the above example there are two placeholders '${e.name}' and '${eparent.surname}' . 两个值都必须存在,否则这个模板将不会起作用。

 

自定义模板转换器

如果你需要在将数据送给solr之前,对数据进行一些处理,你可以写一个你自己的转换器。让我们来看一个例子。在我们的schema中我们有一个单值的域叫做‘artistName’,类型是String。这个域的值包含了多个单词,例如‘Celine Dion’,这里有一个问题 ,这个值包含一些开头空格和结尾空格,这些空格不是我们想要的。solr的WhitespaceAnalyze在这里用不上,因为,我们并不想把这个字符串切词了。一个可以选择的解决方案就是自己写一个TrimTransformer。

一个简单的TrimTransformer
package foo; public class TrimTransformer { public Object transformRow(Map row) { String artist = row.get("artist"); if (artist != null) row.put("ar", artist.trim()); return row; } }

不需要去继承任何类。这个类只需要有transformRow 方法,就像上面的那样。DataImportHandler会自动辨别它,并使用反射机制来调用它。你可以在你的data-config.xml文件中这样来设置:

<entity name="artist" query="..." transformer="foo.TrimTransformer"> <field column="artistName" /> </entity>
一个通用的TrimTransformer

假设,你想写一个通用的TrimTransformer,这样你就不用将要处理的列写在的代码里面。这里,我们需要在data-config.xml中设一个标记来表示这个域是否要应用这个转换器。

<entity name="artist" query="..." transformer="foo.TrimTransformer"> <field column="artistName" trim="true" /> </entity>

现在,你需要去继承 Transformer 这个抽象类,并使用Context中的API来获得实体中的域,并获得域中的属性,检查标记有没有被设值。

package foo; public class TrimTransformer extends Transformer { public Map transformRow(Map row, Context context) { List> fields = context.getAllEntityFields(); for (Map field : fields) { // Check if this field has trim="true" specified in the data-config.xml String trim = field.get("trim"); if ("true".equals(trim)) { // Apply trim on this field String columnName = field.get("column"); // Get this field's value from the current row String value = row.get(columnName); // Trim and put the updated value back in the current row if (value != null) row.put(columnName, value.trim()); } } return row; } }  

如果域是多值的,那么返回值将会是一个list而不是单单一个对象,而且需要被恰当的处理。你可以将DataImprotHandler打包成一个jar包,然后再扩展Transformer和Context。

 

EntityProcessor(实体处理器)

默认的情况下,每个实体都会被sqlEntityProcessor处理。在系统使用RDBMS作为数据源的时候,它很适用。对于其他的数据源,例如 REST 或者不是sql的数据源 ,你可以选择继承org.apache.solr.handler.dataimport.Entityprocessor. 这个抽象类。它被设计成从实体中一行一行的读取数据。最简单的实现自己的实体处理器的方式是 继承EntityProcessorBase ,然后重写方法 public Map nextRow() method。 'EntityProcessor'依赖于数据源来获取数据。数据源的返回类型对实体处理器来说是很重要的。下面是一些内嵌的实体处理器。

SqlEntityProcessor

它是默认的,数据源必须是DataSource类型的,在这里默认的情况下使用的是jdbcDataSource。

XPathEntityProcessor

处理XML类型的数据源。数据源的类型必须是DataSource类型的,这种类型的数据源有HttpDataSource和FileDatasource类型。

FileListEntityProcessor

简单的处理器,它能够从文件系统中得到文件的集合。这个系统基于一些标准,它不使用数据源,下面是实体的属性:

  • fileName :(必须) 辨别文件的正则表达式

  • baseDir : (必须) 根目录(虚拟路径)

  • recursive : 是否要递归的获取文件,默认是false。

  • excludes : 匹配文件名的正则表达式

  • newerThan : 一个数字参数 . 使用格式 (yyyy-MM-dd HH:mm:ss) . 它可以是一个datemath 类型的字符串,例如:('NOW-3DAYS'). 需要加单引号。它也可以是一个变量,像${var.name}这样。

  • olderThan : 一个数字参数 . 跟上一条的规则是一样的

  • rootEntity :根实体的值必须是false,除非你想索引文件名。位置直接在下面的是根实体,这就意味着根实体产生的行都将被当成一个document存放在lucene里面。但是,在这个例子里面,我们并不想为每个文件建立一个document,我们想对x实体产生的行建立document,因为实体f的属性rootEntiry等于false,所以在直接位于实体f下面的实体将成为根实体,它所产生的行将会被当成一个document。

  • dataSource :它必须被设为null值,因为这里并不需要使用任何的数据源,即是说,我们将不会创建Datasource的实例。(在大多数的情况下,只有一个数据源,jdbc数据源,所有的实体都用,在这里,数据源是没有必要的。)

例子:   

<dataConfig> <dataSource type="FileDataSource" /> <document> <entity name="f" processor="FileListEntityProcessor" fileName=".*xml" newerThan="'NOW-3DAYS'" recursive="true" rootEntity="false" dataSource="null"> <entity name="x" processor="XPathEntityProcessor" forEach="/the/record/xpath" url="${f.fileAbsolutePath}"> <field column="full_name" xpath="/field/xpath"/> </entity> </entity> <document> <dataConfig>

千万要注意rootEntiry这个属性,由这个处理器所产生的域有fileAbsolutePath,fileSize,fileLastModified,fileName.

CachedSqlEntityProcessor

 

应该说,这是SqlEntityProcessor的一个扩展,这个处理器通过缓存一些行,来减少数据库查询。它几乎对根实体没有用,因为这个实体中只有一个sql语句被执行了。

Example 1.

<entity name="x" query="select * from x"> <entity name="y" query="select * from y where xid=${x.id}" processor="CachedSqlEntityProcessor"> </entity> <entity>

这个例子的用法跟下面的是一样的,一个查询被执行完,它的结果被存储起来,下次这个查询再被执行的的时候,它将会从缓存中取出结果并返回。

Example 2:

<entity name="x" query="select * from x"> <entity name="y" query="select * from y" processor="CachedSqlEntityProcessor" where="xid=x.id"> </entity> <entity>

这个例子跟前一个的区别在于属性‘where’。这个例子中,查询语句将从表中取回所有的数据,并把他们都放在缓存中。其中的关键就在域属性‘where’。缓存使用y中的xid作为键值,实体被查询的时候x.id的值就会被计算出来,我们首先会在缓存中找匹配的数据,接着返回。

 

在属性where中,=号之前的值是y中的列,=号之后的值是计算出来的要在缓存中查找的值。

DataSource(数据源)

 org.apache.solr.handler.dataimport.DataSource 能被继承。

public abstract class DataSource { public abstract void init(Context context, Properties initProps); public abstract T getData(String query); public abstract void close(); }

 它必须在数据源的定义部分被配置。

<dataSource type="com.foo.FooDataSource" prop1="hello"/>
JdbcdataSource

这个是默认的,它的声明如下:

public class JdbcDataSource extends DataSource >>

 

它可以一条一条的遍历数据库,每一行数据被当作一个Map。

HttpDataSource

XPathEntityProcessor使用这个数据源 . 它的声明如下:

public class HttpDataSource extends DataSource
FileDataSource

这个很像HttpDataSource . 它的声明如下:

public class FileDataSource extends DataSource

The attributes are:

  • basePath: (可选的)  ,得到所需要的值时必须的基本路径。

  • encoding: (可选的)当文件编码跟平台编码不一样的时候,应当设定这个值。

Boosting , Skipping documents(提高文档的得分,或者跳过文档)

我们还可以在运行的时候提高一个文档的得分,或者跳过某一个特定的文档。

可以通过自定义转化器,增加一个属性,并将它设为true,这样就可以跳过这个文档了。可以通过,增加一个属性docBoost ,属性是文档的评分的这种方式给文档打分。Write a custom Transformer to add a value $skipDoc with a value 'true' to skip that document. To boost a document with a given value add $docBoost with the boost value

在 solrconfig.xml中增加数据源

 

我们也可以在solrconfig.xml中配置数据源,属性是一样的,只是方式稍微有点不同。 

     
    
      <requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
    <lst name="defaults">
      <str name="config">/home/username/data-config.xml</str>
      <lst name="datasource">
         <str name="driver">com.mysql.jdbc.Driver</str>
         <str name="url">jdbc:mysql://localhost/dbname</str>
         <str name="user">db_username</str>
         <str name="password">db_password</str>
      </lst>
    </lst>
  </requestHandler>
    
 

 

结构图

下面的这个图显示了一般的配置文件的逻辑流程。

上面的这个图表达了这样的一个意思:一共有三个数据源,两个关系数据库的数据源,和一个http/xml的数据源。

 

         jdbc1 和jdbc2 是JdbcDataSource ,它配置在solrconfig.xml文件中。

  • http是一个HttpDataSource类型的数据源。

  • 根实体是一个叫做a的表,它使用jdbc1作为它的数据源。实体一般都与表名相同。

  • 实体A有两个子实体 B 和C 。B使用http数据源,C使用jdbc2数据源。

  • 在执行一个full-import的命令的时候,根实体A会首先被执行。

  • 由实体A导出的每一行,都会被传给实体B和实体C。

  • B和C通过占位符来使用实体A中的数据。占位符:${A.a}。

    • B 有一个url属性

    • C 有一个query属性

  • C 有两个转换器 ‘f’和‘g’。

  • 由C产生的每一行数据,都会被有序的传给 'f '和‘g’(转换器是链式的,即有序的)。每个转换器都能够改变输入的值。在这里转换器‘g’将从一行数据(f(c .1))中产生两行数据。 

  • 最近将每个实体的结果合并成为一个文档。

    • 请注意:从C产生的中间结果,例如C.1 c.2 ,f(c.1) f(c.2),都将被忽略掉。

域声明

域的声明,能够帮助我们通过提供一些额外的信息得到那些不能自动获取到的值。它依赖于结果集中的列。在dataConfig里面配置的域,一般情况下应该跟schema配置的一样。它应该自动继承schema.xml中的所有的域。但是,你不能增加一些额外的域。  那么,什么时候增加域声明呢?

  • 当实体处理器所产生的域的名字,跟相应的域在schema.xml中的名字不一样的时候。

  • 当内嵌的转换器需要一些额外的信息来决定哪个域要处理,以及该怎么处理的时候。

  • XPathEntityprocessor 或者其他的处理器,显示的要求一些额外的信息的时候。

关于行(row)和多值域

行在DataimportHandler中的表现形式是一个Map。在这个map里面,key是域的名字,value可以任何一个合法的solr 类型。value也能够是合法的solr类型的聚集(这将会映射到一个多值域)。如果数据源是RDBMS的话,一般是不会产生多值域的。当然我们可以通过加一个子实体的方式来产生多值域。这里子实体返回的多个域,相当于父实体的一个多值域。如果数据源是xml的话,产生多值域是一件相当简单的事情。

变量

变量是指最终代替那些占位符的值。这是一个多级的map,每一个命名空间都是一个map,命名空间使用.分隔。例如 占位符 ${item.ID}, 'item'是一个命名空间(也是一个map),ID是这个命名空间下的一个值。我们很容易推导出 占位符 ${item.x.ID} 这里x是另外一个map。变量的值能够从Context中获得,也可以在RDMS的query属性中或者http数据源的url属性中使用类似${}的占位符获得。

使用函数来自定义query和url的格式

 命名空间这个概念在这里也是相当的有用的。用户可能想要传一个经过计算的值给 query或者url,比如这里有一个Data类型的数据,但是你的数据源只支持另外一种格式的数据源。我们提供了一些函数,或许它们能够帮你完成一些事情。

  • formatDate : 它可以像这样去使用,'${dataimporter.functions.formatDate(item.ID, yyyy-MM-dd HH:mm)}' 。它的第一个参数是一个合法的变量,第二个参数是一种时间格式(这里使用的格式工具是SimpledateFormat),The first argument can be a valid value from the VariableResolver and the second cvalue can be a a format string (use SimpledateFormat) . 它可以是一个经过计算的值,它使用solr的时间表示方式。(要注意,它必须被单引号括起来

  • escapeSql : 使用它可以对特别的sql 字符串进行包装。例子 : '${dataimporter.functions.escapeSql(item.ID)}'. 这里只使用一个参数,这个参数必须是一个合法的VaraiableResolver.

  • encodeUrl : 使用这个对url进行编码。例子e: '${dataimporter.functions.encodeUrl(item.ID)}' . 只使用一个参数,这个参数必须是一个合法的VariableResolver

访问请求参数

我们可以使用'request'命名空间来访问传递给http 请求的参数。例如'${dataimporter.request.command}' 将会返回被执行的命令。任何参数都可以通过这种方式得到。

交互式的开发模式Interactive Development Mode

这是一个很酷的,并且功能强大的工具。它能够帮助你通过图形界面来建立一个dataconfig.xml文档。你可以通过DataImport - 航梦 - 火星?地球? http://host:port/solr/admin/dataimport.jsp 来访问它。以下是它的特性:

  • 这个界面有两个板块,RHS是用来获取输入的,LHS是用来显示输出的。

  • 当你点击debug now 按钮的时候,它将会执行配置文件,并且显示结果文档。

  • 你可以通过start和rows这两个参数来调试 类似从115开始到118这样的文档。

  • 选择 'verbose'选项表示你想要得到一些关于中间步骤的信息。包括query产生的数据,传给转换器的数据,以及转换器产生的数据。

  • 如果在运行过程中发生了异常,那么LHS板块将显示异常信息。

  • fields是由实体产生的。当域没有在schema.xml中声明,也没有在dataConfig.xml有声明的时候,转换器就不会对该域进行处理了。

屏幕快照

哪里可以找到它?

DataimportHandler是solr的新加的特性。

  • 从 DataImport - 航梦 - 火星?地球? Solr website 下载一个最新的版本 。

  • 通过 Full Import 的例子来感受一下。

Solr JIRA.的 DataImport - 航梦 - 火星?地球? SOLR-469 你可以查看到有关DataImporthandler的一些开发讨论。

转自:http://blog.sina.com.cn/s/blog_496c82a90100eg53.html

英文原文:http://wiki.apache.org/solr/DataImportHandler

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值