Pentaho是一个以工作流为核心的、强调面向解决方案而非工具组件的BI套件,整合了多个开源项目,目标是和商业BI相抗衡。它偏向于与业务流程相结合的BI解决方案,侧重于大中型企业应用。它允许商业分析人员或开发人员创建报表,仪表盘,分析模型,商业规则和 BI 流程。
在开发Pentaho插件的过程中,最直接遇到的问题是,Pentaho启动速度太慢,我们想测试我们写的插件的时候,必须重新打jar包,并覆盖相关的配置,再重新启动Pentaho加载插件,这样一来,测试的效率就及其低下了,在对Pentaho不熟悉的情况下,想要通过Pentaho那少的及其可怜的文档,和Pentaho的Tomcat日志来定位分析解决插件中的bug,就更难了。
所以搭建一个Pentaho插件的快速开发环境就很有必要了,以下将围绕这点展开,所用的Pentaho的版本是7.0
想要开发Pentaho的插件,我们重点关注两个文件夹,“/pentaho-solutions/system”和“tomcat”,一个是将来插件放置的位置,另一个是Pentaho启动用到的内置Tomcat,将来开发部署到Pentaho进行测试的过程中,需要查看Tomcat的log。
可先参考Pentaho官方的这篇文档,文档有点旧,但大体上做个参考没太大的问题。
Developing Plugins https://wiki.pentaho.com/display/ServerDoc2x/Developing+Plugins
Pentaho插件需要在“/pentaho-solutions/system”下新建一个目录,我们假定为“my-plugin”。
完成以上步骤后,启动pentaho,在logs文件夹中的pentaho.log可以看到插件的加载情况。
++该快速开发环境最终使用的框架主要有 spring,jersey,mybatis++,我们需要达成的目标是
plugins {
id 'java'
id 'idea'
}
group 'com.xxx'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile group: 'org.hibernate.validator', name: 'hibernate-validator', version: '6.0.13.Final'
compile group: 'com.sun.jersey', name: 'jersey-core', version: '1.19.1'
compile group: 'com.sun.jersey', name: 'jersey-server', version: '1.19.1'
compile group: 'com.sun.jersey', name: 'jersey-client', version: '1.19.1'
compile(group: 'com.sun.jersey', name: 'jersey-json', version: '1.19.1')
compile group: 'com.sun.jersey', name: 'jersey-bundle', version: '1.19.4'
compile group: 'com.sun.jersey.contribs', name: 'jersey-multipart', version: '1.19.1'
compile group: 'com.sun.jersey.contribs', name: 'jersey-spring', version: '1.19.1'
compile group: 'com.sun.jersey', name: 'jersey-servlet', version: '1.19.1'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: '2.0.5.RELEASE'
compile group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '1.3.2'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.0.5.RELEASE'
compile group: 'org.springframework.boot', name: 'spring-boot-devtools', version: '2.0.5.RELEASE'
runtime "mysql:mysql-connector-java:8.0.12"
compile group: 'com.microsoft.sqlserver', name: 'mssql-jdbc', version: '6.1.0.jre8'
compile 'org.apache.commons:commons-dbcp2:2.5.0'
compile 'commons-dbcp:commons-dbcp:1.4'
compile group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1'
compile fileTree(dir: 'jars', include: ['*.jar'])
testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '2.0.5.RELEASE'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
//run "gradle build copyJars"
//run "gradle copyJars"
task copyJars(type: Copy) {
from configurations.runtime
into './build/libs'
}
关注几个重点:
kettle-core-7.0.0.0-25.jar
kettle-engine-7.0.0.0-25.jar
metastore-7.0.0.0-25.jar
pentaho-database-model-7.0.0.0-25.jar
pentaho-platform-api-7.0.0.0-25.jar
pentaho-platform-build-utils-7.0.0.0-25.jar
pentaho-platform-core-7.0.0.0-25.jar
pentaho-platform-extensions-7.0.0.0-25.jar
pentaho-platform-repository-7.0.0.0-25.jar
pentaho-platform-scheduler-7.0.0.0-25.jar
pentaho-reporting-engine-classic-core-7.0.0.0-25.jar
pentaho-reporting-engine-classic-core-platform-plugin-7.0.0.0-25.jar
新建类 JerseyConfig
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class JerseyConfig implements ServletContextInitializer {
public JerseyConfig() {
}
@Override
public void onStartup(ServletContext servletContext) {
final SpringServlet servlet = new SpringServlet();
final ServletRegistration.Dynamic appServlet = servletContext.addServlet("appServlet", servlet);
appServlet.setLoadOnStartup(1);
final Set<String> mappingConflicts = appServlet.addMapping("/*");
if (!mappingConflicts.isEmpty()) {
throw new IllegalStateException("'appServlet' cannot be mapped to '/' under Tomcat versions <= 7.0 .14 ");
}
}
}
这里需考虑,在最后部署到pentaho上的时候,是需要使用pentaho提供的数据源接口的,而在开发时,我们只需要直连数据库就可以了。
在plugin.spring.xml中增加bean,如下:
<bean id="dataSource" class="com.xxx.xxx.DbConfig" factory-method="getDataSource"/>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/xxx/mapper/**/*Mapper.xml"/>
<property name="typeAliasesPackage" value="com.xxx.xxx.xxx"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.xxx.xxx.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
新建类DbConfig,假设pentaho的数据源是 pth,代码如下:
public class DbConfig {
public static final String DEV = "DEV";
public static final String PROD = "PROD";
public static DataSource getDataSource() {
String current = "DEV";
if (current.equals(DEV)) {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/zzz?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
} else if (current.equals(PROD)) {
return new LazyDataSource();
}
return null;
}
static class LazyDataSource extends AbstractDataSource {
@Override
public Connection getConnection() {
try {
return getDataSource().getConnection();
} catch (ObjectFactoryException | DBDatasourceServiceException | SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
public Connection getConnection(String username, String password) {
try {
return getDataSource().getConnection(username, password);
} catch (ObjectFactoryException | DBDatasourceServiceException | SQLException e) {
e.printStackTrace();
}
return null;
}
public DataSource getDataSource() throws ObjectFactoryException, DBDatasourceServiceException {
final IDBDatasourceService datasourceService = PentahoSystem.getObjectFactory()
.get(IDBDatasourceService.class, PentahoSessionHolder.getSession());
return datasourceService.getDataSource("pth");
}
}
}
定义的“mybatis-config.xml”,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
<setting name="logImpl" value="LOG4J" />
</settings>
</configuration>
api开发涉及的几个要点:
在“plugin.spring.xml”中添加
<bean id="exampleApi" class="com.xxx.xxx.api.ExampleApi" scope="prototype">
<property name="exampleService" ref="exampleService"/>
</bean>
可以看到,这和普通的spring mvc添加bean方式是一样的,因为pentaho内置了spring。
但我尝试通过注解的方式添加bean,失败了。
新建类 ExampleApi
public class ExampleApi {
private SalesForecastingService salesForecastingService;
@Inject
public SalesForecastingApi(SalesForecastingService salesForecastingService) {
this.salesForecastingService = salesForecastingService;
}
public SalesForecastingService getSalesForecastingService() {
return salesForecastingService;
}
public void setSalesForecastingService(SalesForecastingService salesForecastingService) {
this.salesForecastingService = salesForecastingService;
}
}
@Inject是javax.inject包下的注解。
在ExampleApi添加方法后,内容如下:
@Path("/xxx/api/yyy")
public class ExampleApi {
private ExampleService exampleService;
@Inject
public ExampleApi(ExampleService exampleService) {
this.exampleService = exampleService;
}
@GET
@Path("/getExample")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
public String getExample(@QueryParam("name") String name) {
return "hello " + name;
}
@POST
@Path("/postExample")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.TEXT_PLAIN)
public String postExample(@FormParam("name") String name) {
return "hello" + name;
}
@POST
@Path("/uploadExample")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.TEXT_PLAIN)
public String uploadExample(
@QueryParam("name") String name,
@FormDataParam("file") InputStream file) {
return "导入成功";
}
public ExampleService getExampleService() {
return exampleService;
}
public void setExampleService(ExampleService exampleService) {
this.exampleService = exampleService;
}
}
现在来分析下代码
service层就比较简单了,如下:
新建类ExampleService
public class ExampleService {
ExampleMapper exampleMapper;
public String hello() {
return "hello world";
}
public ExampleMapper getExampleMapper() {
return exampleMapper;
}
public void setExampleMapper(ExampleMapper exampleMapper) {
this.exampleMapper = exampleMapper;
}
}
同时注意“plugin.spring.xml”,我们之前就在api里面定义了该service了,现在也要定义service,并注入mapper。
<bean id="exampleService" class="com.xxx.xxx.service.ExampleService" scope="prototype">
<property name="exampleMapper" ref="exampleMapper"/>
</bean>
mapper 与 model层和通常的并没有太大区别,只是注意下,要在“plugin.spring.xml”里面定义mapper的搜索位置,不需要在mapper类上注解@Mapper。参考 3.3 关联使用MyBatis
因为我们引入了spring boot,所以在测试的环境下一样需要Application。代码很简单,如下:
@SpringBootApplication
@ImportResource(value = {"classpath:plugin.spring.xml"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
注意,我们没有定义application.yaml,也没有@Controller,@Service的注解,所以此处,我们一样通过@ImportResource来注解类。
至此,“plugin.spring.xml”文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<!--测试环境下注释此行-->
<bean id="api" class="org.pentaho.platform.web.servlet.JAXRSPluginServlet"/>
<context:annotation-config/>
<bean id="dataSource" class="com.xxx.xxx.DbConfig" factory-method="getDataSource"/>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/xxx/xxx/mapper/**/*Mapper.xml"/>
<property name="typeAliasesPackage" value="com.xxx.xxx.xxx.entity"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.xxx.xxx.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<bean id="exampleApi" class="com.xxx.xxx.api.ExampleApi" scope="prototype">
<property name="exampleService" ref="exampleService"/>
</bean>
<bean id="exampleService" class="com.xxx.xxx.service.ExampleService" scope="prototype">
<property name="exampleMapper" ref="exampleMapper"/>
</bean>
</beans>
上面是一个最简单的案例,可根据自己的情况修改。
新手最容易遇到的就是插件加载失败的问题,造成这个问题,可能性有很多种,但主要集中在
pentaho的插件,在目前看来,更适合做一些功能单一,不太复杂的东西,如果涉及到太多数据库的各种增删改查,甚至有流程性,管理性的需求,就不太合适了。如果是有复杂的业务流程,个人的解决方案是,独立研发一套软件,结合实际的业务流程,部分涉及权限等的功能点也可利用pentaho的api接口,独立部署运行,会更合适一些,便于开发调试运行,开发成本会更低一些。事实上,本文很多内容,归根到底,就是JAXB的标准+spring4.3.2的结合。个人对pentaho也是刚接触,踩了不少坑,也还没细细研读pentaho的源码,可能本文也有不少缺漏及错误,但大体上是没有问题的,仅供参考,如有错误,烦请指出,承蒙指教,不胜感激。