Mondrian之XMLA服务

XMLA是一个基于SOAP的XML协议,是专为访问多维数据库设计的。最初好像是由微软定义的,不过由于是采用的SOAP协议,所以可以很轻松的被各种平台使用。

Mondrian作为一个OLAP引擎自然也提供了发布XMLA服务的功能,下面的实例将介绍如何利用Mondrian发布一个XMLA服务,以及客户端如何利用XMLA服务访问元数据和执行MDX查询。

数据我们还是采用上一个实例用到的销售模型,详见http://blog.csdn.net/chch87/article/details/23953731

首先是服务端,Mondrian并没有用WebService的实现,而是通过servlet来发布服务。Mondrian一共提供了三个servlet:MondrianXmlaServlet,基于mondrian引擎的实现。DynamicDatasourceXmlaServlet,在MondrianXmlaServlet的基础上增加了动态加载数据源的能力。Olap4jXmlaServlet,基于Olap4j的实现。这里就介绍一下MondrianXmlaServlet和Olap4jXmlaServlet。


MondrianXmlaServlet

首先是数据源的配置,WEB-INF下建立一个datasources.xml文件如下(我是在官方demo上改的):

<?xml version="1.0"?>
<DataSources>
	<DataSource>
		<DataSourceName>Sample</DataSourceName>
		<DataSourceDescription>Mondrian Sample</DataSourceDescription>
		<URL>http://127.0.0.1:8080/XmlaServer/xmla</URL>

		<DataSourceInfo>Provider=mondrian;Jdbc='jdbc:derby:classpath:/sample';JdbcDrivers=org.apache.derby.jdbc.EmbeddedDriver;JdbcUser=sa;JdbcPassword=sa;Catalog=http://127.0.0.1:8080/XmlaServer/sample.xml;PoolNeeded=false;
		</DataSourceInfo>
		<!-- Provider name must be 'Mondrian'. -->
		<ProviderName>Mondrian</ProviderName>
		<!-- Provider type must be 'MDP'. -->
		<ProviderType>MDP</ProviderType>
		<!-- Authentication mode. Allowable values are 'Unauthenticated', 'Authenticated', 
			'Integrated'. -->
		<AuthenticationMode>Unauthenticated</AuthenticationMode>

		<!-- A data source contains one or more catalogs. -->
		<Catalogs>
			<Catalog name="sample">
				<!-- Mondrian connect string for the data source. This element is optional; 
					if specified, it overrides the data source's <DataSourceInfo> element. Typically, 
					you would use this element if the catalogs have different JDBC data sources. -->
				<!-- <DataSourceInfo>Provider=mondrian;Jdbc=jdbc:odbc:MondrianFoodMart;JdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver</DataSourceInfo> -->
				<!-- URI of the schema definition file. -->
				<Definition>http://127.0.0.1:8080/XmlaServer/sample.xml</Definition>
			</Catalog>
		</Catalogs>
	</DataSource>
</DataSources>

每个属性的意义官方注释里写的很清楚了,这里就注意两点

一个是DataSourceInfo里的PoolNeeded=false,如果不设置这个属性的话,会出现Could not create a validated object, cause: A read-only user or a user in a read-only database is not permitted to disable read-only mode on a connection.这个错误,原因可能是我采用的derby数据库不支持dbcp的连接池,把PoolNeeded=false;设上不缓存数据库连接就行了。

还有一个就是Catalogs结点的设置,Catalogs是用来对应多Catalog的,但是如果你只有一个Catalog,也不能将Catalogs省略,也就是说同样的Catalog要分别在DataSourceInfo和Catalogs定义两遍。

然后是servlet的配置:

	<servlet>
		<servlet-name>MondrianXmlaServlet</servlet-name>
		<servlet-class>mondrian.xmla.impl.MondrianXmlaServlet</servlet-class>
		<init-param>
			<param-name>CharacterEncoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
	</servlet>

	<servlet-mapping>
		<servlet-name>MondrianXmlaServlet</servlet-name>
		<url-pattern>/xmla</url-pattern>
	</servlet-mapping>

这里有个CharacterEncoding的设置,主要是为了防止SOAP请求中包含中文。但仅仅是这样并不能解决所有的中文问题,前一篇文章中就提到了引擎内部对模式中的中文解析有bug,解决办法只能是修改源码了,这里我向它妥协,把模式定义(sample.xml)全修改成了英文。至此MondrianXmlaServlet的配置就完成了。


Olap4jXmlaServlet

官方并没有给出Olap4jXmlaServlet的demo,实际用起来也确实有些问题,看来不推荐使用。跟上面不一样,没有datasources的设置,看样子也不支持多数据源。直接就是servlet的配置了:

	<servlet>
		<servlet-name>Olap4jXmlaServlet</servlet-name>
		<servlet-class>mondrian.xmla.impl.Olap4jXmlaServlet</servlet-class>
		<init-param>
			<param-name>OlapDriverClassName</param-name>
			<param-value>mondrian.olap4j.MondrianOlap4jDriver</param-value>
		</init-param>
		<init-param>
			<param-name>OlapDriverConnectionString</param-name>
			<param-value>jdbc:mondrian:Jdbc='jdbc:derby:classpath:/sample';JdbcDrivers=org.apache.derby.jdbc.EmbeddedDriver;JdbcUser=sa;JdbcPassword=sa;Catalog=http://127.0.0.1:8080/XmlaServer/sample.xml;PoolNeeded=false;</param-value>
		</init-param>
	</servlet>

	<servlet-mapping>
		<servlet-name>Olap4jXmlaServlet</servlet-name>
		<url-pattern>/olap4jXmla</url-pattern>
	</servlet-mapping>

如上所示,指定了Olap4j的驱动和url就行了,本来这样应该就可以了的,可实际调用的时候,出现了下面的错误:

Caused by: java.lang.UnsupportedOperationException
	at mondrian.olap4j.MondrianOlap4jConnection.rollback(MondrianOlap4jConnection.java:272)
	at org.apache.commons.dbcp.DelegatingConnection.rollback(DelegatingConnection.java:265)
	at org.apache.commons.dbcp.PoolableConnectionFactory.passivateObject(PoolableConnectionFactory.java:352)
	at org.apache.commons.pool.impl.GenericObjectPool.addObjectToPool(GenericObjectPool.java:1430)
	at org.apache.commons.pool.impl.GenericObjectPool.addObject(GenericObjectPool.java:1694)
	at org.apache.commons.dbcp.BasicDataSource.createDataSource(BasicDataSource.java:865)
	... 31 more

debug跟到dbcp的代码,会发现BasicDataSource初始化连接的时候会检查新创建的连接,当连接不能自动提交的时候会尝试rollback,这里的数据连接并不是关系数据库的连接而是Olap4j连接,尽管Olap4j连接也符合JDBC标准,但也并不是实现了JDBC的每一个接口,比如这个rollback就没实现,那自然就会报错了。这又是一个BUG了,而且不改不行,应该是Olap4j数据连接与BasicDataSource不兼容。下面就是我的修改,NewOlap4jXmlaServlet继承了Olap4jXmlaServlet,重新简单的实现了createConnectionFactory方法。(这里插句题外话,Mondrian采用的开源协议是EPL,也就是意味着基于它的非独立模块也必须开源,比如BUG修正之类的,所以拿来做商用的要忘开了小心侵权哦。。)

package xmlaserver;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;

import mondrian.xmla.XmlaHandler;
import mondrian.xmla.impl.Olap4jXmlaServlet;

import org.olap4j.OlapConnection;

@SuppressWarnings("serial")
public class NewOlap4jXmlaServlet extends Olap4jXmlaServlet {

    @Override
    protected XmlaHandler.ConnectionFactory createConnectionFactory(ServletConfig servletConfig)
            throws ServletException {

        final String olap4jDriverClassName = servletConfig.getInitParameter("OlapDriverClassName");
        final String olap4jDriverConnectionString = servletConfig
                .getInitParameter("OlapDriverConnectionString");

        return new XmlaHandler.ConnectionFactory() {

            @Override
            public OlapConnection getConnection(String catalog, String schema, String roleName,
                    Properties props) throws SQLException {

                try {
                    Class.forName(olap4jDriverClassName);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                Connection olap4jConn = DriverManager.getConnection(olap4jDriverConnectionString);
                return olap4jConn.unwrap(OlapConnection.class);
            }

            @Override
            public Map<String, Object> getPreConfiguredDiscoverDatasourcesResponse() {
                return null;
            }

        };
    }
}


servlet的配置也稍作修改

	<servlet>
		<servlet-name>Olap4jXmlaServlet</servlet-name>
		<!-- <servlet-class>mondrian.xmla.impl.Olap4jXmlaServlet</servlet-class> -->
		<servlet-class>xmlaserver.NewOlap4jXmlaServlet</servlet-class>
		<init-param>
			<param-name>OlapDriverClassName</param-name>
			<param-value>mondrian.olap4j.MondrianOlap4jDriver</param-value>
		</init-param>
		<init-param>
			<param-name>OlapDriverConnectionString</param-name>
			<param-value>jdbc:mondrian:Jdbc='jdbc:derby:classpath:/sample';JdbcDrivers=org.apache.derby.jdbc.EmbeddedDriver;JdbcUser=sa;JdbcPassword=sa;Catalog=http://127.0.0.1:8080/XmlaServer/sample.xml;PoolNeeded=false;</param-value>
		</init-param>
	</servlet>

	<servlet-mapping>
		<servlet-name>Olap4jXmlaServlet</servlet-name>
		<url-pattern>/olap4jXmla</url-pattern>
	</servlet-mapping>


至此服务端的介绍就结束了,下面开始介绍客户端的用法。

Olap4j已经实现一个XMLA客户端,所以客户端只要引入Olap4j相关jar包就可以了。下面是一个具体实例

package xmlaclient;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;

import org.olap4j.CellSet;
import org.olap4j.OlapConnection;
import org.olap4j.OlapDatabaseMetaData;
import org.olap4j.OlapStatement;
import org.olap4j.layout.CellSetFormatter;
import org.olap4j.layout.RectangularCellSetFormatter;

public class TestXmla {

    public static void main(String[] args) throws Exception {
        
        String url = "http://127.0.0.1:8080/XmlaServer/xmla";
        String userName = "sa";
        String password = "sa";

        // 加载驱动
        Class.forName("org.olap4j.driver.xmla.XmlaOlap4jDriver");
        
        // 建立连接
        String strUrl = "jdbc:xmla:Server=" + url;
        strUrl += ";JdbcUser=" + userName;
        strUrl += ";JdbcPassword=" + password;
        
        Connection olap4jConn = DriverManager.getConnection(strUrl);
        OlapConnection olapConn = olap4jConn.unwrap(OlapConnection.class);
        
        OlapDatabaseMetaData databaseMetaData = olapConn.getMetaData();
        // 获取数据立方体信息
        ResultSet resultSet = databaseMetaData.getCubes(null, null, null);
        while (resultSet.next()) {
            System.out.println("CUBE_NAME:" + resultSet.getString("CUBE_NAME"));
        }
        
        // 获取维度信息
        resultSet = databaseMetaData.getHierarchies(null, null, "Sales", null, null);
        while (resultSet.next()) {
            System.out.println("HIERARCHY_NAME:" + resultSet.getString("HIERARCHY_NAME"));
            
        }
        
        
        // 执行查询
//        String mdxStr = "select {[Stores].Members} on columns,"
//                  + "{[Time].Members} ON rows" 
//                  + " from Sales ";
        String mdxStr = "select {[Measures].[Sales],[Measures].[Profit]} on columns,"
                + "{[Quarter].Members} ON rows" 
                + " from Sales " 
                + "where [Store].[支店01]";
        OlapStatement statement = olapConn.createStatement();
        CellSet cellSet = statement.executeOlapQuery(mdxStr);

        // 输出结果
        CellSetFormatter formatter = new RectangularCellSetFormatter(false);
        formatter.format(cellSet, new PrintWriter(System.out, true));
        
    }

}

当服务端的web程序启动后,客户端就可以通过xmla服务url(http://127.0.0.1:8080/XmlaServer/xmla)来访问多维数据的元数据和执行mdx查询了,非常简单。


最后,给出该实例的完整代码:http://download.csdn.net/detail/chch87/7216471

猜你喜欢

转载自blog.csdn.net/chch87/article/details/24059895