com.fer.hr.service.olap.ThinQueryService.java Source code

Java tutorial

Introduction

Here is the source code for com.fer.hr.service.olap.ThinQueryService.java

Source

/*  
 *   Copyright 2012 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 com.fer.hr.service.olap;

import com.fer.hr.olap.dto.SaikuCube;
import com.fer.hr.olap.dto.SimpleCubeElement;
import com.fer.hr.olap.dto.resultset.CellDataSet;
import com.fer.hr.olap.query.IQuery;
import com.fer.hr.olap.query.IQuery.QueryType;
import com.fer.hr.olap.query.OlapQuery;
import com.fer.hr.olap.query.QueryDeserializer;
import com.fer.hr.olap.query2.ThinHierarchy;
import com.fer.hr.olap.query2.ThinQuery;
import com.fer.hr.olap.query2.ThinQueryModel.AxisLocation;
import com.fer.hr.olap.query2.util.Fat;
import com.fer.hr.olap.query2.util.Thin;
import com.fer.hr.olap.util.*;
import com.fer.hr.olap.util.formatter.CellSetFormatterFactory;
import com.fer.hr.olap.util.formatter.FlattenedCellSetFormatter;
import com.fer.hr.olap.util.formatter.ICellSetFormatter;
import org.saiku.query.Query;
import org.saiku.query.QueryDetails;
import org.saiku.query.QueryHierarchy;
import org.saiku.query.QueryLevel;
import org.saiku.query.util.QueryUtil;
import com.fer.hr.service.olap.totals.AxisInfo;
import com.fer.hr.service.olap.totals.TotalNode;
import com.fer.hr.service.olap.totals.TotalsListsBuilder;
import com.fer.hr.service.olap.totals.aggregators.TotalAggregator;
import com.fer.hr.service.util.KeyValue;
import com.fer.hr.service.util.QueryContext;
import com.fer.hr.service.util.QueryContext.ObjectKey;
import com.fer.hr.service.util.QueryContext.Type;
import com.fer.hr.service.util.exception.SaikuServiceException;
import com.fer.hr.service.util.export.CsvExporter;
import com.fer.hr.service.util.export.ExcelExporter;

import org.apache.commons.lang.StringUtils;
import org.olap4j.*;
import org.olap4j.mdx.ParseTreeNode;
import org.olap4j.mdx.ParseTreeWriter;
import org.olap4j.mdx.SelectNode;
import org.olap4j.mdx.parser.impl.DefaultMdxParserImpl;
import org.olap4j.metadata.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.Writer;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

import mondrian.olap4j.SaikuMondrianHelper;

public class ThinQueryService implements Serializable {

    /**
     * Unique serialization UID
     */
    private static final long serialVersionUID = -7615296596528274904L;

    private static final Logger log = LoggerFactory.getLogger(ThinQueryService.class);

    private static final AtomicLong ID_GENERATOR = new AtomicLong();

    private OlapDiscoverService olapDiscoverService;

    private CellSetFormatterFactory cff = new CellSetFormatterFactory();

    private final Map<String, QueryContext> context = new HashMap<>();

    public void setOlapDiscoverService(OlapDiscoverService os) {
        this.olapDiscoverService = os;
    }

    public void setCellSetFormatterFactory(CellSetFormatterFactory cff) {
        this.cff = cff;
    }

    public ThinQuery createQuery(ThinQuery tq) {
        if (StringUtils.isBlank(tq.getName())) {
            tq.setName(UUID.randomUUID().toString());
        }
        Map<String, Object> cubeProperties = olapDiscoverService.getProperties(tq.getCube());
        tq.getProperties().putAll(cubeProperties);
        if (!context.containsKey(tq.getName())) {
            //         Cube cub = olapDiscoverService.getNativeCube(tq.getCube());
            //         Query query = new Query(tq.getName(), cub);
            //         tq = Thin.convert(query, tq.getCube());
            QueryContext qt = new QueryContext(Type.OLAP, tq);
            qt.store(ObjectKey.QUERY, tq);
            this.context.put(tq.getName(), qt);
        }
        return tq;
    }

    public QueryContext getContext(String name) {
        return this.context.get(name);
    }

    @Deprecated
    public ThinQuery createEmpty(String name, SaikuCube cube) {
        try {
            Cube cub = olapDiscoverService.getNativeCube(cube);
            Query query = new Query(name, cub);
            return Thin.convert(query, cube);

        } catch (Exception e) {
            log.error("Cannot create new query for cube :" + cube, e);
        }
        return null;

    }

    CellSet executeInternalQuery(ThinQuery query) throws Exception {
        String runId = "RUN#:" + ID_GENERATOR.getAndIncrement();
        QueryContext queryContext = context.get(query.getName());

        if (queryContext == null) {
            queryContext = new QueryContext(Type.OLAP, query);
            this.context.put(query.getName(), queryContext);
        }

        OlapConnection con = olapDiscoverService.getNativeConnection(query.getCube().getConnection());
        if (StringUtils.isNotBlank(query.getCube().getCatalog())) {
            con.setCatalog(query.getCube().getCatalog());
        }

        if (queryContext.contains(ObjectKey.STATEMENT)) {
            Statement s = queryContext.getStatement();
            s.cancel();
            s.close();
            s = null;
            queryContext.remove(ObjectKey.STATEMENT);
        }

        OlapStatement stmt = con.createStatement();
        queryContext.store(ObjectKey.STATEMENT, stmt);

        query = updateQuery(query);

        try {
            String mdx = query.getParameterResolvedMdx();
            log.info(runId + "\tType:" + query.getType() + ":\n" + mdx);

            CellSet cs = stmt.executeOlapQuery(mdx);
            queryContext.store(ObjectKey.RESULT, cs);
            if (query != null) {
                queryContext.store(ObjectKey.QUERY, query);
            }
            return cs;
        } finally {
            stmt.close();
            queryContext.remove(ObjectKey.STATEMENT);
        }
    }

    public CellDataSet execute(ThinQuery tq) {
        if (tq.getProperties().containsKey("saiku.olap.result.formatter")) {
            return execute(tq, tq.getProperties().get("saiku.olap.result.formatter").toString());
        }
        return execute(tq, "");
    }

    public CellDataSet execute(ThinQuery tq, String formatter) {
        String formatterName = formatter == null ? "" : formatter.toLowerCase();
        ICellSetFormatter cf = cff.forName(formatterName);
        return execute(tq, cf);
    }

    public CellDataSet getFormattedResult(String query, String format) throws Exception {
        QueryContext qc = getContext(query);
        ThinQuery tq = qc.getOlapQuery();
        CellSet cs = qc.getOlapResult();
        String formatterName = (StringUtils.isBlank(format) ? "" : format.toLowerCase());
        ICellSetFormatter cf = cff.forName(formatterName);
        CellDataSet result = OlapResultSetUtil.cellSet2Matrix(cs, cf);

        if (ThinQuery.Type.QUERYMODEL.equals(tq.getType()) && cf instanceof FlattenedCellSetFormatter
                && tq.hasAggregators()) {
            calculateTotals(tq, result, cs, cf);
        }
        return result;
    }

    private CellDataSet execute(ThinQuery tq, ICellSetFormatter formatter) {
        try {

            Long start = (new Date()).getTime();
            log.debug("Query Start");
            CellSet cellSet = executeInternalQuery(tq);
            log.debug("Query End");
            String runId = "RUN#:" + ID_GENERATOR.get();
            Long exec = (new Date()).getTime();

            CellDataSet result = OlapResultSetUtil.cellSet2Matrix(cellSet, formatter);
            Long format = (new Date()).getTime();

            if (ThinQuery.Type.QUERYMODEL.equals(tq.getType()) && formatter instanceof FlattenedCellSetFormatter
                    && tq.hasAggregators()) {
                calculateTotals(tq, result, cellSet, formatter);
            }
            Long totals = (new Date()).getTime();
            log.info(runId + "\tSize: " + result.getWidth() + "/" + result.getHeight() + "\tExecute:\t"
                    + (exec - start) + "ms\tFormat:\t" + (format - exec) + "ms\tTotals:\t" + (totals - format)
                    + "ms\t Total: " + (totals - start) + "ms");

            result.setRuntime(new Double(format - start).intValue());
            return result;
        } catch (Exception | Error e) {
            throw new SaikuServiceException("Can't execute query: " + tq.getName(), e);
        }
    }

    public void cancel(String name) throws SQLException {
        if (context.containsKey(name)) {
            QueryContext queryContext = context.get(name);
            if (queryContext.contains(ObjectKey.STATEMENT)) {
                Statement stmt = queryContext.getStatement();
                if (stmt != null && !stmt.isClosed()) {
                    stmt.cancel();
                    stmt.close();
                }
                stmt = null;
                queryContext.remove(ObjectKey.STATEMENT);
            }
        }
    }

    public ThinQuery updateQuery(ThinQuery old) throws Exception {
        if (ThinQuery.Type.QUERYMODEL.equals(old.getType())) {
            Cube cub = olapDiscoverService.getNativeCube(old.getCube());
            Query q = Fat.convert(old, cub);
            ThinQuery tqAfter = Thin.convert(q, old.getCube());
            old.setQueryModel(tqAfter.getQueryModel());
            old.setMdx(tqAfter.getMdx());
        }
        if (context.containsKey(old.getName())) {
            QueryContext qc = context.get(old.getName());
            qc.store(ObjectKey.QUERY, old);
        }
        String mdx = old.getMdx();
        List<String> params = QueryUtil.parseParameters(mdx);
        old.addParameters(params);

        Map<String, Object> cubeProperties = olapDiscoverService.getProperties(old.getCube());
        old.getProperties().putAll(cubeProperties);

        return old;
    }

    public void deleteQuery(String queryName) {
        try {
            if (context.containsKey(queryName)) {
                QueryContext qc = context.remove(queryName);
                qc.destroy();
            }
        } catch (Exception e) {
            throw new SaikuServiceException(e);
        }
    }

    public byte[] getExport(String queryName, String type) {
        return getExport(queryName, type, new FlattenedCellSetFormatter());
    }

    public byte[] getExport(String queryName, String type, String formatter) {
        String formatterName = formatter == null ? "" : formatter.toLowerCase();
        ICellSetFormatter cf = cff.forName(formatterName);
        return getExport(queryName, type, cf);
    }

    private byte[] getExport(String queryName, String type, ICellSetFormatter formatter) {
        if (StringUtils.isNotBlank(type) && context.containsKey(queryName)) {
            CellSet rs = context.get(queryName).getOlapResult();
            ThinQuery tq = context.get(queryName).getOlapQuery();
            List<ThinHierarchy> filterHierarchies = null;
            if (ThinQuery.Type.QUERYMODEL.equals(tq.getType())) {
                filterHierarchies = tq.getQueryModel().getAxes().get(AxisLocation.FILTER).getHierarchies();
            }
            if (type.toLowerCase().equals("xls")) {
                return ExcelExporter.exportExcel(rs, formatter, filterHierarchies);
            }
            if (type.toLowerCase().equals("csv")) {
                return CsvExporter.exportCsv(rs, SaikuProperties.webExportCsvDelimiter,
                        SaikuProperties.webExportCsvTextEscape, formatter);
            }
        }
        return new byte[0];
    }

    public ResultSet drillthrough(String queryName, int maxrows, String returns) {
        OlapStatement stmt = null;
        try {

            ThinQuery query = context.get(queryName).getOlapQuery();
            final OlapConnection con = olapDiscoverService.getNativeConnection(query.getCube().getConnection());
            stmt = con.createStatement();
            String mdx = query.getMdx();
            if (maxrows > 0) {
                mdx = "DRILLTHROUGH MAXROWS " + maxrows + " " + mdx;
            } else {
                mdx = "DRILLTHROUGH " + mdx;
            }
            if (StringUtils.isNotBlank(returns)) {
                mdx += "\r\n RETURN " + returns;
            }
            return stmt.executeQuery(mdx);
        } catch (SQLException e) {
            throw new SaikuServiceException("Error DRILLTHROUGH: " + queryName, e);
        } finally {
            try {
                if (stmt != null)
                    stmt.close();
            } catch (Exception e) {
            }
        }
    }

    public boolean isMdxDrillthrough(ThinQuery query) {
        try {
            if (ThinQuery.Type.MDX.equals(query.getType())) {
                SaikuCube cube = query.getCube();
                final OlapConnection con = olapDiscoverService.getNativeConnection(cube.getConnection());
                return SaikuMondrianHelper.isMondrianDrillthrough(con, query.getMdx());
            }
        } catch (Exception | Error e) {
            log.warn("Error checking for DRILLTHROUGH: " + query.getName() + " DRILLTHROUGH MDX:" + query.getMdx(),
                    e);
        }
        return false;

    }

    public ResultSet drillthrough(ThinQuery query) {
        OlapStatement stmt = null;
        try {
            SaikuCube cube = query.getCube();
            final OlapConnection con = olapDiscoverService.getNativeConnection(cube.getConnection());
            stmt = con.createStatement();
            return stmt.executeQuery(query.getMdx());
        } catch (SQLException e) {
            throw new SaikuServiceException(
                    "Error DRILLTHROUGH: " + query.getMdx() + " DRILLTHROUGH MDX:" + query.getMdx(), e);
        } finally {
            try {
                if (stmt != null)
                    stmt.close();
            } catch (Exception e) {
            }
        }

    }

    public ResultSet drillthrough(String queryName, List<Integer> cellPosition, Integer maxrows, String returns) {
        OlapStatement stmt = null;
        try {
            QueryContext queryContext = context.get(queryName);
            ThinQuery query = queryContext.getOlapQuery();
            CellSet cs = queryContext.getOlapResult();
            SaikuCube cube = query.getCube();
            final OlapConnection con = olapDiscoverService.getNativeConnection(cube.getConnection());
            stmt = con.createStatement();
            SelectNode sn = (new DefaultMdxParserImpl().parseSelect(query.getMdx()));
            String select = null;
            StringBuilder buf = new StringBuilder();
            if (sn.getWithList() != null && sn.getWithList().size() > 0) {
                buf.append("WITH \n");
                StringWriter sw = new StringWriter();
                ParseTreeWriter ptw = new ParseTreeWriter(sw);
                final PrintWriter pw = ptw.getPrintWriter();
                for (ParseTreeNode with : sn.getWithList()) {
                    with.unparse(ptw);
                    pw.println();
                }
                buf.append(sw.toString());
            }
            buf.append("SELECT (");
            for (int i = 0; i < cellPosition.size(); i++) {
                List<Member> members = cs.getAxes().get(i).getPositions().get(cellPosition.get(i)).getMembers();
                for (int k = 0; k < members.size(); k++) {
                    Member m = members.get(k);
                    if (k > 0 || i > 0) {
                        buf.append(", ");
                    }
                    buf.append(m.getUniqueName());
                }
            }
            buf.append(") ON COLUMNS \r\n");
            buf.append("FROM [").append(cube.getName()).append("]\r\n");
            final Writer writer = new StringWriter();
            sn.getFilterAxis().unparse(new ParseTreeWriter(new PrintWriter(writer)));
            if (StringUtils.isNotBlank(writer.toString())) {
                buf.append("WHERE ").append(writer.toString());
            }
            select = buf.toString();
            if (maxrows > 0) {
                select = "DRILLTHROUGH MAXROWS " + maxrows + " " + select + "\r\n";
            } else {
                select = "DRILLTHROUGH " + select + "\r\n";
            }
            if (StringUtils.isNotBlank(returns)) {
                select += "\r\n RETURN " + returns;
            }
            log.debug("Drill Through for query (" + queryName + ") : \r\n" + select);
            return stmt.executeQuery(select);
        } catch (Exception e) {
            throw new SaikuServiceException("Error DRILLTHROUGH: " + queryName, e);
        } finally {
            try {
                if (stmt != null)
                    stmt.close();
            } catch (Exception e) {
            }
        }
    }

    public byte[] exportDrillthroughCsv(String queryName, int maxrows) {
        OlapStatement stmt = null;
        try {
            QueryContext queryContext = context.get(queryName);
            ThinQuery query = queryContext.getOlapQuery();
            final OlapConnection con = olapDiscoverService.getNativeConnection(query.getCube().getConnection());
            stmt = con.createStatement();
            String mdx = query.getMdx();
            if (maxrows > 0) {
                mdx = "DRILLTHROUGH MAXROWS " + maxrows + " " + mdx;
            } else {
                mdx = "DRILLTHROUGH " + mdx;
            }

            ResultSet rs = stmt.executeQuery(mdx);
            return CsvExporter.exportCsv(rs);
        } catch (SQLException e) {
            throw new SaikuServiceException("Error DRILLTHROUGH: " + queryName, e);
        } finally {
            try {
                if (stmt != null)
                    stmt.close();
            } catch (Exception e) {
            }
        }

    }

    public byte[] exportResultSetCsv(ResultSet rs) {
        return CsvExporter.exportCsv(rs);
    }

    public byte[] exportResultSetCsv(ResultSet rs, String delimiter, String enclosing, boolean printHeader,
            List<KeyValue<String, String>> additionalColumns) {
        return CsvExporter.exportCsv(rs, delimiter, enclosing, printHeader, additionalColumns);
    }

    public List<SimpleCubeElement> getResultMetadataMembers(String queryName, boolean preferResult,
            String hierarchyName, String levelName, String searchString, int searchLimit) {

        if (context.containsKey(queryName)) {
            CellSet cs = context.get(queryName).getOlapResult();
            List<SimpleCubeElement> members = new ArrayList<>();
            Set<Level> levels = new HashSet<>();
            boolean search = StringUtils.isNotBlank(searchString);
            preferResult = (preferResult && !search);
            searchString = search ? searchString.toLowerCase() : null;

            if (cs != null && preferResult) {
                for (CellSetAxis axis : cs.getAxes()) {
                    int posIndex = 0;
                    for (Hierarchy h : axis.getAxisMetaData().getHierarchies()) {
                        if (h != null
                                && (h.getUniqueName().equals(hierarchyName) || h.getName().equals(hierarchyName))) {
                            log.debug("Found hierarchy in the result: " + hierarchyName);
                            if (h.getLevels().size() == 1) {
                                break;
                            }
                            Set<Member> mset = new HashSet<>();
                            for (Position pos : axis.getPositions()) {
                                Member m = pos.getMembers().get(posIndex);
                                if (!m.getLevel().getLevelType().equals(org.olap4j.metadata.Level.Type.ALL)) {
                                    levels.add(m.getLevel());
                                }
                                if (m.getLevel().getUniqueName().equals(levelName)
                                        || m.getLevel().getName().equals(levelName)) {
                                    mset.add(m);
                                }
                            }

                            members = ObjectUtil.convert2Simple(mset);
                            Collections.sort(members, new SaikuUniqueNameComparator());

                            break;
                        }
                        posIndex++;
                    }
                }
                log.debug("Found members in the result: " + members.size());

            }
            if (cs == null || !preferResult || members.size() == 0 || levels.size() == 1) {
                members = olapDiscoverService.getLevelMembers(context.get(queryName).getOlapQuery().getCube(),
                        hierarchyName, levelName, searchString, searchLimit);
            }
            return members;
        }
        return null;
    }

    private void calculateTotals(ThinQuery tq, CellDataSet result, CellSet cellSet, ICellSetFormatter formatter)
            throws Exception {
        if (ThinQuery.Type.QUERYMODEL.equals(tq.getType()) && formatter instanceof FlattenedCellSetFormatter) {
            Cube cub = olapDiscoverService.getNativeCube(tq.getCube());
            Query query = Fat.convert(tq, cub);

            QueryDetails details = query.getDetails();
            Measure[] selectedMeasures = new Measure[details.getMeasures().size()];
            for (int i = 0; i < selectedMeasures.length; i++)
                selectedMeasures[i] = details.getMeasures().get(i);
            result.setSelectedMeasures(selectedMeasures);

            int rowsIndex = 0;
            if (!cellSet.getAxes().get(0).getAxisOrdinal().equals(Axis.ROWS)) {
                rowsIndex = (rowsIndex + 1) & 1;
            }
            // TODO - refactor this using axis ordinals etc.
            final AxisInfo[] axisInfos = new AxisInfo[] { new AxisInfo(cellSet.getAxes().get(rowsIndex)),
                    new AxisInfo(cellSet.getAxes().get((rowsIndex + 1) & 1)) };
            List<TotalNode>[][] totals = new List[2][];
            TotalsListsBuilder builder = null;
            for (int index = 0; index < 2; index++) {
                final int second = (index + 1) & 1;
                TotalAggregator[] aggregators = new TotalAggregator[axisInfos[second].maxDepth + 1];
                for (int i = 1; i < aggregators.length - 1; i++) {
                    List<String> aggs = query.getAggregators(axisInfos[second].uniqueLevelNames.get(i - 1));
                    String totalFunctionName = aggs != null && aggs.size() > 0 ? aggs.get(0) : null;
                    aggregators[i] = StringUtils.isNotBlank(totalFunctionName)
                            ? TotalAggregator.newInstanceByFunctionName(totalFunctionName)
                            : null;
                }
                List<String> aggs = query.getAggregators(axisInfos[second].axis.getAxisOrdinal().name());
                String totalFunctionName = aggs != null && aggs.size() > 0 ? aggs.get(0) : null;
                aggregators[0] = StringUtils.isNotBlank(totalFunctionName)
                        ? TotalAggregator.newInstanceByFunctionName(totalFunctionName)
                        : null;
                builder = new TotalsListsBuilder(selectedMeasures, aggregators, cellSet, axisInfos[index],
                        axisInfos[second]);
                totals[index] = builder.buildTotalsLists();
            }
            result.setLeftOffset(axisInfos[0].maxDepth);
            result.setRowTotalsLists(totals[1]);
            result.setColTotalsLists(totals[0]);
        }
    }

    public ThinQuery zoomIn(String queryName, List<List<Integer>> realPositions) {

        try {
            if (context.containsKey(queryName)) {
                CellSet cs = context.get(queryName).getOlapResult();
                ThinQuery old = context.get(queryName).getOlapQuery();
                Cube cub = olapDiscoverService.getNativeCube(old.getCube());
                Query q = Fat.convert(old, cub);

                if (cs == null) {
                    throw new SaikuServiceException("Cannot zoom in if last cellset is null");
                }
                if (realPositions == null || realPositions.size() == 0) {
                    throw new SaikuServiceException("Cannot zoom in if zoom in position is empty");
                }

                Map<Hierarchy, Set<Member>> memberSelection = new HashMap<>();
                for (List<Integer> position : realPositions) {
                    for (int k = 0; k < position.size(); k++) {
                        Position p = cs.getAxes().get(k).getPositions().get(position.get(k));
                        List<Member> members = p.getMembers();
                        for (Member m : members) {
                            Hierarchy h = m.getHierarchy();
                            if (!memberSelection.containsKey(h)) {
                                Set<Member> mset = new HashSet<>();
                                memberSelection.put(h, mset);
                            }
                            memberSelection.get(h).add(m);
                        }
                    }
                }

                for (Hierarchy h : memberSelection.keySet()) {
                    QueryHierarchy qh = q.getHierarchy(h);
                    for (QueryLevel ql : qh.getActiveQueryLevels()) {
                        ql.getInclusions().clear();
                        ql.getExclusions().clear();
                        ql.setMdxSetExpression(null);
                    }
                    for (Member m : memberSelection.get(h)) {
                        qh.includeMember(m);
                    }
                }
                ThinQuery tqAfter = Thin.convert(q, old.getCube());
                q = null;
                return tqAfter;
            } else {
                throw new SaikuServiceException("Cannot get query result from context: " + queryName);
            }

        } catch (Exception e) {
            throw new SaikuServiceException("Error zoom in on query: " + queryName, e);
        }

    }

    public ThinQuery drillacross(String queryName, List<Integer> cellPosition, Map<String, List<String>> levels) {
        try {
            ThinQuery old = context.get(queryName).getOlapQuery();
            Cube cub = olapDiscoverService.getNativeCube(old.getCube());
            Query query = Fat.convert(old, cub);
            CellSet cs = context.get(queryName).getOlapResult();

            Set<Level> levelSet = new HashSet<>();
            if (cs == null) {
                throw new SaikuServiceException("Cannot drill across. Last CellSet empty");
            }
            for (int i = 0; i < cellPosition.size(); i++) {
                List<Member> members = cs.getAxes().get(i).getPositions().get(cellPosition.get(i)).getMembers();
                for (Member m : members) {
                    QueryHierarchy qh = query.getHierarchy(m.getHierarchy());
                    if (qh.getHierarchy().getDimension().getName().equals("Measures")) {
                        Measure measure = query.getMeasure(m.getName());
                        if (!query.getDetails().getMeasures().contains(measure)) {
                            query.getDetails().add(measure);
                        }

                    } else {
                        qh.clearSelection();
                        qh.clearFilters();
                        qh.clearSort();
                        query.moveHierarchy(qh, Axis.FILTER);
                        qh.includeMember(m);
                        levelSet.add(m.getLevel());
                    }

                }
            }
            boolean clearedMeasures = false;

            if (levels != null) {
                for (String key : levels.keySet()) {
                    String dimensionName = key.split("###")[0];

                    if ("Measures".equals(dimensionName)) {
                        if (!clearedMeasures) {
                            query.getDetails().getMeasures().clear();
                            clearedMeasures = true;
                        }
                        for (String measureName : levels.get(key)) {
                            Measure measure = query.getMeasure(measureName);
                            if (measure != null) {
                                query.getDetails().add(measure);
                            } else {
                                for (Measure m : cub.getMeasures()) {
                                    if (m.getUniqueName().equals(measureName)) {
                                        query.getDetails().add(m);
                                    }
                                }
                            }
                        }
                        continue;
                    }
                    String hierarchyName = key.split("###")[1];
                    Dimension d = cub.getDimensions().get(dimensionName);
                    Hierarchy h = d.getHierarchies().get(hierarchyName);
                    QueryHierarchy qh = query.getHierarchy(h);
                    for (Level l : h.getLevels()) {
                        for (String levelU : levels.get(key)) {
                            if (l.getUniqueName().equals(levelU) || l.getName().equals(levelU)) {
                                qh.includeLevel(l);
                            }
                        }
                    }
                    if (qh.getActiveQueryLevels().size() > 0) {
                        query.moveHierarchy(qh, Axis.ROWS);
                    }
                }
            }
            if (query.getDetails().getMeasures().size() == 0) {
                QueryHierarchy qh = query.getHierarchy("Measures");
                Member defaultMeasure = qh.getHierarchy().getDefaultMember();
                query.getDetails().add(query.getMeasure(defaultMeasure.getName()));
            }
            return Thin.convert(query, old.getCube());
        } catch (Exception e) {
            throw new SaikuServiceException("Error drilling across: " + queryName, e);
        }
    }

    public boolean isOldQuery(String xml) {
        return StringUtils.isNotBlank(xml) && xml.trim().startsWith("<?xml");
    }

    public ThinQuery convertQuery(String xml) throws Exception {
        if (StringUtils.isNotBlank(xml) && xml.trim().startsWith("<?xml")) {
            QueryDeserializer qd = new QueryDeserializer();
            SaikuCube scube = qd.getFakeCube(xml);
            OlapConnection con = olapDiscoverService.getNativeConnection(scube.getConnection());
            IQuery query = qd.unparse(xml, con);

            if (QueryType.QM.equals(query.getType())) {
                OlapQuery qr = (OlapQuery) query;
                Query sQ = QueryConverter.convertQuery(qr.getQuery());
                SaikuCube converted = ObjectUtil.convert(scube.getConnection(), sQ.getCube());
                return Thin.convert(sQ, converted);
            } else {
                SaikuCube converted = ObjectUtil.convert(scube.getConnection(),
                        olapDiscoverService.getNativeCube(scube));
                return new ThinQuery(query.getName(), converted, query.getMdx());
            }
        }
        return null;

    }
}