/*!
* 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 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;
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;
/**
* 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
{
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);
}
}
public void initialize(final DataFactoryContext dataFactoryContext) throws ReportDataFactoryException
{
super.initialize(dataFactoryContext);
membersOnAxisSorted = "true".equals
(dataFactoryContext.getConfiguration().getConfigProperty(MondrianDataFactoryModule.MEMBER_ON_AXIS_SORTED_KEY));
}
}