/*! * 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 ) ); } }