前言:这里以一个例子来说明PRD报表工具强大的扩展功能。 我们用4种不同的方式来实现一个REGE()函数,其目的用来来提取想要的内容。需要传入两个参数,一个是原始字符串,一个是包含一个分组的正则表达式,表达式运算结果是正则表达式匹配的第一个分组。
相关说明:
a)、示例数据库:Pentaho自带的SampleData
b)、示例的SQL脚本:
SELECT
"ORDERFACT"."PRODUCTCODE",
"PRODUCTS"."PRODUCTNAME",
"PRODUCTS"."PRODUCTLINE",
SUM("ORDERFACT"."QUANTITYORDERED") AS QUANTITYORDERED,
SUM("ORDERFACT"."TOTALPRICE") AS TOTALPRICE
FROM "ORDERFACT" INNER JOIN "PRODUCTS" ON "ORDERFACT"."PRODUCTCODE" ="PRODUCTS"."PRODUCTCODE"
GROUP BY
"PRODUCTS"."PRODUCTLINE","ORDERFACT"."PRODUCTCODE",
"PRODUCTS"."PRODUCTNAME"
ORDER BY
"PRODUCTS"."PRODUCTLINE","ORDERFACT"."PRODUCTCODE",
"PRODUCTS"."PRODUCTNAME"
c)、关于如何创建一个PRD报表文件请参考链接文档
http://www.jianshu.com/p/e9b1762061fa
一、内置JAVA脚本
这里用 BeanShell(BSH)来实现一个表达式。打开函数对话框,展开 Script 分组,选
择BeanShell(BSH)。
按下面设置 Expression 属性:
import java.util.regex.*;
Object getValue() {
try{
// 创建一个基于正则表达式的输入模式
final Pattern pattern = Pattern.compile("S(\\d+)_.*");
//final Matcher matcher = pattern.matcher(dataRow.get("PRODUCTCODE"));
// 查找的字段,如果不为空,创建一个匹配
final Object object = dataRow.get("PRODUCTCODE");
if( object == null ){
return null;
}
final Matcher matcher = pattern.matcher(object.toString());
// 在字符串中找到第一个匹配项
matcher.find();
// 返回第一组内找到匹配
return matcher.group(1);
}catch(Exception e){
return e.getMessage();
}
}
将上述代码添加到下图箭头所指处:
把函数拖入报表即可:
展示样式:
二、内置JavaScript脚本
用 Javascript 来 实 现 。 选 择 Script 下 面 的 JavaScript 函 数 , 或 者 选 择 Bean-Scripting
Framework(BSF)并把表达式编程语言属性设置为 javascript。
Expression 属性按如下设置:
dataRow.get("PRODUCTCODE").match(/S(\d+)_.*/)[1]
方式一:
方式二:
把函数拖入报表即可:
展示样式:
三、本地构建PRD源码的目录结构
以上的2种方式为PRD报表工具内置的扩展功能的脚本化方式,不过通过这种方式实现的表达式仅限于当前报表有效,如果用户想要定义一套全局都有效的表达式该如何操作喃?辛运的是,Pentaho Reporting 有一套扩展系统的方式,能够无侵入性地增加功能。这主要是通过定义模块、加载模块的方式来实现的。
接下来我们将PRD的源码从GitHub上Maven到本地,利用JAVA语言编写相应的功能模块,并将其update到报表工具中,进而扩展PRD的功能!
1、Maven项目到本地
官方github地址:https://github.com/pentaho/pentaho-reporting
小编github地址:https://github.com/TaoPengFei/pentaho-reporting
- 设置settings.xml文件
(1)、 如果你使用IDEA自导的Maven的项目管理,请将settings.xml放置在你本地C盘的/.m2目录下
(2)、 如果你本地创建了Maven构件库,请将settings.xml放置在你Maven安装目录下
我的安装目录是:D:\Maven\apache-maven-3.3.9\conf
- 用IDEA打开Maven项目 File Open -> pom.xml
- 等待Maven依赖包下载完毕,之前没有用过Maven的同学可能得自己学习一下Maven的简单实用,可以参考这篇文章http://blog.csdn.net/myarrow/article/details/50824793 ;
- PRD源码目录结构
2、报表引擎
Pentaho Reporting 包含几大部分:
基础库(libraries)
报表引擎核心(engine core)
其依赖于基础库。代码是 org.pentaho.reporting.engine.classic.core.**
报表扩展(extentions),包括各种数据源适配器,脚本支持等
报表设计器(designer)
3、Libraries基础库
主要基础库的解释
3.1、libformula
libformula是一个提供公式支持的基础库,包括:公式解析、公式运算以及一批预定义的表
达式和函数。公式的语法基于 OpenFormula 标准。
- Function接口
接口 Function 是 libformula 中所有预定义的函数实现的接口。 一个函数是一个任意的运算,
返回的值类型只有在函数运算完成后才可用。函数必须是无状态的,这意味着,使用完全相
同的参数调用相同的函数必须总是返回相同的计算结果。当需要自定义函数时,如果函数跟报表状态无关,并且内部不需要访问报表的当前数据行,那么可以选择实现 libformula 的 Function 接口。
Function 接口只有两个方法:
package org.pentaho.reporting.libraries.formula.function;
import org.pentaho.reporting.libraries.formula.EvaluationException;
import org.pentaho.reporting.libraries.formula.FormulaContext;
import org.pentaho.reporting.libraries.formula.lvalues.TypeValuePair;
import java.io.Serializable;
public interface Function extends Serializable {
public String getCanonicalName();
public TypeValuePair evaluate( FormulaContext context,
ParameterCallback parameters )
throws EvaluationException;
}
- Expression 接口
在报表引擎核心模块的 function 包有一个 Expression 接口,表示一个表达式。表达式不维护
状态,因此是轻量级的函数。表达式用于在报表的一行内计算值,可以用一个 dataRow 变
量来访问这个报表内当前行的其他字段、表达式或函数。
Expression 接口主要的方法有:
package org.pentaho.reporting.engine.classic.core.function;
import org.pentaho.reporting.engine.classic.core.DataRow;
import org.pentaho.reporting.engine.classic.core.ResourceBundleFactory;
import org.pentaho.reporting.libraries.base.config.Configuration;
import java.io.Serializable;
public interface Expression extends Cloneable, Serializable {
public String getName();
public void setName( String name );
public Object getValue();
public boolean isActive();
public DataRow getDataRow();
public Object clone() throws CloneNotSupportedException;
public int getDependencyLevel();
public void setDependencyLevel( int level );
public Expression getInstance();
public ResourceBundleFactory getResourceBundleFactory();
public Configuration getReportConfiguration();
public void setRuntime( ExpressionRuntime runtime );
public ExpressionRuntime getRuntime();
public boolean isDeepTraversing();
public boolean isPreserve();
}
4、实现SLEEP()函数
现在自定义一个SLEEP()的函数。可以通过实现 LibFormula 库 Function 接口的方式来自定义函数。
4.1、定义函数分类类
用于创建新的Others分类。
package org.pentaho.reporting.libraries.formula.function.others;
import org.pentaho.reporting.libraries.formula.function.AbstractFunctionCategory;
import org.pentaho.reporting.libraries.formula.function.FunctionCategory;
/**
* Created by 陶鹏飞 on 2017/3/6.
*/
public final class OthersFunctionCategory extends AbstractFunctionCategory {
public static final FunctionCategory CATEGORY = new OthersFunctionCategory();
private OthersFunctionCategory(){
super("org.pentaho.reporting.libraries.formula.function.others.category");
}
}
4.2、定义函数类
重点是实现 evaluate 方法。
package org.pentaho.reporting.libraries.formula.function.others;
import org.pentaho.reporting.libraries.formula.EvaluationException;
import org.pentaho.reporting.libraries.formula.FormulaContext;
import org.pentaho.reporting.libraries.formula.LibFormulaErrorValue;
import org.pentaho.reporting.libraries.formula.function.Function;
import org.pentaho.reporting.libraries.formula.function.ParameterCallback;
import org.pentaho.reporting.libraries.formula.lvalues.TypeValuePair;
import org.pentaho.reporting.libraries.formula.typing.Type;
import org.pentaho.reporting.libraries.formula.typing.coretypes.LogicalType;
/**
* Created by 陶鹏飞 on 2017/3/3.
*/
public class SleepFunction implements Function {
private static final long serialVersionUID = 4984027687466610131L;
private static final TypeValuePair RETURN_INTERRUPTED = new TypeValuePair( LogicalType.TYPE, Boolean.FALSE );
private static final TypeValuePair RETURN_UNINTERRUPTED = new TypeValuePair(LogicalType.TYPE,Boolean.TRUE);
public SleepFunction(){
// Constructor
}
@Override
/* (non-Javadoc)
* @see org.pentaho.reporting.libraries.formula.function.Function#getCanonicalName()
*/
public String getCanonicalName() {
return "SLEEP";
}
@Override
/* (non-Javadoc)
* @see org.pentaho.reporting.libraries.formula.function.Function#evaluate(org.pentaho.reporting.libraries.formula.FormulaContext, org.pentaho.reporting.libraries.formula.function.ParameterCallback)
*/
public TypeValuePair evaluate(FormulaContext context, ParameterCallback parameters) throws EvaluationException {
final int parameterCount = parameters.getParameterCount();
if ( parameterCount < 1 ){
throw new EvaluationException(LibFormulaErrorValue.ERROR_ARGUMENTS_VALUE);
}
final Type type1 = parameters.getType(0);
final Object value1 = parameters.getValue(0);
final Number result = context.getTypeRegistry().convertToNumber(type1,value1);
if ( result == null || (result.intValue() < 0) ){
throw new EvaluationException(LibFormulaErrorValue.ERROR_INVALID_ARGUMENT_VALUE);
}
Boolean sleepInterrupted = false;
// The number should be the number of milliseconds to sleep
try {
Thread.sleep(result.intValue());
} catch (InterruptedException e) {
//e.printStackTrace();
sleepInterrupted = true;
}
// Whether the function was interrupted or completed.
return sleepInterrupted ? RETURN_INTERRUPTED : RETURN_UNINTERRUPTED;
}
}
4.3、定义函数描述类
需要为函数定义一个函数描述类。需要调用父构造函数的方法,以加载资源。
package org.pentaho.reporting.libraries.formula.function.others;
import org.pentaho.reporting.libraries.formula.function.AbstractFunctionDescription;
import org.pentaho.reporting.libraries.formula.function.FunctionCategory;
import org.pentaho.reporting.libraries.formula.function.information.InformationFunctionCategory;
import org.pentaho.reporting.libraries.formula.typing.Type;
import org.pentaho.reporting.libraries.formula.typing.coretypes.LogicalType;
import org.pentaho.reporting.libraries.formula.typing.coretypes.NumberType;
/**
* Created by 陶鹏飞 on 2017/3/3.
*/
public class SleepFunctionDescription extends AbstractFunctionDescription {
private static final long serialVersionUID = 2368106667495213328L;
public SleepFunctionDescription() {
super("SLEEP", "org.pentaho.reporting.libraries.formula.function.others.Sleep-Function");
}
@Override
public Type getValueType() {
return LogicalType.TYPE;
}
@Override
public FunctionCategory getCategory() {
return OthersFunctionCategory.CATEGORY;
}
@Override
public int getParameterCount() {
return 1; // 1 Parameter - number of milliseconds
}
/**
* Returns the parameter type at the given position using the function metadata. The first parameter is at the
* position 0;
*
* @param position The parameter index.
* @return The parameter type.
*/
@Override
public Type getParameterType(int position) {
return NumberType.GENERIC_NUMBER;
}
/**
* Defines, whether the parameter at the given position is mandatory. A mandatory parameter must be filled in, while
* optional parameters need not to be filled in.
*
* @param position
* @return
*/
@Override
public boolean isParameterMandatory(int position) {
return true;
}
}
4.4、定义资源文件
需要定义一个资源文件用于界面显示。
- Others分类资源文件
#
# Created by 陶鹏飞 on 2017/3/6.
#
display-name=Others
description=Contains some user-defined statistical functions.
- 说明函数资源文件(国际化)
#
# Created by 陶鹏飞 on 2017/3/6.
#
display-name=SLEEP
description=Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds.
parameter.0.display-name=Number
parameter.0.description=The number of milliseconds to sleep (positive number only).
#
# Created by 陶鹏飞 on 2017/3/6.
#
display-name=SLEEP
description=\u4f7f\u5f53\u524d\u6267\u884c\u7684\u7ebf\u7a0b\u4e3a\u6307\u5b9a\u7684\u6beb\u79d2\u6570\u4f11\u7720\uff08\u6682\u65f6\u505c\u6b62\u6267\u884c\uff09.
parameter.0.display-name=Number
parameter.0.description=\u7761\u7720\u7684\u6beb\u79d2\u6570\uff08\u4ec5\u6b63\u6570).
4.5、注册函数
为了把函数注册到 libformula 模块,需要增加一个名为 libformula.properties 的属性文件,内容如下:
##
# Others functions
org.pentaho.reporting.libraries.formula.functions.others.Sleep.class=org.pentaho.reporting.libraries.formula.function.others.SleepFunction
org.pentaho.reporting.libraries.formula.functions.others.Sleep.description=org.pentaho.reporting.libraries.formula.function.others.SleepFunctionDescription
4.6、使用及效果
利用IDEA将libformula文件夹编译成libformula-6.1.0.1-196.jar包,并替换PRD报表开发工具目录D:\prd-ce-6.1.0.1-196\report-designer\lib下的libformula-6.1.0.1-196.jar包,重启 PRD。
效果展示:
四、在步骤三的基础上实现REGE()函数
现在根据步骤三自定义一个REGE()函数。因为不需要访问报表状态和数据行,所以可以通过
实现 LibFormula 库 Function 接口的方式来自定义函数。我们将REGE()放置在Others分类下,所以不需要在创建定义函数分类的JAVA类了。
4.1、定义函数类
首先要实现一个函数类,这里是 RegexFunction。重点是实现 evaluate 方法。
package org.pentaho.reporting.libraries.formula.function.others;
import org.pentaho.reporting.libraries.formula.EvaluationException;
import org.pentaho.reporting.libraries.formula.FormulaContext;
import org.pentaho.reporting.libraries.formula.LibFormulaErrorValue;
import org.pentaho.reporting.libraries.formula.function.Function;
import org.pentaho.reporting.libraries.formula.function.ParameterCallback;
import org.pentaho.reporting.libraries.formula.lvalues.TypeValuePair;
import org.pentaho.reporting.libraries.formula.typing.TypeRegistry;
import org.pentaho.reporting.libraries.formula.typing.coretypes.TextType;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by 陶鹏飞 on 2017/3/3.
*/
public class RegexFunction implements Function{
@Override
public String getCanonicalName() {
return "REGEX";
}
@Override
public TypeValuePair evaluate(FormulaContext context, ParameterCallback parameters) throws EvaluationException {
if ( parameters.getParameterCount() != 2 ){
throw new EvaluationException(LibFormulaErrorValue.ERROR_ARGUMENTS_VALUE);
}
final TypeRegistry typeRegistry = context.getTypeRegistry();
final String param1 = typeRegistry.convertToText( parameters.getType(0), parameters.getValue(0) );
final String param2 = typeRegistry.convertToText( parameters.getType(1), parameters.getValue(1) );
try {
final Pattern pattern = Pattern.compile(param1);
final Matcher matcher = pattern.matcher(param2);
matcher.find();
return new TypeValuePair(TextType.TYPE, matcher.group(1));
}catch (Exception e){
return new TypeValuePair(TextType.TYPE,e.getMessage());
}
}
}
4.2、定义函数描述类
需要为函数定义一个函数描述类。需要调用父构造函数的方法,以加载资源。
package org.pentaho.reporting.libraries.formula.function.others;
import org.pentaho.reporting.libraries.formula.function.AbstractFunctionDescription;
import org.pentaho.reporting.libraries.formula.function.FunctionCategory;
import org.pentaho.reporting.libraries.formula.typing.Type;
import org.pentaho.reporting.libraries.formula.typing.coretypes.LogicalType;
import org.pentaho.reporting.libraries.formula.typing.coretypes.TextType;
/**
* Created by 陶鹏飞 on 2017/3/3.
*/
public class RegexFunctionDescription extends AbstractFunctionDescription{
public RegexFunctionDescription(){
//确保调用父构造函数,带上函数名和函数的资源包名
super("REGEX","org.pentaho.reporting.libraries.formula.function.others.Regex-Function");
}
//把函数放到Others分类中
@Override
public FunctionCategory getCategory() {
return OthersFunctionCategory.CATEGORY;
}
//函数返回2个参数
@Override
public int getParameterCount() {
return 2;
}
/**
* Returns the parameter type at the given position using the function metadata. The first parameter is at the
* position 0;
*
* @param position The parameter index.
* @return The parameter type.
*/
@Override
public Type getParameterType(int position) {
return TextType.TYPE;
}
@Override
public Type getValueType() {
return LogicalType.TYPE;
}
/**
* Defines, whether the parameter at the given position is mandatory. A mandatory parameter must be filled in, while
* optional parameters need not to be filled in.
*
* @param position
* @return
*/
@Override
public boolean isParameterMandatory(int position) {
return true;
}
}
4.3、资源文件(国际化)
需要定义一个资源文件用于界面显示。这里叫 Regex-Function.properties。
#
# Created by 陶鹏飞 on 2017/3/6.
#
display-name=REGEX
description=Executes a regular expression on a string, returning the first found group
parameter.0.display-name=Regular Expression
parameter.0.description=A Java Regular Expression string, with a grouping defined within the string.
parameter.1.display-name=String Input
parameter.1.description=A string to parse.
#
# Created by 陶鹏飞 on 2017/3/6.
#
display-name=REGEX
description=\u5728\u5b57\u7b26\u4e32\u4e0a\u6267\u884c\u6b63\u5219\u8868\u8fbe\u5f0f\uff0c\u8fd4\u56de\u7b2c\u4e00\u4e2a\u5df2\u627e\u5230\u7684\u7ec4\u3002
parameter.0.display-name=Regular Expression
parameter.0.description=\u4e00\u4e2ajava\u6b63\u5219\u8868\u8fbe\u5f0f\u7684\u5b57\u7b26\u4e32\uff0c\u7528\u5b57\u7b26\u4e32\u4e2d\u5b9a\u4e49\u7684\u5206\u7ec4\u3002
parameter.1.display-name=String Input
parameter.1.description=\u89e3\u6790\u5b57\u7b26\u4e32\u3002
4.4、注册函数
为了把函数注册到 libformula 模块,需要增加一个名为 libformula.properties 的属性文件,内容如下:
##
# Others functions
org.pentaho.reporting.libraries.formula.functions.others.Sleep.class=org.pentaho.reporting.libraries.formula.function.others.SleepFunction
org.pentaho.reporting.libraries.formula.functions.others.Sleep.description=org.pentaho.reporting.libraries.formula.function.others.SleepFunctionDescription
org.pentaho.reporting.libraries.formula.functions.others.Regex.class=org.pentaho.reporting.libraries.formula.function.others.RegexFunction
org.pentaho.reporting.libraries.formula.functions.others.Regex.description=org.pentaho.reporting.libraries.formula.function.others.RegexFunctionDescription
4.5、使用及效果
五、在步骤三的基础上实现REGE()表达式
、、、
六、相关资源下载
- libformula-6.1.0.1-196.jar包下载:http://download.csdn.net/detail/github_37559821/9774131
- 示例报表文件下载:http://download.csdn.net/detail/github_37559821/9774156