/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.extensions.datasources.mondrian;
import mondrian.mdx.MemberExpr;
import mondrian.olap.CacheControl;
import mondrian.olap.Connection;
import mondrian.olap.Cube;
import mondrian.olap.Exp;
import mondrian.olap.Hierarchy;
import mondrian.olap.Literal;
import mondrian.olap.Member;
import mondrian.olap.MondrianException;
import mondrian.olap.MondrianProperties;
import mondrian.olap.OlapElement;
import mondrian.olap.Parameter;
import mondrian.olap.Position;
import mondrian.olap.Query;
import mondrian.olap.Result;
import mondrian.olap.Util;
import mondrian.olap.type.MemberType;
import mondrian.olap.type.NumericType;
import mondrian.olap.type.SetType;
import mondrian.olap.type.StringType;
import mondrian.olap.type.Type;
import mondrian.server.Statement;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.AbstractDataFactory;
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
import org.pentaho.reporting.engine.classic.core.DataFactory;
import org.pentaho.reporting.engine.classic.core.DataFactoryContext;
import org.pentaho.reporting.engine.classic.core.DataRow;
import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException;
import org.pentaho.reporting.engine.classic.core.util.PropertyLookupParser;
import org.pentaho.reporting.libraries.base.config.Configuration;
import org.pentaho.reporting.libraries.base.util.CSVTokenizer;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.formatting.FastMessageFormat;
import java.lang.reflect.Array;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import java.util.regex.PatternSyntaxException;
/**
* This data-factory operates in Legacy-Mode providing a preprocessed view on the mondrian result. It behaves exactly as
* known from the Pentaho-Platform and the Pentaho-Report-Designer. This mode of operation breaks the structure of the
* resulting table as soon as new rows are returned by the server.
*
* @author Thomas Morgner
*/
public abstract class AbstractMDXDataFactory extends AbstractDataFactory {
/**
* The message compiler maps all named references into numeric references.
*/
protected static class MDXCompiler extends PropertyLookupParser {
private HashSet<String> collectedParameter;
private DataRow parameters;
private Locale locale;
/**
* Default Constructor.
*/
protected MDXCompiler( final DataRow parameters,
final Locale locale ) {
if ( locale == null ) {
throw new NullPointerException( "Locale must not be null" );
}
if ( parameters == null ) {
throw new NullPointerException( "Parameter datarow must not be null" );
}
this.collectedParameter = new HashSet<String>();
this.parameters = parameters;
this.locale = locale;
setMarkerChar( '$' );
setOpeningBraceChar( '{' );
setClosingBraceChar( '}' );
}
/**
* Looks up the property with the given name. This replaces the name with the current index position.
*
* @param name the name of the property to look up.
* @return the translated value.
*/
protected String lookupVariable( final String name ) {
final CSVTokenizer tokenizer = new CSVTokenizer( name, false );
if ( tokenizer.hasMoreTokens() == false ) {
// invalid reference ..
return null;
}
final String parameterName = tokenizer.nextToken();
collectedParameter.add( parameterName );
final Object o = parameters.get( parameterName );
String subType = null;
final StringBuilder b = new StringBuilder( name.length() + 4 );
b.append( '{' );
b.append( "0" );
while ( tokenizer.hasMoreTokens() ) {
b.append( ',' );
final String token = tokenizer.nextToken();
b.append( token );
if ( subType == null ) {
subType = token;
}
}
b.append( '}' );
final String formatString = b.toString();
if ( "string".equals( subType ) ) {
if ( o == null ) {
return "null"; // NON-NLS
}
return quote( String.valueOf( o ) );
}
final FastMessageFormat messageFormat = new FastMessageFormat( formatString, locale );
return messageFormat.format( new Object[] { o } );
}
public Set<String> getCollectedParameter() {
return Collections.unmodifiableSet( (Set<String>) collectedParameter.clone() );
}
}
private static final String ACCEPT_ROLES_CONFIG_KEY =
"org.pentaho.reporting.engine.classic.extensions.datasources.mondrian.role-filter.static.accept";
private static final String ACCEPT_REGEXP_CONFIG_KEY =
"org.pentaho.reporting.engine.classic.extensions.datasources.mondrian.role-filter.reg-exp.accept";
private static final String DENY_ROLE_CONFIG_KEY =
"org.pentaho.reporting.engine.classic.extensions.datasources.mondrian.role-filter.static.deny";
private static final String DENY_REGEXP_CONFIG_KEY =
"org.pentaho.reporting.engine.classic.extensions.datasources.mondrian.role-filter.reg-exp.deny";
private static final String ROLE_FILTER_ENABLE_CONFIG_KEY =
"org.pentaho.reporting.engine.classic.extensions.datasources.mondrian.role-filter.enable";
private String jdbcUser;
private String jdbcUserField;
private String jdbcPassword;
private String jdbcPasswordField;
private String dynamicSchemaProcessor;
private Boolean useSchemaPool;
private Boolean useContentChecksum;
private Properties baseConnectionProperties;
private String role;
private String roleField;
private CubeFileProvider cubeFileProvider;
private DataSourceProvider dataSourceProvider;
private MondrianConnectionProvider mondrianConnectionProvider;
private String designTimeName;
private transient Connection connection;
private static final String[] EMPTY_QUERYNAMES = new String[ 0 ];
private static final Log logger = LogFactory.getLog( AbstractMDXDataFactory.class );
private boolean membersOnAxisSorted;
public AbstractMDXDataFactory() {
this.mondrianConnectionProvider =
ClassicEngineBoot.getInstance().getObjectFactory().get( MondrianConnectionProvider.class );
this.baseConnectionProperties = new Properties();
}
public MondrianConnectionProvider getMondrianConnectionProvider() {
return mondrianConnectionProvider;
}
public void setMondrianConnectionProvider( final MondrianConnectionProvider mondrianConnectionProvider ) {
if ( mondrianConnectionProvider == null ) {
throw new NullPointerException();
}
this.mondrianConnectionProvider = mondrianConnectionProvider;
}
public String getDynamicSchemaProcessor() {
return dynamicSchemaProcessor;
}
public void setDynamicSchemaProcessor( final String dynamicSchemaProcessor ) {
this.dynamicSchemaProcessor = dynamicSchemaProcessor;
}
public boolean isMembersOnAxisSorted() {
return membersOnAxisSorted;
}
public void setMembersOnAxisSorted( final boolean membersOnAxisSorted ) {
this.membersOnAxisSorted = membersOnAxisSorted;
}
public Boolean isUseSchemaPool() {
return useSchemaPool;
}
public void setUseSchemaPool( final Boolean useSchemaPool ) {
this.useSchemaPool = useSchemaPool;
}
public Boolean isUseContentChecksum() {
return useContentChecksum;
}
public void setUseContentChecksum( final Boolean useContentChecksum ) {
this.useContentChecksum = useContentChecksum;
}
public String getRole() {
return role;
}
public void setRole( final String role ) {
this.role = role;
}
public String getRoleField() {
return roleField;
}
public void setRoleField( final String roleField ) {
this.roleField = roleField;
}
public CubeFileProvider getCubeFileProvider() {
return cubeFileProvider;
}
public void setCubeFileProvider( final CubeFileProvider cubeFileProvider ) {
this.cubeFileProvider = cubeFileProvider;
}
public DataSourceProvider getDataSourceProvider() {
return dataSourceProvider;
}
public void setDataSourceProvider( final DataSourceProvider dataSourceProvider ) {
this.dataSourceProvider = dataSourceProvider;
}
public String getJdbcUser() {
return jdbcUser;
}
public void setJdbcUser( final String jdbcUser ) {
this.jdbcUser = jdbcUser;
}
public String getJdbcPassword() {
return jdbcPassword;
}
public void setJdbcPassword( final String jdbcPassword ) {
this.jdbcPassword = jdbcPassword;
}
public String getJdbcUserField() {
return jdbcUserField;
}
public void setJdbcUserField( final String jdbcUserField ) {
this.jdbcUserField = jdbcUserField;
}
public String getJdbcPasswordField() {
return jdbcPasswordField;
}
public void setJdbcPasswordField( final String jdbcPasswordField ) {
this.jdbcPasswordField = jdbcPasswordField;
}
public Properties getBaseConnectionProperties() {
return (Properties) baseConnectionProperties.clone();
}
/**
* Sets base connection properties. These will be overriden by any programatically set properties.
*
* @param connectionProperties
*/
public void setBaseConnectionProperties( final Properties connectionProperties ) {
if ( connectionProperties != null ) {
this.baseConnectionProperties.clear();
this.baseConnectionProperties.putAll( connectionProperties );
}
}
/**
* Checks whether the query would be executable by this datafactory. This performs a rough check, not a full query.
*
* @param query
* @param parameters
* @return
*/
public boolean isQueryExecutable( final String query, final DataRow parameters ) {
return true;
}
/**
* Closes the data factory and frees all resources held by this instance.
*/
public void close() {
if ( connection != null ) {
connection.close();
}
connection = null;
}
/**
* Access the cache control on a per-datasource level. Setting "onlyCurrentSchema" to true will selectively purge the
* mondrian cache for the specifc schema only.
*
* @param parameters
* @param onlyCurrentSchema
* @throws ReportDataFactoryException
*/
public void clearCache( final DataRow parameters,
final boolean onlyCurrentSchema ) throws ReportDataFactoryException {
try {
final Connection connection =
mondrianConnectionProvider
.createConnection( computeProperties( parameters ), dataSourceProvider.getDataSource() );
try {
final CacheControl cacheControl = connection.getCacheControl( null );
if ( onlyCurrentSchema ) {
cacheControl.flushSchema( connection.getSchema() );
} else {
cacheControl.flushSchemaCache();
}
} finally {
connection.close();
}
} catch ( SQLException e ) {
logger.error( e );
throw new ReportDataFactoryException(
"Failed to create DataSource (SQL Exception - error code: " + e.getErrorCode() + "):" + e.toString(), e );
} catch ( MondrianException e ) {
logger.error( e );
throw new ReportDataFactoryException( "Failed to create DataSource (Mondrian Exception):" + e.toString(), e );
}
}
/**
* Queries a datasource. The string 'query' defines the name of the query. The Parameterset given here may contain
* more data than actually needed for the query.
* <p/>
* The parameter-dataset may change between two calls, do not assume anything, and do not hold references to the
* parameter-dataset or the position of the columns in the dataset.
*
* @param rawMdxQuery the mdx Query string.
* @param parameters the parameters for the query
* @return the result of the query as table model.
* @throws org.pentaho.reporting.engine.classic.core.ReportDataFactoryException if an error occured while performing
* the query.
*/
public Result performQuery( final String rawMdxQuery, final DataRow parameters ) throws ReportDataFactoryException {
try {
if ( connection == null ) {
connection = mondrianConnectionProvider
.createConnection( computeProperties( parameters ), dataSourceProvider.getDataSource() );
}
} catch ( SQLException e ) {
throw new ReportDataFactoryException( "Failed to create datasource:" + e.getLocalizedMessage(), e );
} catch ( MondrianException e ) {
throw new ReportDataFactoryException( "Failed to create datasource:" + e.getLocalizedMessage(), e );
}
try {
if ( connection == null ) {
throw new ReportDataFactoryException( "Factory is closed." );
}
final MDXCompiler compiler = new MDXCompiler( parameters, getLocale() );
final String mdxQuery = compiler.translateAndLookup( rawMdxQuery, parameters );
// Alternatively, JNDI is possible. Maybe even more ..
final Query query = connection.parseQuery( mdxQuery );
final Statement statement = query.getStatement();
final int queryTimeoutValue = calculateQueryTimeOut( parameters );
if ( queryTimeoutValue > 0 ) {
statement.setQueryTimeoutMillis( queryTimeoutValue * 1000 );
}
parametrizeQuery( parameters, query );
//noinspection deprecation
final Result resultSet = connection.execute( query );
if ( resultSet == null ) {
throw new ReportDataFactoryException( "query returned no resultset" );
}
return resultSet;
} catch ( MondrianException e ) {
throw new ReportDataFactoryException( "Failed to create datasource:" + e.getLocalizedMessage(), e );
}
}
private void parametrizeQuery( final DataRow parameters, final Query query ) throws ReportDataFactoryException {
final Parameter[] parameterDefs = query.getParameters();
for ( int i = 0; i < parameterDefs.length; i++ ) {
final Parameter def = parameterDefs[ i ];
final Type parameterType = def.getType();
final Object parameterValue = preprocessMemberParameter( def, parameters, parameterType );
final Object processedParamValue = computeParameterValue( query, parameterValue, parameterType );
// Mondrian allows null values to be passed in, so we'll go ahead and
// convert null values to their defaults for now until MONDRIAN-745 is
// resolved.
final Exp exp = def.getDefaultExp();
if ( processedParamValue == null && exp != null && exp instanceof Literal ) {
Literal exp1 = (Literal) exp;
def.setValue( exp1.getValue() );
} else {
def.setValue( processedParamValue );
}
}
}
private Object preprocessMemberParameter( final Parameter def,
final DataRow parameters,
final Type parameterType ) {
Object parameterValue = parameters.get( def.getName() );
// Mondrian doesn't handle null MemberType/SetType parameters well (http://jira.pentaho.com/browse/MONDRIAN-745)
// If parameterValue is null, give it the default value
if ( parameterValue != null ) {
return parameterValue;
}
try {
if ( parameterType instanceof MemberType || parameterType instanceof SetType ) {
return def.getDefaultExp().toString();
}
} catch ( final Exception e ) {
// Ignore - this is a safety procedure anyway
}
return null;
}
private Object computeParameterValue( final Query query,
final Object parameterValue,
final Type parameterType ) throws ReportDataFactoryException {
final Object processedParamValue;
if ( parameterValue != null ) {
if ( parameterType instanceof StringType ) {
if ( !( parameterValue instanceof String ) ) {
throw new ReportDataFactoryException( parameterValue + " is incorrect for type " + parameterType );
}
processedParamValue = parameterValue;
} else if ( parameterType instanceof NumericType ) {
if ( !( parameterValue instanceof Number ) ) {
throw new ReportDataFactoryException( parameterValue + " is incorrect for type " + parameterType );
}
processedParamValue = parameterValue;
} else if ( parameterType instanceof MemberType ) {
final MemberType memberType = (MemberType) parameterType;
final Hierarchy hierarchy = memberType.getHierarchy();
if ( parameterValue instanceof String ) {
final Member member = findMember( query, hierarchy, query.getCube(), String.valueOf( parameterValue ) );
if ( member != null ) {
processedParamValue = new MemberExpr( member );
} else {
processedParamValue = null;
}
} else {
if ( !( parameterValue instanceof OlapElement ) ) {
throw new ReportDataFactoryException( parameterValue + " is incorrect for type " + parameterType );
} else {
processedParamValue = parameterValue;
}
}
} else if ( parameterType instanceof SetType ) {
final SetType setType = (SetType) parameterType;
final Hierarchy hierarchy = setType.getHierarchy();
if ( parameterValue instanceof String ) {
final String rawString = (String) parameterValue;
final String[] memberStr = rawString.replaceFirst( "^ *\\{", "" ).replaceFirst( "} *$", "" ).split( "," );
final List<Member> list = new ArrayList<Member>( memberStr.length );
for ( int j = 0; j < memberStr.length; j++ ) {
final String str = memberStr[ j ];
final Member member = findMember( query, hierarchy, query.getCube(), String.valueOf( str ) );
if ( member != null ) {
list.add( member );
}
}
processedParamValue = list;
} else {
if ( !( parameterValue instanceof OlapElement ) ) {
throw new ReportDataFactoryException( parameterValue + " is incorrect for type " + parameterType );
} else {
processedParamValue = parameterValue;
}
}
} else {
processedParamValue = parameterValue;
}
} else {
processedParamValue = null;
}
return processedParamValue;
}
private Member findMember( final Query query,
final Hierarchy hierarchy,
final Cube cube,
final String parameter ) throws ReportDataFactoryException {
try {
final Member directValue = yuckyInternalMondrianLookup( query, hierarchy, parameter );
if ( directValue != null ) {
return directValue;
}
} catch ( Exception e ) {
// It is non fatal if that fails. Invalid input has this effect.
}
Member memberById = null;
Member memberByUniqueId = null;
final boolean searchForNames = MondrianProperties.instance().NeedDimensionPrefix.get() == false;
final boolean missingMembersIsFatal = MondrianProperties.instance().IgnoreInvalidMembersDuringQuery.get();
try {
final Member directValue = lookupDirectly( hierarchy, cube, parameter, searchForNames );
if ( directValue != null ) {
return directValue;
}
} catch ( Exception e ) {
// It is non fatal if that fails. Invalid input has this effect.
}
final Query memberQuery = connection.parseQuery( "SELECT " + hierarchy.getQualifiedName() // NON-NLS
+ ".AllMembers ON 0, {} ON 1 FROM " + cube.getQualifiedName() ); // NON-NLS
final Result result = connection.execute( memberQuery );
try {
final List<Position> positionList = result.getAxes()[ 0 ].getPositions();
for ( int i = 0; i < positionList.size(); i++ ) {
final Position position = positionList.get( i );
for ( int j = 0; j < position.size(); j++ ) {
final Member member = position.get( j );
if ( parameter.equals( MondrianUtil.getUniqueMemberName( member ) ) ) {
if ( memberByUniqueId == null ) {
memberByUniqueId = member;
} else {
logger
.warn( "Encountered a member with a duplicate unique key: " + member.getQualifiedName() ); // NON-NLS
}
}
if ( searchForNames == false ) {
continue;
}
if ( parameter.equals( member.getName() ) ) {
if ( memberById == null ) {
memberById = member;
} else {
logger.warn( "Encountered a member with a duplicate name: " + member.getQualifiedName() ); // NON-NLS
}
}
}
}
} finally {
result.close();
}
if ( memberByUniqueId != null ) {
return memberByUniqueId;
}
if ( memberById != null ) {
return memberById;
}
if ( missingMembersIsFatal ) {
throw new ReportDataFactoryException( "No member matches parameter value '" + parameter + "'." );
}
return null;
}
private Member lookupDirectly( final Hierarchy hierarchy,
final Cube cube,
final String parameter,
final boolean searchForNames ) {
Member memberById = null;
Member memberByUniqueId = null;
final Query queryDirect =
connection.parseQuery( "SELECT STRTOMEMBER(" + quote( parameter ) + ") ON 0, {} ON 1 FROM " // NON-NLS
+ cube.getQualifiedName() );
final Result resultDirect = connection.execute( queryDirect );
try {
final List<Position> positionList = resultDirect.getAxes()[ 0 ].getPositions();
for ( int i = 0; i < positionList.size(); i++ ) {
final Position position = positionList.get( i );
for ( int j = 0; j < position.size(); j++ ) {
final Member member = position.get( j );
// If the parameter starts with '[', we'll assume we have the full
// member specification specification. Otherwise, keep the funky lookup
// route. We do check whether we get a second member (heck, should not
// happen, but I've seen pigs fly already).
if ( parameter.startsWith( "[" ) ) {
if ( memberByUniqueId == null ) {
memberByUniqueId = member;
} else {
logger.warn( "Encountered a member with a duplicate key: " + member.getQualifiedName() ); // NON-NLS
}
}
if ( searchForNames == false ) {
continue;
}
if ( parameter.equals( member.getName() ) ) {
if ( memberById == null ) {
memberById = member;
} else {
logger.warn( "Encountered a member with a duplicate name: " + member.getQualifiedName() ); // NON-NLS
}
}
}
}
} finally {
resultDirect.close();
}
if ( memberByUniqueId != null ) {
final Hierarchy memberHierarchy = memberByUniqueId.getHierarchy();
if ( hierarchy != memberHierarchy ) {
if ( ObjectUtilities.equal( hierarchy, memberHierarchy ) == false ) {
logger
.warn( "Cannot match hierarchy of member found with the hierarchy specfied in the parameter: " // NON-NLS
+ "Unabe to guarantee that the correct member has been queried, returning null." ); // NON-NLS
return null;
}
}
return memberByUniqueId;
}
if ( memberById != null ) {
final Hierarchy memberHierarchy = memberById.getHierarchy();
if ( hierarchy != memberHierarchy ) {
if ( ObjectUtilities.equal( hierarchy, memberHierarchy ) == false ) {
logger
.warn( "Cannot match hierarchy of member found with the hierarchy specfied in the parameter: " // NON-NLS
+ "Unabe to guarantee that the correct member has been queried, returning null" ); // NON-NLS
return null;
}
}
return memberById;
}
return null;
}
protected Member yuckyInternalMondrianLookup( final Query query, final Hierarchy hierarchy, final String parameter ) {
final Member memberById = (Member) Util.lookup( query, Util.parseIdentifier( parameter ) );
if ( memberById != null ) {
final Hierarchy memberHierarchy = memberById.getHierarchy();
if ( hierarchy != memberHierarchy ) {
if ( ObjectUtilities.equal( hierarchy, memberHierarchy ) == false ) {
logger
.warn( "Cannot match hierarchy of member found with the hierarchy specfied in the parameter: " // NON-NLS
+ "Unabe to guarantee that the correct member has been queried, returning null" ); // NON-NLS
return null;
}
}
return memberById;
}
return null;
}
protected int extractQueryLimit( final DataRow parameters ) {
final Object queryLimit = parameters.get( DataFactory.QUERY_LIMIT );
final int queryLimitValue;
if ( queryLimit instanceof Number ) {
final Number i = (Number) queryLimit;
queryLimitValue = Math.max( 0, i.intValue() );
} else {
// means no limit at all
queryLimitValue = 0;
}
return queryLimitValue;
}
private String computeRole( final DataRow parameters ) throws ReportDataFactoryException {
if ( roleField != null ) {
final Object field = parameters.get( roleField );
if ( field != null ) {
if ( field instanceof Object[] ) {
final Object[] roleArray = (Object[]) field;
final StringBuffer buffer = new StringBuffer();
final int length = roleArray.length;
for ( int i = 0; i < length; i++ ) {
final Object o = roleArray[ i ];
if ( o == null ) {
continue;
}
final String role = filter( String.valueOf( o ) );
if ( role == null ) {
continue;
}
buffer.append( quoteRole( role ) );
}
return buffer.toString();
} else if ( field.getClass().isArray() ) {
final StringBuffer buffer = new StringBuffer();
final int length = Array.getLength( field );
for ( int i = 0; i < length; i++ ) {
final Object o = Array.get( field, i );
if ( o == null ) {
continue;
}
final String role = filter( String.valueOf( o ) );
if ( role == null ) {
continue;
}
buffer.append( quoteRole( role ) );
}
return buffer.toString();
}
final String role = filter( String.valueOf( field ) );
if ( role != null ) {
return role;
}
}
}
return filter( role );
}
private String quoteRole( final String role ) {
if ( role.indexOf( ',' ) == -1 ) {
return role;
}
final StringBuffer b = new StringBuffer( role.length() + 5 );
final char[] chars = role.toCharArray();
for ( int i = 0; i < chars.length; i++ ) {
final char c = chars[ i ];
if ( c == ',' ) {
b.append( c );
}
b.append( c );
}
return b.toString();
}
private String computeJdbcUser( final DataRow parameters ) {
if ( jdbcUserField != null ) {
final Object field = parameters.get( jdbcUserField );
if ( field != null ) {
return String.valueOf( field );
}
}
return jdbcUser;
}
private String computeJdbcPassword( final DataRow parameters ) {
if ( jdbcPasswordField != null ) {
final Object field = parameters.get( jdbcPasswordField );
if ( field != null ) {
return String.valueOf( field );
}
}
return jdbcPassword;
}
private Properties computeProperties( final DataRow parameters ) throws ReportDataFactoryException {
if ( cubeFileProvider == null ) {
throw new ReportDataFactoryException( "No CubeFileProvider" );
}
final Properties properties = getBaseConnectionProperties();
final String catalog = cubeFileProvider.getCubeFile( getResourceManager(), getContextKey() );
if ( catalog == null ) {
throw new ReportDataFactoryException( "No valid catalog given." );
}
properties.setProperty( "Catalog", catalog ); // NON-NLS
final String role = computeRole( parameters );
if ( role != null ) {
properties.setProperty( "Role", role ); // NON-NLS
}
final String jdbcUser = computeJdbcUser( parameters );
if ( StringUtils.isEmpty( jdbcUser ) == false ) {
properties.setProperty( "JdbcUser", jdbcUser ); // NON-NLS
}
final String jdbcPassword = computeJdbcPassword( parameters );
if ( StringUtils.isEmpty( jdbcPassword ) == false ) {
properties.setProperty( "JdbcPassword", jdbcPassword ); // NON-NLS
}
final Locale locale = getLocale();
if ( locale != null ) {
properties.setProperty( "Locale", locale.toString() ); // NON-NLS
}
if ( isUseContentChecksum() != null ) {
properties.setProperty( "UseContentChecksum", String.valueOf( isUseContentChecksum() ) ); // NON-NLS
}
if ( isUseSchemaPool() != null ) {
properties.setProperty( "UseSchemaPool", String.valueOf( isUseSchemaPool() ) ); // NON-NLS
}
if ( getDynamicSchemaProcessor() != null ) {
properties.setProperty( "DynamicSchemaProcessor", getDynamicSchemaProcessor() ); // NON-NLS
}
return properties;
}
public AbstractMDXDataFactory clone() {
final AbstractMDXDataFactory dataFactory = (AbstractMDXDataFactory) super.clone();
dataFactory.connection = null;
if ( this.baseConnectionProperties != null ) {
dataFactory.baseConnectionProperties = (Properties) this.baseConnectionProperties.clone();
}
return dataFactory;
}
public String getDesignTimeName() {
return designTimeName;
}
public void setDesignTimeName( final String designTimeName ) {
this.designTimeName = designTimeName;
}
/**
* Returns all known query-names. A data-factory may accept more than the query-names returned here.
*
* @return the known query names.
*/
public String[] getQueryNames() {
return EMPTY_QUERYNAMES;
}
/**
* Attempts to cancel the query process that is generating the data for this data factory. If it is not possible to
* cancel the query, this call should be ignored.
*/
public void cancelRunningQuery() {
}
protected static String quote( final String original ) {
// This solution needs improvements. Copy blocks instead of single
// characters.
final int length = original.length();
final StringBuffer b = new StringBuffer( length * 12 / 10 );
b.append( '"' );
for ( int i = 0; i < length; i++ ) {
final char c = original.charAt( i );
if ( c == '"' ) {
b.append( '"' );
b.append( '"' );
} else {
b.append( c );
}
}
b.append( '"' );
return b.toString();
}
private String filter( final String role ) throws ReportDataFactoryException {
final Configuration configuration = ClassicEngineBoot.getInstance().getGlobalConfig();
if ( "true".equals( configuration.getConfigProperty( ROLE_FILTER_ENABLE_CONFIG_KEY ) ) == false ) {
return role;
}
final Iterator staticDenyKeys = configuration.findPropertyKeys( DENY_ROLE_CONFIG_KEY );
while ( staticDenyKeys.hasNext() ) {
final String key = (String) staticDenyKeys.next();
final String value = configuration.getConfigProperty( key );
if ( ObjectUtilities.equal( value, role ) ) {
return null;
}
}
final Iterator regExpDenyKeys = configuration.findPropertyKeys( DENY_REGEXP_CONFIG_KEY );
while ( regExpDenyKeys.hasNext() ) {
final String key = (String) regExpDenyKeys.next();
final String value = configuration.getConfigProperty( key );
try {
if ( role.matches( value ) ) {
return null;
}
} catch ( PatternSyntaxException pe ) {
throw new ReportDataFactoryException( "Unable to match reg-exp role filter:", pe );
}
}
boolean hasAccept = false;
final Iterator staticAcceptKeys = configuration.findPropertyKeys( ACCEPT_ROLES_CONFIG_KEY );
while ( staticAcceptKeys.hasNext() ) {
hasAccept = true;
final String key = (String) staticAcceptKeys.next();
final String value = configuration.getConfigProperty( key );
if ( ObjectUtilities.equal( value, role ) ) {
return role;
}
}
final Iterator regExpAcceptKeys = configuration.findPropertyKeys( ACCEPT_REGEXP_CONFIG_KEY );
while ( regExpAcceptKeys.hasNext() ) {
hasAccept = true;
final String key = (String) regExpAcceptKeys.next();
final String value = configuration.getConfigProperty( key );
try {
if ( role.matches( value ) ) {
return role;
}
} catch ( PatternSyntaxException pe ) {
throw new ReportDataFactoryException( "Unable to match reg-exp role filter:", pe );
}
}
if ( hasAccept == false ) {
return role;
}
return null;
}
protected String translateQuery( final String query ) {
return query;
}
protected String computedQuery( final String queryName, final DataRow parameters ) throws ReportDataFactoryException {
return queryName;
}
public ArrayList<Object> getQueryHash( final String queryRaw, final DataRow parameter )
throws ReportDataFactoryException {
final ArrayList<Object> list = new ArrayList<Object>();
list.add( getClass().getName() );
list.add( translateQuery( queryRaw ) );
if ( getCubeFileProvider() != null ) {
list.add( getCubeFileProvider().getConnectionHash() );
}
if ( getDataSourceProvider() != null ) {
list.add( getDataSourceProvider().getConnectionHash() );
}
list.add( getMondrianConnectionProvider().getConnectionHash( computeProperties( parameter ) ) );
list.add( computeProperties( parameter ) );
return list;
}
public String[] getReferencedFields( final String queryName,
final DataRow parameters ) throws ReportDataFactoryException {
final boolean isNewConnection = connection == null;
try {
if ( connection == null ) {
connection = mondrianConnectionProvider.createConnection
( computeProperties( parameters ), dataSourceProvider.getDataSource() );
}
} catch ( SQLException e ) {
logger.error( e );
throw new ReportDataFactoryException(
"Failed to create DataSource (SQL Exception - error code: " + e.getErrorCode() + "):" + e.toString(), e );
} catch ( MondrianException e ) {
logger.error( e );
throw new ReportDataFactoryException( "Failed to create DataSource (Mondrian Exception):" + e.toString(), e );
}
try {
if ( connection == null ) {
throw new ReportDataFactoryException( "Factory is closed." );
}
final LinkedHashSet<String> parameter = new LinkedHashSet<String>();
final MDXCompiler compiler = new MDXCompiler( parameters, getLocale() );
final String computedQuery = computedQuery( queryName, parameters );
final String mdxQuery = compiler.translateAndLookup( computedQuery, parameters );
parameter.addAll( compiler.getCollectedParameter() );
// Alternatively, JNDI is possible. Maybe even more ..
final Query query = connection.parseQuery( mdxQuery );
final Parameter[] queryParameters = query.getParameters();
for ( int i = 0; i < queryParameters.length; i++ ) {
final Parameter queryParameter = queryParameters[ i ];
parameter.add( queryParameter.getName() );
}
if ( jdbcUserField != null ) {
parameter.add( jdbcUserField );
}
if ( roleField != null ) {
parameter.add( roleField );
}
parameter.add( DataFactory.QUERY_LIMIT );
return parameter.toArray( new String[ parameter.size() ] );
} catch ( MondrianException e ) {
throw new ReportDataFactoryException( "Failed to create datasource:" + e.getLocalizedMessage(), e );
} finally {
if ( isNewConnection ) {
close();
}
}
}
public void initialize( final DataFactoryContext dataFactoryContext ) throws ReportDataFactoryException {
super.initialize( dataFactoryContext );
membersOnAxisSorted = "true".equals
( dataFactoryContext.getConfiguration()
.getConfigProperty( MondrianDataFactoryModule.MEMBER_ON_AXIS_SORTED_KEY ) );
}
}