/*
* Copyright 2014 OSBI Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.saiku.olap.query;
import org.saiku.olap.dto.SaikuCube;
import org.saiku.olap.dto.SaikuTag;
import org.saiku.olap.dto.filter.SaikuFilter;
import org.saiku.olap.query.QueryProperties.QueryProperty;
import org.saiku.olap.query.QueryProperties.QueryPropertyFactory;
import org.saiku.olap.util.QueryConverter;
import org.saiku.olap.util.SaikuProperties;
import org.saiku.olap.util.exception.SaikuIncompatibleException;
import org.saiku.olap.util.exception.SaikuOlapException;
import org.saiku.olap.util.formatter.ICellSetFormatter;
import org.saiku.service.util.exception.SaikuServiceException;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.olap4j.*;
import org.olap4j.Axis.Standard;
import org.olap4j.impl.IdentifierParser;
import org.olap4j.mdx.ParseTreeWriter;
import org.olap4j.mdx.SelectNode;
import org.olap4j.metadata.Catalog;
import org.olap4j.metadata.Cube;
import org.olap4j.query.*;
import org.olap4j.query.QueryDimension.HierarchizeMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import mondrian.rolap.RolapConnection;
/**
* OlapQuery.
*/
public class OlapQuery implements IQuery {
private static final Logger LOG = LoggerFactory.getLogger(OlapQuery.class);
private static final String SCENARIO = "Scenario";
private final Query query;
@NotNull
private final Properties properties = new Properties();
@NotNull
private final Map<String, String> totalsFunctions = new HashMap<String, String>();
private final SaikuCube cube;
private Scenario scenario;
@Nullable
private SaikuTag tag = null;
@Nullable
private SaikuFilter filter;
@Nullable
private CellSet cellset = null;
@Nullable
private OlapStatement statement = null;
private final OlapConnection connection;
private ICellSetFormatter formatter;
public OlapQuery(Query query, OlapConnection connection, SaikuCube cube, boolean applyDefaultProperties) {
this.query = query;
this.cube = cube;
this.connection = connection;
if (applyDefaultProperties) {
applyDefaultProperties();
}
}
public OlapQuery(Query query, OlapConnection connection, SaikuCube cube) {
this(query, connection, cube, true);
}
public void swapAxes() {
this.query.swapAxes();
QueryAxis rows = query.getAxis(Axis.ROWS);
QueryAxis cols = query.getAxis(Axis.COLUMNS);
SortOrder colSort = cols.getSortOrder();
String colSortIdentifier = cols.getSortIdentifierNodeName();
cols.clearSort();
if (rows.getSortOrder() != null) {
cols.sort(rows.getSortOrder(), rows.getSortIdentifierNodeName());
}
rows.clearSort();
if (colSort != null) {
rows.sort(colSort, colSortIdentifier);
}
try {
// 3-way swap
String colFilter = cols.getFilterCondition();
LimitFunction colLimit = cols.getLimitFunction();
BigDecimal colN = cols.getLimitFunctionN();
String colLimitSort = cols.getLimitFunctionSortLiteral();
cols.clearFilter();
cols.clearLimitFunction();
// columns
if (rows.getLimitFunction() != null) {
cols.limit(rows.getLimitFunction(), rows.getLimitFunctionN(), rows.getLimitFunctionSortLiteral());
}
if (StringUtils.isNotBlank(rows.getFilterCondition())) {
cols.filter(rows.getFilterCondition());
}
rows.clearFilter();
rows.clearLimitFunction();
// rows
if (colLimit != null) {
rows.limit(colLimit, colN, colLimitSort);
}
if (StringUtils.isNotBlank(colFilter)) {
rows.filter(colFilter);
}
} catch (NoSuchMethodError e) {
LOG.error("Could not swap axis", e);
}
}
public Map<Axis, QueryAxis> getAxes() {
return this.query.getAxes();
}
public QueryAxis getAxis(Axis axis) {
return this.query.getAxis(axis);
}
public QueryAxis getAxis(String name) throws SaikuOlapException {
if ("UNUSED".equals(name)) {
return getUnusedAxis();
}
Standard standardAxis = Standard.valueOf(name);
if (standardAxis == null) {
throw new SaikuOlapException("Axis (" + name + ") not found for query (" + query.getName() + ")");
}
Axis queryAxis = Axis.Factory.forOrdinal(standardAxis.axisOrdinal());
return query.getAxis(queryAxis);
}
public Cube getCube() {
return this.query.getCube();
}
public QueryAxis getUnusedAxis() {
return this.query.getUnusedAxis();
}
public void moveDimension(@NotNull QueryDimension dimension, Axis axis) {
moveDimension(dimension, axis, -1);
}
public void moveDimension(@NotNull QueryDimension dimension, Axis axis, int position) {
QueryAxis oldQueryAxis = findAxis(dimension);
QueryAxis newQueryAxis = query.getAxis(axis);
if (!Axis.FILTER.equals(axis)) {
dimension.setHierarchyConsistent(true);
dimension.setHierarchizeMode(HierarchizeMode.PRE);
} else {
dimension.setHierarchyConsistent(false);
dimension.clearHierarchizeMode();
}
if (oldQueryAxis != null && newQueryAxis != null && oldQueryAxis.getLocation() != newQueryAxis.getLocation()
&& oldQueryAxis.getLocation() != null) {
for (QueryAxis qAxis : query.getAxes().values()) {
if (qAxis.getSortOrder() != null && qAxis.getSortIdentifierNodeName() != null) {
String sortLiteral = qAxis.getSortIdentifierNodeName();
if (sortLiteral.startsWith(dimension.getDimension().getUniqueName()) || sortLiteral
.startsWith("[" + dimension.getName())) {
qAxis.clearSort();
// System.out.println("Removed Sort: " + qAxis.getLocation() + " - "+ sortLiteral);
}
}
}
}
if (oldQueryAxis != null && newQueryAxis != null && (position > -1 || oldQueryAxis.getLocation() != newQueryAxis
.getLocation())) {
oldQueryAxis.removeDimension(dimension);
if (position > -1) {
newQueryAxis.addDimension(position, dimension);
} else {
newQueryAxis.addDimension(dimension);
}
}
}
public QueryDimension getDimension(String name) {
return this.query.getDimension(name);
}
@Nullable
private QueryAxis findAxis(QueryDimension dimension) {
if (query.getUnusedAxis().getDimensions().contains(dimension)) {
return query.getUnusedAxis();
} else {
Map<Axis, QueryAxis> axes = query.getAxes();
for (Axis axis : axes.keySet()) {
if (axes.get(axis).getDimensions().contains(dimension)) {
return axes.get(axis);
}
}
}
return null;
}
public String getMdx() {
final Writer writer = new StringWriter();
if (SaikuProperties.OLAPCONVERTQUERY) {
try {
SelectNode s = QueryConverter.convert(query);
s.unparse(new ParseTreeWriter(new PrintWriter(writer)));
} catch (SaikuIncompatibleException se) {
LOG.debug("Cannot convert to new query model mdx, falling back to old version", se);
this.query.getSelect().unparse(new ParseTreeWriter(new PrintWriter(writer)));
} catch (Exception e) {
throw new SaikuServiceException("Cannot convert to new query model", e);
}
} else {
this.query.getSelect().unparse(new ParseTreeWriter(new PrintWriter(writer)));
}
return writer.toString();
}
public SaikuCube getSaikuCube() {
return cube;
}
public String getName() {
return query.getName();
}
public CellSet execute() throws Exception {
try {
if (statement != null) {
statement.close();
statement = null;
}
if (scenario != null && query.getDimension(SCENARIO) != null) {
QueryDimension dimension = query.getDimension(SCENARIO);
moveDimension(dimension, Axis.FILTER);
Selection sel =
dimension.createSelection(IdentifierParser.parseIdentifier("[Scenario].[" + getScenario().getId() + "]"));
if (!dimension.getInclusions().contains(sel)) {
dimension.getInclusions().add(sel);
}
}
String mdx = getMdx();
LOG.trace("Executing query (" + this.getName() + ") :\n" + mdx);
final Catalog catalog = query.getCube().getSchema().getCatalog();
this.connection.setCatalog(catalog.getName());
OlapStatement stmt = connection.createStatement();
this.statement = stmt;
CellSet cellSet = stmt.executeOlapQuery(mdx);
if (scenario != null && query.getDimension(SCENARIO) != null) {
QueryDimension dimension = query.getDimension(SCENARIO);
dimension.getInclusions().clear();
moveDimension(dimension, null);
}
return cellSet;
} finally {
if (this.statement != null) {
this.statement.close();
}
}
}
private void applyDefaultProperties() {
if (SaikuProperties.OLAPEFAULTNONEMPTY) {
query.getAxis(Axis.ROWS).setNonEmpty(true);
query.getAxis(Axis.COLUMNS).setNonEmpty(true);
}
}
public void resetAxisSelections(@NotNull QueryAxis axis) {
for (QueryDimension dim : axis.getDimensions()) {
dim.clearInclusions();
dim.clearExclusions();
dim.clearSort();
}
try {
axis.clearFilter();
axis.clearLimitFunction();
axis.clearSort();
} catch (NoSuchMethodError e) {
LOG.error("Could not reset axis", e);
}
}
public void clearAllQuerySelections() {
resetAxisSelections(getUnusedAxis());
Map<Axis, QueryAxis> axes = getAxes();
for (Axis axis : axes.keySet()) {
resetAxisSelections(axes.get(axis));
}
}
public void resetQuery() {
clearAllQuerySelections();
Map<Axis, QueryAxis> axes = getAxes();
for (Axis axis : axes.keySet()) {
QueryAxis qAxis = axes.get(axis);
for (int i = 0; i < qAxis.getDimensions().size(); i++) {
QueryDimension qDim = qAxis.getDimensions().get(0);
moveDimension(qDim, null);
}
}
}
public void clearAxis(String axisName) throws SaikuOlapException {
if (StringUtils.isNotBlank(axisName)) {
QueryAxis qAxis = getAxis(axisName);
resetAxisSelections(qAxis);
for (int i = 0; i < qAxis.getDimensions().size(); i++) {
QueryDimension qDim = qAxis.getDimensions().get(0);
moveDimension(qDim, null);
}
}
}
public void setProperties(@Nullable Properties props) {
if (props != null) {
this.properties.putAll(props);
for (Object key : props.keySet()) {
String key2 = (String) key;
String value = props.getProperty((String) key);
QueryProperty prop = QueryPropertyFactory.getProperty(key2, value, this);
prop.handle();
}
}
}
@NotNull
public Properties getProperties() {
this.properties.putAll(QueryPropertyFactory.forQuery(this));
try {
connection.createScenario();
if (query.getDimension("Scenario") != null) {
this.properties.put("org.saiku.connection.scenario", Boolean.toString(true));
} else {
this.properties.put("org.saiku.connection.scenario", Boolean.toString(false));
}
this.properties.put("org.saiku.query.explain", Boolean.toString(connection.isWrapperFor(RolapConnection.class)));
} catch (Exception e) {
this.properties.put("org.saiku.connection.scenario", Boolean.toString(false));
this.properties.put("org.saiku.query.explain", Boolean.toString(false));
}
return this.properties;
}
public String toXml() {
QuerySerializer qs = new QuerySerializer(this);
return qs.createXML();
}
public Boolean isDrillThroughEnabled() {
return query.getCube().isDrillThroughEnabled();
}
@NotNull
public QueryType getType() {
return QueryType.QM;
}
public void setMdx(String mdx) {
throw new UnsupportedOperationException();
}
public void setScenario(Scenario scenario) {
this.scenario = scenario;
}
public Scenario getScenario() {
return scenario;
}
public void setTag(SaikuTag tag) {
this.tag = tag;
}
@Nullable
public SaikuTag getTag() {
return this.tag;
}
public void removeTag() {
tag = null;
}
public void setFilter(SaikuFilter filter) {
this.filter = filter;
}
@Nullable
public SaikuFilter getFilter() {
return this.filter;
}
public void removeFilter() {
this.filter = null;
}
public void storeCellset(CellSet cs) {
this.cellset = cs;
}
@Nullable
public CellSet getCellset() {
return cellset;
}
public void setStatement(OlapStatement os) {
this.statement = os;
}
@Nullable
public OlapStatement getStatement() {
return this.statement;
}
public void cancel() throws Exception {
if (this.statement != null && !this.statement.isClosed()) {
statement.cancel();
statement.close();
}
this.statement = null;
}
public OlapConnection getConnection() {
return this.connection;
}
public void storeFormatter(ICellSetFormatter formatter) {
this.formatter = formatter;
}
public ICellSetFormatter getFormatter() {
return formatter;
}
public void setTotalFunction(String uniqueLevelName, String value) {
totalsFunctions.put(uniqueLevelName, value);
}
public String getTotalFunction(String uniqueLevelName) {
String retVal = totalsFunctions.get(uniqueLevelName);
return retVal;
}
@NotNull
public Map<String, String> getTotalFunctions() {
return totalsFunctions;
}
public Query getQuery() {
return query;
}
}