org.saiku.web.rest.resources.QueryResource.java Source code

Java tutorial

Introduction

Here is the source code for org.saiku.web.rest.resources.QueryResource.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 org.saiku.web.rest.resources;

import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import org.saiku.olap.dto.*;
import org.saiku.olap.dto.filter.SaikuFilter;
import org.saiku.olap.dto.resultset.CellDataSet;
import org.saiku.olap.query.IQuery;
import org.saiku.olap.util.ObjectUtil;
import org.saiku.olap.util.SaikuProperties;
import org.saiku.olap.util.formatter.CellSetFormatter;
import org.saiku.olap.util.formatter.FlattenedCellSetFormatter;
import org.saiku.olap.util.formatter.HierarchicalCellSetFormatter;
import org.saiku.olap.util.formatter.ICellSetFormatter;
import org.saiku.service.olap.OlapDiscoverService;
import org.saiku.service.olap.OlapQueryService;
import org.saiku.service.util.exception.SaikuServiceException;
import org.saiku.web.export.JSConverter;
import org.saiku.web.rest.objects.MdxQueryObject;
import org.saiku.web.rest.objects.SavedQuery;
import org.saiku.web.rest.objects.SelectionRestObject;
import org.saiku.web.rest.objects.resultset.QueryResult;
import org.saiku.web.rest.util.RestUtil;
import org.saiku.web.rest.util.ServletUtil;
import org.saiku.web.svg.PdfReport;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.io.StringReader;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;

import javax.servlet.ServletException;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;

/**
 * QueryServlet contains all the methods required when manipulating an OLAP Query.
 * @author Tom Barber
 *
 */
@Component
@Path("/saiku/{username}/query")
@XmlAccessorType(XmlAccessType.NONE)
@Deprecated
public class QueryResource {

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

    private OlapQueryService olapQueryService;
    private ISaikuRepository repository;

    //@Autowired
    public void setOlapQueryService(OlapQueryService olapqs) {
        olapQueryService = olapqs;
    }

    //@Autowired
    public void setRepository(ISaikuRepository repository) {
        this.repository = repository;
    }

    //@Autowired
    public void setOlapDiscoverService(OlapDiscoverService olapds) {
        OlapDiscoverService olapDiscoverService = olapds;
    }

    /*
     * Query methods
     */

    /**
     * Return a list of open queries.
     */
    @GET
    @Produces({ "application/json" })
    public List<String> getQueries() {
        return olapQueryService.getQueries();
    }

    @GET
    @Produces({ "application/json" })
    @Path("/{queryname}")
    public SaikuQuery getQuery(@PathParam("queryname") String queryName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "\tGET");
        }
        return olapQueryService.getQuery(queryName);
    }

    /**
     * Delete query from the query pool.
     * @return a HTTP 410(Works) or HTTP 404(Call failed).
     */
    @DELETE
    @Path("/{queryname}")
    public Status deleteQuery(@PathParam("queryname") String queryName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "\tDELETE");
        }
        try {
            olapQueryService.deleteQuery(queryName);
            return (Status.GONE);
        } catch (Exception e) {
            log.error("Cannot delete query (" + queryName + ")", e);
            throw new WebApplicationException(Response.serverError().entity(e).build());
        }
    }

    /**
     * Create a new Saiku Query.
     * @param connectionName the name of the Saiku connection.
     * @param cubeName the name of the cube.
     * @param catalogName the catalog name.
     * @param schemaName the name of the schema.
     * @param queryName the name you want to assign to the query.
     * @return 
     * 
     * @return a query model.
     * 
     * @see 
     */
    @POST
    @Produces({ "application/json" })
    @Path("/{queryname}")
    public SaikuQuery createQuery(@FormParam("connection") String connectionName,
            @FormParam("cube") String cubeName, @FormParam("catalog") String catalogName,
            @FormParam("schema") String schemaName, @FormParam("xml") String xmlOld,
            @PathParam("queryname") String queryName, MultivaluedMap<String, String> formParams)
            throws ServletException {
        try {
            String file = null, xml = null;
            if (formParams != null) {
                xml = formParams.containsKey("xml") ? formParams.getFirst("xml") : xmlOld;
                file = formParams.containsKey("file") ? formParams.getFirst("file") : null;
                if (StringUtils.isNotBlank(file)) {
                    Response f = repository.getResource(file);
                    xml = new String((byte[]) f.getEntity());
                }
            } else {
                xml = xmlOld;
            }
            if (log.isDebugEnabled()) {
                log.debug(
                        "TRACK\t" + "\t/query/" + queryName + "\tPOST\t xml:" + (xml == null) + " file:" + (file));
            }
            SaikuCube cube = new SaikuCube(connectionName, cubeName, cubeName, cubeName, catalogName, schemaName);
            if (StringUtils.isNotBlank(xml)) {
                String query = ServletUtil.replaceParameters(formParams, xml);
                return olapQueryService.createNewOlapQuery(queryName, query);
            }
            return olapQueryService.createNewOlapQuery(queryName, cube);
        } catch (Exception e) {
            throw new WebApplicationException(e);
        }
    }

    /**
     * Get the query properties.
     * @param queryName
     * @return
     */
    @GET
    @Produces({ "application/json" })
    @Path("/{queryname}/properties")
    public Properties getProperties(@PathParam("queryname") String queryName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/properties\tGET");
        }
        return olapQueryService.getProperties(queryName);
    }

    /**
     * Set the query properties
     * @param queryName
     * @param properties
     * @return
     */
    @POST
    @Produces({ "application/json" })
    @Path("/{queryname}/properties")
    public Properties setProperties(@PathParam("queryname") String queryName,
            @FormParam("properties") String properties) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/properties\tPOST");
        }
        try {
            Properties props = new Properties();
            StringReader sr = new StringReader(properties);
            props.load(sr);
            return olapQueryService.setProperties(queryName, props);
        } catch (Exception e) {
            log.error("Cannot set properties for query (" + queryName + ")", e);
            return null;
        }

    }

    @POST
    @Produces({ "application/json" })
    @Path("/{queryname}/properties/{propertyKey}")
    public Properties setProperties(@PathParam("queryname") String queryName,
            @PathParam("propertyKey") String propertyKey, @FormParam("propertyValue") String propertyValue) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/properties/" + propertyKey + "\tPOST");
        }
        try {
            Properties props = new Properties();
            props.put(propertyKey, propertyValue);
            return olapQueryService.setProperties(queryName, props);
        } catch (Exception e) {
            log.error("Cannot set property (" + propertyKey + " ) for query (" + queryName + ")", e);
            return null;
        }

    }

    @GET
    @Path("/{queryname}/mdx")
    public MdxQueryObject getMDXQuery(@PathParam("queryname") String queryName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/mdx/\tGET");
        }
        try {
            String mdx = olapQueryService.getMDXQuery(queryName);
            return new MdxQueryObject(mdx);
        } catch (Exception e) {
            log.error("Cannot get mdx for query (" + queryName + ")", e);
            return null;
        }
    }

    @POST
    @Consumes("application/x-www-form-urlencoded")
    @Path("/{queryname}/mdx")
    public void setMDXQuery(@PathParam("queryname") String queryName, @FormParam("mdx") String mdx) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/mdx/\tPOST");
        }
        try {
            olapQueryService.setMdx(queryName, mdx);
        } catch (Exception e) {
            log.error("Cannot set mdx for query (" + queryName + ")", e);
        }
    }

    @GET
    @Produces({ "application/json" })
    @Path("/{queryname}/xml")
    public SavedQuery getQueryXml(@PathParam("queryname") String queryName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/xml/\tGET");
        }
        try {
            String xml = olapQueryService.getQueryXml(queryName);
            return new SavedQuery(queryName, null, xml);
        } catch (Exception e) {
            log.error("Cannot get xml for query (" + queryName + ")", e);
            return null;
        }

    }

    @GET
    @Produces({ "application/vnd.ms-excel" })
    @Path("/{queryname}/export/xls")
    public Response getQueryExcelExport(@PathParam("queryname") String queryName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/export/xls/\tGET");
        }
        return getQueryExcelExport(queryName, "flattened");
    }

    @GET
    @Produces({ "application/vnd.ms-excel" })
    @Path("/{queryname}/export/xls/{format}")
    public Response getQueryExcelExport(@PathParam("queryname") String queryName,
            @PathParam("format") @DefaultValue("flattened") String format) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/export/xls/" + format + "\tGET");
        }
        try {
            byte[] doc = olapQueryService.getExport(queryName, "xls", format);
            String name = SaikuProperties.webExportExcelName + "." + SaikuProperties.webExportExcelFormat;
            return Response.ok(doc, MediaType.APPLICATION_OCTET_STREAM)
                    .header("content-disposition", "attachment; filename = " + name)
                    .header("content-length", doc.length).build();
        } catch (Exception e) {
            log.error("Cannot get excel for query (" + queryName + ")", e);
            return Response.serverError().build();
        }
    }

    @GET
    @Produces({ "text/csv" })
    @Path("/{queryname}/export/csv")
    public Response getQueryCsvExport(@PathParam("queryname") String queryName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/export/csv\tGET");
        }
        return getQueryCsvExport(queryName, "flattened");
    }

    @GET
    @Produces({ "text/csv" })
    @Path("/{queryname}/export/csv/{format}")
    public Response getQueryCsvExport(@PathParam("queryname") String queryName,
            @PathParam("format") String format) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/export/csv/" + format + "\tGET");
        }
        try {
            byte[] doc = olapQueryService.getExport(queryName, "csv", format);
            String name = SaikuProperties.webExportCsvName;
            return Response.ok(doc, MediaType.APPLICATION_OCTET_STREAM)
                    .header("content-disposition", "attachment; filename = " + name + ".csv")
                    .header("content-length", doc.length).build();
        } catch (Exception e) {
            log.error("Cannot get csv for query (" + queryName + ")", e);
            return Response.serverError().build();
        }
    }

    @POST
    @Produces({ "application/pdf" })
    @Path("/{queryname}/export/pdf")
    public Response exportPdfWithChart(@PathParam("queryname") String queryName,
            @PathParam("svg") @DefaultValue("") String svg) {
        return exportPdfWithChartAndFormat(queryName, null, svg);
    }

    @GET
    @Produces({ "application/pdf" })
    @Path("/{queryname}/export/pdf")
    public Response exportPdf(@PathParam("queryname") String queryName) {
        return exportPdfWithChartAndFormat(queryName, null, null);
    }

    @GET
    @Produces({ "application/pdf" })
    @Path("/{queryname}/export/pdf/{format}")
    public Response exportPdfWithFormat(@PathParam("queryname") String queryName,
            @PathParam("format") String format) {
        return exportPdfWithChartAndFormat(queryName, format, null);
    }

    @POST
    @Produces({ "application/pdf" })
    @Path("/{queryname}/export/pdf/{format}")
    public Response exportPdfWithChartAndFormat(@PathParam("queryname") String queryName,
            @PathParam("format") String format, @FormParam("svg") @DefaultValue("") String svg) {

        try {
            PdfReport pdf = new PdfReport();
            CellDataSet cs = null;
            if (StringUtils.isNotBlank(format)) {
                cs = olapQueryService.execute(queryName, format);
            } else {
                cs = olapQueryService.execute(queryName);
            }

            byte[] doc = pdf.pdf(cs, svg);
            return Response.ok(doc).type("application/pdf")
                    .header("content-disposition", "attachment; filename = export.pdf")
                    .header("content-length", doc.length).build();
        } catch (Exception e) {
            log.error("Error exporting query to  PDF", e);
            return Response.serverError().entity(e.getMessage()).status(Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @GET
    @Produces({ "text/html" })
    @Path("/{queryname}/export/html/{format}")
    public Response exportHtml(@PathParam("queryname") String queryName, @PathParam("format") String format,
            @QueryParam("css") @DefaultValue("false") Boolean css,
            @QueryParam("tableonly") @DefaultValue("false") Boolean tableonly,
            @QueryParam("wrapcontent") @DefaultValue("true") Boolean wrapcontent) {
        try {
            CellDataSet cs = null;
            if (StringUtils.isNotBlank(format)) {
                cs = olapQueryService.execute(queryName, format);
            } else {
                cs = olapQueryService.execute(queryName);
            }
            QueryResult qr = RestUtil.convert(cs);
            String content = JSConverter.convertToHtml(qr, wrapcontent);
            String html = "";
            if (!tableonly) {
                html += "<!DOCTYPE html><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n";
                if (css) {
                    html += "<style>\n";
                    InputStream is = JSConverter.class.getResourceAsStream("saiku.table.full.css");
                    String cssContent = IOUtils.toString(is);
                    html += cssContent;
                    html += "</style>\n";
                }
                html += "</head>\n<body><div class='workspace_results'>\n";
            }
            html += content;
            if (!tableonly) {
                html += "\n</div></body></html>";
            }
            return Response.ok(html).build();
        } catch (Exception e) {
            log.error("Error exporting query to HTML", e);
            return Response.serverError().entity(e.getMessage()).status(Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @DELETE
    @Path("/{queryname}/result")
    public Status cancel(@PathParam("queryname") String queryName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/result\tDELETE");
        }
        try {

            olapQueryService.cancel(queryName);
            return Response.Status.OK;
        } catch (Exception e) {
            log.error("Cannot execute query (" + queryName + ")", e);
            return Response.Status.INTERNAL_SERVER_ERROR;
        }
    }

    @GET
    @Produces({ "application/json" })
    @Path("/{queryname}/result")
    public QueryResult execute(@PathParam("queryname") String queryName,
            @QueryParam("limit") @DefaultValue("0") int limit) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/result\tGET");
        }
        try {

            CellDataSet cs = olapQueryService.execute(queryName);
            return RestUtil.convert(cs, limit);
        } catch (Exception e) {
            log.error("Cannot execute query (" + queryName + ")", e);
            String error = ExceptionUtils.getRootCauseMessage(e);
            return new QueryResult(error);
        }
    }

    @POST
    @Produces({ "application/json" })
    @Path("/{queryname}/result/{format}")
    public QueryResult executeMdx(@PathParam("queryname") String queryName, @PathParam("format") String formatter,
            @FormParam("mdx") String mdx, @FormParam("limit") @DefaultValue("0") int limit) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/result" + formatter + "\tPOST");
        }
        try {
            ICellSetFormatter icf;
            formatter = formatter == null ? "" : formatter.toLowerCase();
            switch (formatter) {
            case "flat":
                icf = new CellSetFormatter();
                break;
            case "hierarchical":
                icf = new HierarchicalCellSetFormatter();
                break;
            case "flattened":
                icf = new FlattenedCellSetFormatter();
                break;
            default:
                icf = new FlattenedCellSetFormatter();
                break;
            }

            olapQueryService.qm2mdx(queryName);

            if (olapQueryService.isMdxDrillthrough(queryName, mdx)) {
                Long start = (new Date()).getTime();
                ResultSet rs = olapQueryService.drillthrough(queryName, mdx);
                QueryResult rsc = RestUtil.convert(rs);
                Long runtime = (new Date()).getTime() - start;
                rsc.setRuntime(runtime.intValue());
                return rsc;
            }
            CellDataSet cs = olapQueryService.executeMdx(queryName, mdx, icf);
            return RestUtil.convert(cs, limit);
        } catch (Exception e) {
            log.error("Cannot execute query (" + queryName + ") using mdx:\n" + mdx, e);
            String error = ExceptionUtils.getRootCauseMessage(e);
            return new QueryResult(error);
        }
    }

    @POST
    @Produces({ "application/json" })
    @Path("/{queryname}/result")
    public QueryResult executeMdx(@PathParam("queryname") String queryName, @FormParam("mdx") String mdx,
            @FormParam("limit") @DefaultValue("0") int limit) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/result\tPOST\t" + mdx);
        }
        try {
            return executeMdx(queryName, null, mdx, limit);
        } catch (Exception e) {
            log.error("Cannot execute query (" + queryName + ") using mdx:\n" + mdx, e);
            String error = ExceptionUtils.getRootCauseMessage(e);
            return new QueryResult(error);
        }
    }

    @GET
    @Produces({ "application/json" })
    @Path("/{queryname}/result/metadata/dimensions/{dimension}/hierarchies/{hierarchy}/levels/{level}")
    public List<SimpleCubeElement> getLevelMembers(@PathParam("queryname") String queryName,
            @PathParam("dimension") String dimensionName, @PathParam("hierarchy") String hierarchyName,
            @PathParam("level") String levelName, @QueryParam("result") @DefaultValue("true") boolean result,
            @QueryParam("search") String searchString,
            @QueryParam("searchlimit") @DefaultValue("-1") int searchLimit) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/result/metadata/dimensions/" + dimensionName
                    + "/hierarchies/" + hierarchyName + "/levels/" + levelName + "\tGET");
        }
        try {
            return olapQueryService.getResultMetadataMembers(queryName, result, dimensionName, hierarchyName,
                    levelName, searchString, searchLimit);
        } catch (Exception e) {
            log.error("Cannot execute query (" + queryName + ")", e);
            String error = ExceptionUtils.getRootCauseMessage(e);
            throw new WebApplicationException(Response.serverError().entity(error).build());
        }
    }

    @POST
    @Produces({ "application/json" })
    @Path("/{queryname}/qm2mdx")
    public SaikuQuery transformQm2Mdx(@PathParam("queryname") String queryName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/qm2mdx\tPOST\t");
        }
        try {
            olapQueryService.qm2mdx(queryName);
            return olapQueryService.getQuery(queryName);
        } catch (Exception e) {
            log.error("Cannot transform Qm2Mdx query (" + queryName + ")", e);
        }
        return null;
    }

    @GET
    @Produces({ "application/json" })
    @Path("/{queryname}/explain")
    public QueryResult getExplainPlan(@PathParam("queryname") String queryName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/explain\tGET");
        }
        QueryResult rsc;
        ResultSet rs = null;
        try {
            Long start = (new Date()).getTime();
            rs = olapQueryService.explain(queryName);
            rsc = RestUtil.convert(rs);
            Long runtime = (new Date()).getTime() - start;
            rsc.setRuntime(runtime.intValue());

        } catch (Exception e) {
            log.error("Cannot get explain plan for query (" + queryName + ")", e);
            String error = ExceptionUtils.getRootCauseMessage(e);
            rsc = new QueryResult(error);

        }
        // no need to close resultset, its an EmptyResultset
        return rsc;

    }

    @GET
    @Produces({ "application/json" })
    @Path("/{queryname}/drillthrough")
    public QueryResult drillthrough(@PathParam("queryname") String queryName,
            @QueryParam("maxrows") @DefaultValue("100") Integer maxrows, @QueryParam("position") String position,
            @QueryParam("returns") String returns) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/drillthrough\tGET");
        }
        QueryResult rsc;
        ResultSet rs = null;
        try {
            Long start = (new Date()).getTime();
            if (position == null) {
                rs = olapQueryService.drillthrough(queryName, maxrows, returns);
            } else {
                String[] positions = position.split(":");
                List<Integer> cellPosition = new ArrayList<>();

                for (String p : positions) {
                    Integer pInt = Integer.parseInt(p);
                    cellPosition.add(pInt);
                }

                rs = olapQueryService.drillthrough(queryName, cellPosition, maxrows, returns);
            }
            rsc = RestUtil.convert(rs);
            Long runtime = (new Date()).getTime() - start;
            rsc.setRuntime(runtime.intValue());

        } catch (Exception e) {
            log.error("Cannot execute query (" + queryName + ")", e);
            String error = ExceptionUtils.getRootCauseMessage(e);
            rsc = new QueryResult(error);

        } finally {
            if (rs != null) {
                Statement statement = null;
                Connection con = null;
                try {
                    statement = rs.getStatement();
                    con = rs.getStatement().getConnection();
                } catch (Exception e) {
                    throw new SaikuServiceException(e);
                } finally {
                    try {
                        rs.close();
                        if (statement != null) {
                            statement.close();
                        }
                    } catch (Exception ee) {
                    }

                    rs = null;
                }
            }
        }
        return rsc;

    }

    @POST
    @Produces({ "application/json" })
    @Path("/{queryname}/drillacross")
    public SaikuQuery drillacross(@PathParam("queryname") String queryName, @FormParam("position") String position,
            @FormParam("drill") String returns) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/drillacross\tPOST");
        }
        try {
            String[] positions = position.split(":");
            List<Integer> cellPosition = new ArrayList<>();
            for (String p : positions) {
                Integer pInt = Integer.parseInt(p);
                cellPosition.add(pInt);
            }
            ObjectMapper mapper = new ObjectMapper();
            JavaType ct = mapper.getTypeFactory().constructCollectionType(ArrayList.class, String.class);

            JavaType st = mapper.getTypeFactory().uncheckedSimpleType(String.class);
            Map<String, List<String>> levels = mapper.readValue(returns,
                    mapper.getTypeFactory().constructMapType(Map.class, st, ct));

            return olapQueryService.drillacross(queryName, cellPosition, levels);
        } catch (Exception e) {
            log.error("Cannot execute query (" + queryName + ")", e);
            String error = ExceptionUtils.getRootCauseMessage(e);
            throw new WebApplicationException(Response.serverError().entity(error).build());
        }
    }

    @GET
    @Produces({ "text/csv" })
    @Path("/{queryname}/drillthrough/export/csv")
    public Response getDrillthroughExport(@PathParam("queryname") String queryName,
            @QueryParam("maxrows") @DefaultValue("100") Integer maxrows, @QueryParam("position") String position,
            @QueryParam("returns") String returns) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/drillthrough/export/csv (maxrows:" + maxrows
                    + " position" + position + ")\tGET");
        }
        ResultSet rs = null;

        try {
            if (position == null) {
                rs = olapQueryService.drillthrough(queryName, maxrows, returns);
            } else {
                String[] positions = position.split(":");
                List<Integer> cellPosition = new ArrayList<>();

                for (String p : positions) {
                    Integer pInt = Integer.parseInt(p);
                    cellPosition.add(pInt);
                }

                rs = olapQueryService.drillthrough(queryName, cellPosition, maxrows, returns);
            }
            byte[] doc = olapQueryService.exportResultSetCsv(rs);
            String name = SaikuProperties.webExportCsvName;
            return Response.ok(doc, MediaType.APPLICATION_OCTET_STREAM)
                    .header("content-disposition", "attachment; filename = " + name + "-drillthrough.csv")
                    .header("content-length", doc.length).build();

        } catch (Exception e) {
            log.error("Cannot export drillthrough query (" + queryName + ")", e);
            return Response.serverError().build();
        } finally {
            if (rs != null) {
                try {
                    Statement statement = rs.getStatement();
                    statement.close();
                    rs.close();
                } catch (SQLException e) {
                    throw new SaikuServiceException(e);
                } finally {
                    rs = null;
                }
            }
        }

    }

    @GET
    @Produces({ "application/json" })
    @Path("/{queryname}/result/{format}")
    public QueryResult execute(@PathParam("queryname") String queryName, @PathParam("format") String formatter,
            @QueryParam("limit") @DefaultValue("0") int limit) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/result" + formatter + "\tGET");
        }
        try {
            CellDataSet cs = olapQueryService.execute(queryName, formatter);
            return RestUtil.convert(cs, limit);
        } catch (Exception e) {
            log.error("Cannot execute query (" + queryName + ")", e);
            String error = ExceptionUtils.getRootCauseMessage(e);
            return new QueryResult(error);
        }
    }

    /*
     * Axis Methods.
     */

    /**
     * Return a list of dimensions for an axis in a query.
     * @param queryName the name of the query.
     * @param axisName the name of the axis.
     * @return a list of available dimensions.
     * @see DimensionRestPojo
     */
    @GET
    @Produces({ "application/json" })
    @Path("/{queryname}/axis/{axis}")
    public List<SaikuDimensionSelection> getAxisInfo(@PathParam("queryname") String queryName,
            @PathParam("axis") String axisName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "\tGET");
        }
        return olapQueryService.getAxisSelection(queryName, axisName);
    }

    /**
     * Remove all dimensions and selections on an axis
     * @param queryName the name of the query.
     * @param axisName the name of the axis.
     */
    @DELETE
    @Produces({ "application/json" })
    @Path("/{queryname}/axis/{axis}")
    public Response clearAxis(@PathParam("queryname") String queryName, @PathParam("axis") String axisName) {
        try {
            if (log.isDebugEnabled()) {
                log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "\tDELETE");
            }
            axisName = StringUtils.isNotBlank(axisName) ? axisName.toUpperCase() : null;
            if (axisName != null) {
                IQuery query = olapQueryService.clearAxis(queryName, axisName);
                return Response.ok().entity(ObjectUtil.convert(query)).build();

            }
            throw new Exception("Clear Axis: Axis name cannot be null");
        } catch (Exception e) {
            log.error("Cannot clear axis for query (" + queryName + ")", e);
            return Response.serverError().entity(e.getMessage()).status(Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @DELETE
    @Produces({ "application/json" })
    @Path("/{queryname}/axis/")
    public void clearAllAxisSelections(@PathParam("queryname") String queryName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/axis\tDELETE");
        }
        olapQueryService.resetQuery(queryName);
    }

    @PUT
    @Produces({ "application/json" })
    @Path("/{queryname}/swapaxes")
    public SaikuQuery swapAxes(@PathParam("queryname") String queryName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/swapaxes\tPUT");
        }
        IQuery query = olapQueryService.swapAxes(queryName);
        return ObjectUtil.convert(query);
    }

    @POST
    @Produces({ "application/json" })
    @Path("/{queryname}/cell/{position}/{value}")
    public Status setCell(@PathParam("queryname") String queryName, @PathParam("position") String position,
            @PathParam("value") String value) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/cell/" + position + "/" + value + "\tGET");
        }
        String[] positions = position.split(":");
        List<Integer> cellPosition = new ArrayList<>();

        for (String p : positions) {
            Integer pInt = Integer.parseInt(p);
            cellPosition.add(pInt);
        }

        olapQueryService.setCellValue(queryName, cellPosition, value);
        return Status.OK;

    }

    /*
     * Dimension Methods
     */

    /**
     * Return a dimension and its selections for an axis in a query.
     * @param queryName the name of the query.
     * @param axis the name of the axis.
     * @param dimension the name of the axis.
     * @return a list of available dimensions.
     * @see DimensionRestPojo
     */
    @GET
    @Produces({ "application/json" })
    @Path("/{queryname}/axis/{axis}/dimension/{dimension}")
    public SaikuDimensionSelection getAxisDimensionInfo(@PathParam("queryname") String queryName,
            @PathParam("axis") String axis, @PathParam("dimension") String dimension) {
        try {
            if (log.isDebugEnabled()) {
                log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axis + "/dimension/" + dimension
                        + "\tGET");
            }
            return olapQueryService.getAxisDimensionSelections(queryName, axis, dimension);
        } catch (Exception e) {
            log.error("Cannot decode dimension " + dimension + " for query (" + queryName + ")", e);
            return olapQueryService.getAxisDimensionSelections(queryName, axis, dimension);
        }
    }

    /**
     * Move a dimension from one axis to another.
     * @param queryName the name of the query.
     * @param axisName the name of the axis.
     * @param dimensionName the name of the dimension. 
     * 
     * @return HTTP 200 or HTTP 500.
     * 
     * @see Status
     */
    @POST
    @Path("/{queryname}/axis/{axis}/dimension/{dimension}")
    public Response moveDimension(@PathParam("queryname") String queryName, @PathParam("axis") String axisName,
            @PathParam("dimension") String dimensionName, @FormParam("position") @DefaultValue("-1") int position) {
        try {
            if (log.isDebugEnabled()) {
                log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/dimension/" + dimensionName
                        + "\tPOST");
            }
            olapQueryService.moveDimension(queryName, axisName, dimensionName, position);
            return Response.ok().build();
        } catch (Exception e) {
            log.error("Cannot move dimension " + dimensionName + " for query (" + queryName + ")", e);
            return Response.serverError().entity(e.getMessage()).status(Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    /**
     * Delete a dimension.
     * @return 
     */
    @DELETE
    @Path("/{queryname}/axis/{axis}/dimension/{dimension}")
    public Response deleteDimension(@PathParam("queryname") String queryName, @PathParam("axis") String axisName,
            @PathParam("dimension") String dimensionName) {
        try {
            if (log.isDebugEnabled()) {
                log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/dimension/" + dimensionName
                        + "\tDELETE");
            }
            olapQueryService.removeDimension(queryName, axisName, dimensionName);
            return Response.ok().build();
        } catch (Exception e) {
            log.error("Cannot remove dimension " + dimensionName + " for query (" + queryName + ")", e);
            return Response.serverError().entity(e.getMessage()).status(Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @PUT
    @Consumes("application/x-www-form-urlencoded")
    @Path("/{queryname}/zoomin")
    public SaikuQuery zoomIn(@PathParam("queryname") String queryName,
            @FormParam("selections") String positionListString) {
        try {

            if (log.isDebugEnabled()) {
                log.debug("TRACK\t" + "\t/query/" + queryName + "/zoomIn\tPUT");
            }
            List<List<Integer>> realPositions = new ArrayList<>();
            if (StringUtils.isNotBlank(positionListString)) {
                ObjectMapper mapper = new ObjectMapper();
                String[] positions = mapper.readValue(positionListString,
                        mapper.getTypeFactory().constructArrayType(String.class));
                if (positions != null && positions.length > 0) {
                    for (String position : positions) {
                        String[] rPos = position.split(":");
                        List<Integer> cellPosition = new ArrayList<>();

                        for (String p : rPos) {
                            Integer pInt = Integer.parseInt(p);
                            cellPosition.add(pInt);
                        }
                        realPositions.add(cellPosition);
                    }
                }
            }
            IQuery query = olapQueryService.zoomIn(queryName, realPositions);
            return ObjectUtil.convert(query);

        } catch (Exception e) {
            log.error("Cannot updates selections for query (" + queryName + ")", e);
            throw new WebApplicationException(e);
        }
    }

    @PUT
    @Consumes("application/x-www-form-urlencoded")
    @Path("/{queryname}/axis/{axis}/dimension/{dimension}/")
    public Response updateSelections(@PathParam("queryname") String queryName, @PathParam("axis") String axisName,
            @PathParam("dimension") String dimensionName, @FormParam("selections") String selectionJSON) {
        try {
            if (log.isDebugEnabled()) {
                log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/dimension/" + dimensionName
                        + "\tPUT\t");
            }

            if (selectionJSON != null) {
                ObjectMapper mapper = new ObjectMapper();
                List<SelectionRestObject> selections = mapper.readValue(selectionJSON, mapper.getTypeFactory()
                        .constructCollectionType(ArrayList.class, SelectionRestObject.class));

                // remove stuff first, then add, removing removes all selections for that level first
                for (SelectionRestObject selection : selections) {
                    if (selection.getType() != null && "member".equals(selection.getType().toLowerCase())) {
                        if (selection.getAction() != null && "delete".equals(selection.getAction().toLowerCase())) {
                            olapQueryService.removeMember(queryName, dimensionName, selection.getUniquename(),
                                    "MEMBER");
                        }
                    }
                    if (selection.getType() != null && "level".equals(selection.getType().toLowerCase())) {
                        if (selection.getAction() != null && "delete".equals(selection.getAction().toLowerCase())) {
                            olapQueryService.removeLevel(queryName, dimensionName, selection.getHierarchy(),
                                    selection.getUniquename());
                        }
                    }
                }
                for (SelectionRestObject selection : selections) {
                    if (selection.getType() != null && "member".equals(selection.getType().toLowerCase())) {
                        if (selection.getAction() != null && "add".equals(selection.getAction().toLowerCase())) {
                            olapQueryService.includeMember(queryName, dimensionName, selection.getUniquename(),
                                    "MEMBER", selection.getTotalsFunction(), -1);
                        }
                    }
                    if (selection.getType() != null && "level".equals(selection.getType().toLowerCase())) {
                        if (selection.getAction() != null && "add".equals(selection.getAction().toLowerCase())) {
                            olapQueryService.includeLevel(queryName, dimensionName, selection.getHierarchy(),
                                    selection.getUniquename(), selection.getTotalsFunction());
                        }
                    }
                }
                SaikuDimensionSelection dimsels = getAxisDimensionInfo(queryName, axisName, dimensionName);
                if (dimsels != null && dimsels.getSelections().size() == 0) {
                    moveDimension(queryName, "UNUSED", dimensionName, -1);
                }
                return Response.ok().build();
            }
            throw new Exception("Form did not contain 'selections' parameter");
        } catch (Exception e) {
            log.error("Cannot updates selections for query (" + queryName + ")", e);
            return Response.serverError().entity(e.getMessage()).status(Status.INTERNAL_SERVER_ERROR).build();
        }

    }

    @DELETE
    @Consumes("application/x-www-form-urlencoded")
    @Path("/{queryname}/axis/{axis}/dimension/{dimension}/member/")
    public Response removeMembers(@PathParam("queryname") String queryName, @PathParam("axis") String axisName,
            @PathParam("dimension") String dimensionName, MultivaluedMap<String, String> formParams) {
        try {
            if (log.isDebugEnabled()) {
                log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/dimension/" + dimensionName
                        + "\tPUT");
            }
            if (formParams.containsKey("selections")) {
                LinkedList<String> sels = (LinkedList<String>) formParams.get("selections");
                String selectionJSON = (String) sels.getFirst();
                ObjectMapper mapper = new ObjectMapper(); // can reuse, share globally
                List<SelectionRestObject> selections = mapper.readValue(selectionJSON, mapper.getTypeFactory()
                        .constructCollectionType(ArrayList.class, SelectionRestObject.class));
                for (SelectionRestObject member : selections) {
                    removeMember("MEMBER", queryName, axisName, dimensionName, member.getUniquename());
                }
                return Response.ok().build();
            }
            throw new Exception("Form did not contain 'selections' parameter");
        } catch (Exception e) {
            log.error("Cannot updates selections for query (" + queryName + ")", e);
            return Response.serverError().entity(e.getMessage()).status(Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    /**
     * Move a member.
     * @return 
     */
    @POST
    @Path("/{queryname}/axis/{axis}/dimension/{dimension}/member/{member}")
    public Response includeMember(@FormParam("selection") @DefaultValue("MEMBER") String selectionType,
            @PathParam("queryname") String queryName, @PathParam("axis") String axisName,
            @PathParam("dimension") String dimensionName, @PathParam("member") String uniqueMemberName,
            @FormParam("position") @DefaultValue("-1") int position,
            @FormParam("memberposition") @DefaultValue("-1") int memberposition) {
        try {
            if (log.isDebugEnabled()) {
                log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/dimension/" + dimensionName
                        + "/member/" + uniqueMemberName + "\tPOST");
            }
            olapQueryService.moveDimension(queryName, axisName, dimensionName, position);

            boolean ret = olapQueryService.includeMember(queryName, dimensionName, uniqueMemberName, selectionType,
                    memberposition);
            if (ret) {
                return Response.ok().status(Status.CREATED).build();
            } else {
                throw new Exception("Couldn't include member " + dimensionName);

            }
        } catch (Exception e) {
            log.error("Cannot include member " + dimensionName + " for query (" + queryName + ")", e);
            return Response.serverError().entity(e.getMessage()).status(Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @DELETE
    @Path("/{queryname}/axis/{axis}/dimension/{dimension}/member/{member}")
    public Response removeMember(@FormParam("selection") @DefaultValue("MEMBER") String selectionType,
            @PathParam("queryname") String queryName, @PathParam("axis") String axisName,
            @PathParam("dimension") String dimensionName, @PathParam("member") String uniqueMemberName) {

        try {
            if (log.isDebugEnabled()) {
                log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/dimension/" + dimensionName
                        + "/member/" + uniqueMemberName + "\tDELETE");
            }
            boolean ret = olapQueryService.removeMember(queryName, dimensionName, uniqueMemberName, selectionType);
            if (ret) {
                SaikuDimensionSelection dimsels = olapQueryService.getAxisDimensionSelections(queryName, axisName,
                        dimensionName);
                if (dimsels != null && dimsels.getSelections().size() == 0) {
                    olapQueryService.moveDimension(queryName, "UNUSED", dimensionName, -1);
                }
                return Response.ok().build();
            } else {
                throw new Exception("Cannot remove member " + dimensionName + " for query (" + queryName + ")");
            }
        } catch (Exception e) {
            log.error("Cannot remove member " + dimensionName + " for query (" + queryName + ")", e);
            return Response.serverError().entity(e.getMessage()).status(Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @PUT
    @Path("/{queryname}/axis/{axis}/dimension/{dimension}/children")
    public Response includeChildren(@PathParam("queryname") String queryName, @PathParam("axis") String axisName,
            @PathParam("dimension") String dimensionName, @FormParam("member") String uniqueMemberName) {

        try {
            if (log.isDebugEnabled()) {
                log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/dimension/" + dimensionName
                        + "/children/" + uniqueMemberName + "\tPOST");
            }

            boolean ret = olapQueryService.includeChildren(queryName, dimensionName, uniqueMemberName);
            if (ret) {
                return Response.ok().status(Status.CREATED).build();
            } else {
                throw new Exception("Couldn't include children for " + uniqueMemberName);

            }
        } catch (Exception e) {
            log.error("Cannot include children for " + dimensionName + " for query (" + queryName + ")", e);
            return Response.serverError().entity(e.getMessage()).status(Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @DELETE
    @Path("/{queryname}/axis/{axis}/dimension/{dimension}/children")
    public Response removeChildren(@PathParam("queryname") String queryName, @PathParam("axis") String axisName,
            @PathParam("dimension") String dimensionName, @FormParam("member") String uniqueMemberName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/dimension/" + dimensionName
                    + "/children/" + uniqueMemberName + "\tDELETE");
        }
        try {
            boolean ret = olapQueryService.removeChildren(queryName, dimensionName, uniqueMemberName);
            if (ret) {
                return Response.ok().status(Status.GONE).build();
            } else {
                throw new Exception("Couldn't remove children for " + uniqueMemberName);

            }
        } catch (Exception e) {
            log.error("Cannot remove children for " + dimensionName + " for query (" + queryName + ")", e);
            return Response.serverError().entity(e.getMessage()).status(Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @POST
    @Path("/{queryname}/axis/{axis}/dimension/{dimension}/hierarchy/{hierarchy}/{level}")
    public Response includeLevel(@PathParam("queryname") String queryName, @PathParam("axis") String axisName,
            @PathParam("dimension") String dimensionName, @PathParam("hierarchy") String uniqueHierarchyName,
            @PathParam("level") String uniqueLevelName, @FormParam("position") @DefaultValue("-1") int position) {

        try {
            if (log.isDebugEnabled()) {
                log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/dimension/" + dimensionName
                        + "/hierarchy/" + uniqueHierarchyName + "/" + uniqueLevelName + "\tPOST");
            }
            olapQueryService.moveDimension(queryName, axisName, dimensionName, position);
            boolean ret = olapQueryService.includeLevel(queryName, dimensionName, uniqueHierarchyName,
                    uniqueLevelName);
            if (ret) {
                return Response.ok().status(Status.CREATED).build();
            } else {
                throw new Exception("Something went wrong including level: " + uniqueLevelName);
            }
        } catch (Exception e) {
            log.error("Cannot include level of hierarchy " + uniqueHierarchyName + " for query (" + queryName + ")",
                    e);
            return Response.serverError().entity(e.getMessage()).status(Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @DELETE
    @Path("/{queryname}/axis/{axis}/dimension/{dimension}/hierarchy/{hierarchy}/{level}")
    public Response removeLevel(@PathParam("queryname") String queryName, @PathParam("axis") String axisName,
            @PathParam("dimension") String dimensionName, @PathParam("hierarchy") String uniqueHierarchyName,
            @PathParam("level") String uniqueLevelName) {
        try {
            if (log.isDebugEnabled()) {
                log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/dimension/" + dimensionName
                        + "/hierarchy/" + uniqueHierarchyName + "/" + uniqueLevelName + "\tDELETE");
            }
            boolean ret = olapQueryService.removeLevel(queryName, dimensionName, uniqueHierarchyName,
                    uniqueLevelName);

            if (ret) {
                SaikuDimensionSelection dimsels = olapQueryService.getAxisDimensionSelections(queryName, axisName,
                        dimensionName);
                if (dimsels != null && dimsels.getSelections().size() == 0) {
                    olapQueryService.moveDimension(queryName, "UNUSED", dimensionName, -1);
                }
                return Response.ok().build();
            } else {
                log.error("Cannot remove level of hierarchy " + uniqueHierarchyName + " for query (" + queryName
                        + ")");
            }
            throw new Exception("Something went wrong removing level: " + uniqueLevelName + " from "
                    + uniqueHierarchyName + " for query (" + queryName + ")");
        } catch (Exception e) {
            log.error("Cannot include level of hierarchy " + uniqueHierarchyName + " for query (" + queryName + ")",
                    e);
            return Response.serverError().entity(e.getMessage()).status(Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @PUT
    @Produces({ "application/json" })
    @Path("/{queryname}/tag")
    public Status activateTag(@PathParam("queryname") String queryName, @FormParam("tag") String tagJSON) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/tags\tPUT");
        }
        try {
            ObjectMapper mapper = new ObjectMapper();
            mapper.setVisibilityChecker(mapper.getVisibilityChecker().withFieldVisibility(Visibility.ANY));
            SaikuTag tag = mapper.readValue(tagJSON, SaikuTag.class);

            olapQueryService.setTag(queryName, tag);
            return Status.OK;
        } catch (Exception e) {
            log.error("Cannot add tag " + tagJSON + " for query (" + queryName + ")", e);
        }
        return Status.INTERNAL_SERVER_ERROR;

    }

    @DELETE
    @Produces({ "application/json" })
    @Path("/{queryname}/tag")
    public Status deactivateTag(@PathParam("queryname") String queryName, @PathParam("tagname") String tagName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/tags\tPUT");
        }
        try {
            olapQueryService.disableTag(queryName);
            return Status.OK;
        } catch (Exception e) {
            log.error("Cannot remove tag " + tagName + " for query (" + queryName + ")", e);
        }
        return Status.INTERNAL_SERVER_ERROR;

    }

    @GET
    @Produces({ "application/json" })
    @Path("/{queryname}/filter")
    public Response getFilter(@PathParam("queryname") String queryName, @QueryParam("dimension") String dimension,
            @QueryParam("hierarchy") String hierarchy, @QueryParam("level") String level) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/filter\tGET");
        }
        try {
            SaikuFilter t = olapQueryService.getFilter(queryName, "new", dimension, hierarchy, level);
            return Response.ok(t).build();
        } catch (Exception e) {
            log.error("Cannot get filter for query (" + queryName + ")", e);
            String error = ExceptionUtils.getRootCauseMessage(e);
            return Response.serverError().entity(error).build();
        }
    }

    @PUT
    @Produces({ "application/json" })
    @Path("/{queryname}/filter")
    public Response activateFilter(@PathParam("queryname") String queryName,
            @FormParam("filter") String filterJSON) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/tags\tPUT");
        }
        try {
            ObjectMapper mapper = new ObjectMapper();
            mapper.setVisibilityChecker(mapper.getVisibilityChecker().withFieldVisibility(Visibility.ANY));
            SaikuFilter filter = mapper.readValue(filterJSON, SaikuFilter.class);
            SaikuQuery sq = olapQueryService.applyFilter(queryName, filter);
            return Response.ok(sq).build();
        } catch (Exception e) {
            log.error("Cannot activate filter for query (" + queryName + "), json:" + filterJSON, e);
            String error = ExceptionUtils.getRootCauseMessage(e);
            return Response.serverError().entity(error).build();
        }

    }

    @DELETE
    @Produces({ "application/json" })
    @Path("/{queryname}/filter")
    public Response deactivateFilter(@PathParam("queryname") String queryName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/tags\tPUT");
        }
        try {
            SaikuQuery sq = olapQueryService.removeFilter(queryName);
            return Response.ok(sq).build();
        } catch (Exception e) {
            log.error("Cannot remove filter for query (" + queryName + ")", e);
            String error = ExceptionUtils.getRootCauseMessage(e);
            return Response.serverError().entity(error).build();
        }
    }

    @POST
    @Produces({ "application/json" })
    @Path("/{queryname}/axis/{axis}/sort/{sortorder}/{sortliteral}")
    public void sortAxis(@PathParam("queryname") String queryName, @PathParam("axis") String axisName,
            @PathParam("sortorder") String sortOrder, @PathParam("sortliteral") String sortLiteral) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/sort/" + sortOrder + "/"
                    + sortLiteral + "\tPOST");
        }
        olapQueryService.sortAxis(queryName, axisName, sortLiteral, sortOrder);
    }

    @PUT
    @Produces({ "application/json" })
    @Path("/{queryname}/axis/{axis}/show_totals/{function}")
    public SaikuQuery showGrandTotals(@PathParam("queryname") String queryName, @PathParam("axis") String axisName,
            @PathParam("function") String functionName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/show_totals/" + functionName
                    + "\tPUT");
        }
        IQuery query = olapQueryService.showGrandTotals(queryName, axisName, functionName);
        return ObjectUtil.convert(query);
    }

    @DELETE
    @Produces({ "application/json" })
    @Path("/{queryname}/axis/{axis}/sort")
    public void clearSortAxis(@PathParam("queryname") String queryName, @PathParam("axis") String axisName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/sort/\tDELETE");
        }
        olapQueryService.clearSort(queryName, axisName);
    }

    @POST
    @Produces({ "application/json" })
    @Path("/{queryname}/axis/{axis}/limit/{limitfunction}")
    public void limitAxis(@PathParam("queryname") String queryName, @PathParam("axis") String axisName,
            @PathParam("limitfunction") String limitfunction, @FormParam("n") String n,
            @FormParam("sortliteral") String sortLiteral) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/limit/" + limitfunction + "("
                    + n + ", sort:" + sortLiteral + "\tPOST");
        }
        olapQueryService.limitAxis(queryName, axisName, limitfunction, n, sortLiteral);
    }

    @DELETE
    @Produces({ "application/json" })
    @Path("/{queryname}/axis/{axis}/limit")
    public void clearLimitAxis(@PathParam("queryname") String queryName, @PathParam("axis") String axisName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/limit/\tDELETE");
        }
        olapQueryService.clearLimit(queryName, axisName);
    }

    @POST
    @Produces({ "application/json" })
    @Path("/{queryname}/axis/{axis}/filter")
    public void filterAxis(@PathParam("queryname") String queryName, @PathParam("axis") String axisName,
            @FormParam("filterCondition") String filterCondition) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/filter/ (" + filterCondition
                    + " )\tPOST");
        }
        olapQueryService.filterAxis(queryName, axisName, filterCondition);
    }

    @DELETE
    @Produces({ "application/json" })
    @Path("/{queryname}/axis/{axis}/filter")
    public void clearFilter(@PathParam("queryname") String queryName, @PathParam("axis") String axisName) {
        if (log.isDebugEnabled()) {
            log.debug("TRACK\t" + "\t/query/" + queryName + "/axis/" + axisName + "/filter/\tDELETE");
        }
        olapQueryService.clearFilter(queryName, axisName);
    }

}