org.pentaho.metadata.query.model.util.QueryXmlHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.metadata.query.model.util.QueryXmlHelper.java

Source

/*
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
 * Foundation.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 * or from the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * Copyright (c) 2009 - 2016 Pentaho Corporation..  All rights reserved.
 */
package org.pentaho.metadata.query.model.util;

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.di.core.xml.XMLHandler;
import org.pentaho.metadata.messages.Messages;
import org.pentaho.metadata.model.Category;
import org.pentaho.metadata.model.Domain;
import org.pentaho.metadata.model.LogicalColumn;
import org.pentaho.metadata.model.LogicalModel;
import org.pentaho.metadata.model.concept.types.AggregationType;
import org.pentaho.metadata.model.concept.types.DataType;
import org.pentaho.metadata.query.model.CombinationType;
import org.pentaho.metadata.query.model.Constraint;
import org.pentaho.metadata.query.model.Order;
import org.pentaho.metadata.query.model.Parameter;
import org.pentaho.metadata.query.model.Query;
import org.pentaho.metadata.query.model.Selection;
import org.pentaho.metadata.repository.IMetadataDomainRepository;
import org.pentaho.metadata.util.XmiParser;
import org.pentaho.pms.core.exception.PentahoMetadataException;
import org.pentaho.reporting.libraries.base.util.CSVTokenizer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * This helper class serializes/deserializes the Query object to an MQL XML schema.
 * 
 * @author Will Gorman (wgorman@pentaho.com)
 * 
 */
public class QueryXmlHelper {

    private static final Log logger = LogFactory.getLog(QueryXmlHelper.class);

    //
    // TO XML
    //

    public String toXML(final Query query) {
        if (query == null) {
            logger.error(Messages.getErrorString("QueryXmlHelper.ERROR_0000_QUERY_MUST_NOT_BE_NULL")); //$NON-NLS-1$
            return null;
        }

        try {
            StringWriter stringWriter = new StringWriter();
            StreamResult result = new StreamResult();
            result.setWriter(stringWriter);
            TransformerFactory factory = TransformerFactory.newInstance();
            Document doc = toDocument(query);
            if (doc != null) {
                factory.newTransformer().transform(new DOMSource(doc), result);
                return stringWriter.getBuffer().toString();
            }
        } catch (Exception e) {
            logger.error(Messages.getErrorString("QueryXmlHelper.ERROR_0001_TO_XML_FAILED"), e); //$NON-NLS-1$
        }
        return null;
    }

    public Document toDocument(Query query) {

        if (query == null) {
            logger.error(Messages.getErrorString("QueryXmlHelper.ERROR_0000_QUERY_MUST_NOT_BE_NULL")); //$NON-NLS-1$
            return null;
        }

        Document doc;
        try {
            // create an XML document
            DocumentBuilderFactory dbf = XmiParser.createSecureDocBuilderFactory();
            DocumentBuilder db = dbf.newDocumentBuilder();
            doc = db.newDocument();
            Element mqlElement = doc.createElement("mql"); //$NON-NLS-1$
            doc.appendChild(mqlElement);

            if (addToDocument(mqlElement, doc, query)) {
                return doc;
            } else {
                return null;
            }
        } catch (Exception e) {
            logger.error(Messages.getErrorString("QueryXmlHelper.ERROR_0002_TO_DOCUMENT_FAILED"), e); //$NON-NLS-1$
        }
        return null;
    }

    protected boolean addToDocument(Element mqlElement, Document doc, Query query) {
        try {
            if (query.getDomain() == null) {
                logger.error(Messages.getErrorString("QueryXmlHelper.ERROR_0003_META_SCHEMA_NULL")); //$NON-NLS-1$
                return false;
            }

            if (query.getLogicalModel() == null) {
                logger.error(Messages.getErrorString("QueryXmlHelper.ERROR_0004_BUSINESS_MODEL_NULL")); //$NON-NLS-1$
                return false;
            }

            // insert the domain information
            String data = query.getDomain().getId();
            if (data != null) {
                addTextElement(doc, mqlElement, "domain_id", data); //$NON-NLS-1$
            } else {
                logger.error(Messages.getErrorString("QueryXmlHelper.ERROR_0005_DOMAIN_ID_NULL")); //$NON-NLS-1$
                return false;
            }

            // insert the model information
            data = query.getLogicalModel().getId();
            if (data != null) {
                addTextElement(doc, mqlElement, "model_id", data); //$NON-NLS-1$
            } else {
                logger.error(Messages.getErrorString("QueryXmlHelper.ERROR_0006_MODEL_ID_NULL")); //$NON-NLS-1$
                return false;
            }

            // Add additional options
            Element optionsElement = doc.createElement("options"); //$NON-NLS-1$
            mqlElement.appendChild(optionsElement);
            addOptionsToDocument(doc, optionsElement, query);

            if (query.getParameters() != null && query.getParameters().size() > 0) {
                Element parametersElement = doc.createElement("parameters"); //$NON-NLS-1$
                mqlElement.appendChild(parametersElement);
                addParametersToDocument(doc, parametersElement, query);
            }

            // insert the selections
            Element selectionsElement = doc.createElement("selections"); //$NON-NLS-1$
            for (Selection selection : query.getSelections()) {
                Element selectionElement = doc.createElement("selection"); //$NON-NLS-1$
                addSelectionToDocument(doc, selection, selectionElement);
                selectionsElement.appendChild(selectionElement);
            }
            mqlElement.appendChild(selectionsElement);

            // insert the contraints
            Element constraintsElement = doc.createElement("constraints"); //$NON-NLS-1$
            for (Constraint condition : query.getConstraints()) {
                Element constraintElement = doc.createElement("constraint"); //$NON-NLS-1$
                addConstraintToDocument(doc, condition, constraintElement);
                constraintsElement.appendChild(constraintElement);
            }
            mqlElement.appendChild(constraintsElement);

            // insert the orders
            Element ordersElement = doc.createElement("orders"); //$NON-NLS-1$
            for (Order order : query.getOrders()) {
                Element orderElement = doc.createElement("order"); //$NON-NLS-1$
                addOrderByToDocument(doc, order, orderElement);
                ordersElement.appendChild(orderElement);

            }
            mqlElement.appendChild(ordersElement);

        } catch (Exception e) {
            logger.error(Messages.getErrorString("QueryXmlHelper.ERROR_0007_ADD_TO_DOCUMENT_FAILED"), e); //$NON-NLS-1$
        }
        return true;

    }

    protected void addOptionsToDocument(Document doc, Element optionsElement, Query query) {
        addTextElement(doc, optionsElement, "disable_distinct", Boolean.toString(query.getDisableDistinct())); //$NON-NLS-1$
        addTextElement(doc, optionsElement, "limit", String.valueOf(query.getLimit())); //$NON-NLS-1$
    }

    protected void addParametersToDocument(Document doc, Element parametersElement, Query query) {
        for (Parameter param : query.getParameters()) {
            Element paramElement = doc.createElement("parameter"); //$NON-NLS-1$
            paramElement.setAttribute("name", param.getName()); //$NON-NLS-1$
            paramElement.setAttribute("type", param.getType().toString()); //$NON-NLS-1$
            paramElement.setAttribute("defaultValue", //$NON-NLS-1$
                    param.getDefaultValue() == null ? "" : param.getDefaultValue().toString()); //$NON-NLS-1$
            parametersElement.appendChild(paramElement);
        }
    }

    protected void addParameterFromXmlNode(Query query, Element paramElement) {
        String name = paramElement.getAttribute("name"); //$NON-NLS-1$
        String type = paramElement.getAttribute("type"); //$NON-NLS-1$
        String defaultValue = paramElement.getAttribute("defaultValue"); //$NON-NLS-1$

        Object defaultVal = null;
        if (name != null && type != null && StringUtils.isNotEmpty(defaultValue)) {
            DataType dataType = DataType.valueOf(type.toUpperCase());
            defaultVal = parseDefaultValue(defaultValue, dataType);
        }
        Parameter param = new Parameter(name, DataType.valueOf(type.toUpperCase()), defaultVal);
        query.getParameters().add(param);
    }

    protected Object parseDefaultValue(String defaultValue, DataType dataType) {
        if (defaultValue == null) {
            return null;
        }

        CSVTokenizer csvt = new CSVTokenizer(defaultValue, "|", "\"", true);
        switch (dataType) {
        case BOOLEAN:
            return parseBooleanDefaultParam(defaultValue, csvt);
        case NUMERIC:
            return parseDoubleDefaultParam(defaultValue, csvt);
        default:
            return parseStringDefaultParam(defaultValue, csvt);
        }
    }

    private Object parseBooleanDefaultParam(String defaultValue, CSVTokenizer csvt) {
        if (csvt.countTokens() == 1) {
            return Boolean.parseBoolean(csvt.nextToken());
        } else {
            ArrayList<Boolean> vals = new ArrayList<Boolean>();
            while (csvt.hasMoreTokens()) {
                String token = csvt.nextToken();
                vals.add(Boolean.parseBoolean(token));
            }
            return vals.toArray(new Boolean[0]);
        }
    }

    private Object parseDoubleDefaultParam(String defaultValue, CSVTokenizer csvt) {
        if (csvt.countTokens() == 1) {
            return Double.parseDouble(csvt.nextToken());
        } else {
            ArrayList<Double> vals = new ArrayList<Double>();
            while (csvt.hasMoreTokens()) {
                String token = csvt.nextToken();
                vals.add(Double.parseDouble(token));
            }
            return vals.toArray(new Double[0]);
        }
    }

    private Object parseStringDefaultParam(String defaultValue, CSVTokenizer csvt) {
        if (csvt.countTokens() == 1) {
            return csvt.nextToken();
        } else {
            ArrayList<String> vals = new ArrayList<String>();
            while (csvt.hasMoreTokens()) {
                String token = csvt.nextToken();
                vals.add(token);
            }
            return vals.toArray(new String[0]);
        }
    }

    protected void addSelectionToDocument(Document doc, Selection selection, Element selectionElement) {
        addTextElement(doc, selectionElement, "view", selection.getCategory().getId()); //$NON-NLS-1$
        addTextElement(doc, selectionElement, "column", selection.getLogicalColumn().getId()); //$NON-NLS-1$
        if (selection.getAggregationType() != null) {
            addTextElement(doc, selectionElement, "aggregation", selection.getAggregationType().toString()); //$NON-NLS-1$
        }
    }

    protected void addConstraintToDocument(Document doc, Constraint constraint, Element constraintElement) {
        addTextElement(doc, constraintElement, "operator", //$NON-NLS-1$
                constraint.getCombinationType() == null ? "" : constraint.getCombinationType().toString()); //$NON-NLS-1$
        addTextElement(doc, constraintElement, "condition", constraint.getFormula()); //$NON-NLS-1$
    }

    protected void addOrderByToDocument(Document doc, Order order, Element orderElement) {

        // test that this is lower case
        addTextElement(doc, orderElement, "direction", order.getType().toString()); //$NON-NLS-1$
        addTextElement(doc, orderElement, "view_id", order.getSelection().getCategory().getId()); //$NON-NLS-1$
        addTextElement(doc, orderElement, "column_id", order.getSelection().getLogicalColumn().getId()); //$NON-NLS-1$
        if (order.getSelection().getAggregationType() != null) {
            addTextElement(doc, orderElement, "aggregation", order.getSelection().getAggregationType().toString()); //$NON-NLS-1$
        }

    }

    protected void addTextElement(Document doc, Element element, String elementName, String text) {
        Element childElement = doc.createElement(elementName);
        childElement.appendChild(doc.createTextNode(text));
        element.appendChild(childElement);
    }

    //
    // FROM XML
    //

    public Query fromXML(IMetadataDomainRepository repo, String XML) throws PentahoMetadataException {
        if (XML == null) {
            throw new PentahoMetadataException(Messages.getErrorString("QueryXmlHelper.ERROR_0008_XML_NULL")); //$NON-NLS-1$
        }
        Document doc;

        // Check and open XML document
        try {
            DocumentBuilderFactory dbf = XmiParser.createSecureDocBuilderFactory();
            DocumentBuilder db = dbf.newDocumentBuilder();
            doc = db.parse(new InputSource(new java.io.StringReader(XML)));
        } catch (ParserConfigurationException pcx) {
            throw new PentahoMetadataException(pcx);
        } catch (SAXException sex) {
            throw new PentahoMetadataException(sex);
        } catch (IOException iex) {
            throw new PentahoMetadataException(iex);
        }
        return fromXML(repo, doc);
    }

    public Query fromXML(IMetadataDomainRepository repo, Document doc) throws PentahoMetadataException {

        // get the domain id
        String domainId = getElementText(doc, "domain_id"); //$NON-NLS-1$
        Domain domain = repo.getDomain(domainId);
        if (domain == null) {
            // need to throw an error
            throw new PentahoMetadataException(
                    Messages.getErrorString("QueryXmlHelper.ERROR_0009_DOMAIN_INSTANCE_NULL", domainId)); //$NON-NLS-1$
        }

        return fromXML(doc, domain);
    }

    protected Query fromXML(Document doc, Domain domain) throws PentahoMetadataException {

        // get the model id
        String modelId = getElementText(doc, "model_id"); //$NON-NLS-1$
        LogicalModel model = domain.findLogicalModel(modelId); // This is the business model that was selected.

        if (model == null) {
            throw new PentahoMetadataException(
                    Messages.getErrorString("QueryXmlHelper.ERROR_0010_MODEL_NOT_FOUND", modelId)); //$NON-NLS-1$
        }

        Query query = new Query(domain, model);

        // get the options node if it exists...
        NodeList nList = doc.getElementsByTagName("options"); //$NON-NLS-1$
        if (nList != null) {
            Element optionElement;
            for (int i = 0; i < nList.getLength(); i++) {
                optionElement = (Element) nList.item(i);
                setOptionsFromXmlNode(query, optionElement);
            }
        }

        NodeList paramNodes = doc.getElementsByTagName("parameter"); //$NON-NLS-1$
        Element paramElement;
        for (int idx = 0; idx < paramNodes.getLength(); idx++) {
            paramElement = (Element) paramNodes.item(idx);
            addParameterFromXmlNode(query, paramElement);
        }

        // process the selections
        NodeList nodes = doc.getElementsByTagName("selection"); //$NON-NLS-1$
        Element selectionElement;
        for (int idx = 0; idx < nodes.getLength(); idx++) {
            selectionElement = (Element) nodes.item(idx);
            addSelectionFromXmlNode(query, selectionElement);
        }

        // process the constraints
        nodes = doc.getElementsByTagName("constraint"); //$NON-NLS-1$
        Element constraintElement;
        for (int idx = 0; idx < nodes.getLength(); idx++) {
            constraintElement = (Element) nodes.item(idx);
            addConstraintFromXmlNode(query, constraintElement);
        }

        // process the constraints
        nodes = doc.getElementsByTagName("order"); //$NON-NLS-1$
        Element orderElement;
        for (int idx = 0; idx < nodes.getLength(); idx++) {
            orderElement = (Element) nodes.item(idx);
            addOrderByFromXmlNode(query, orderElement);
        }

        return query;
    }

    protected void setOptionsFromXmlNode(Query query, Element optionElement) throws PentahoMetadataException {
        // Keep default behavior...
        query.setDisableDistinct(false);
        query.setLimit(-1);
        if (optionElement != null) {
            String disableStr = getElementText(optionElement, "disable_distinct"); //$NON-NLS-1$
            if (disableStr != null) {
                query.setDisableDistinct(disableStr.equalsIgnoreCase("true")); //$NON-NLS-1$
            }
            String limitStr = getElementText(optionElement, "limit");
            if (limitStr != null) {
                try {
                    query.setLimit(Integer.parseInt(limitStr));
                } catch (NumberFormatException e) {
                    throw new PentahoMetadataException(
                            Messages.getErrorString("QueryXmlHelper.ERROR_0017_CANNOT_PARSE_LIMIT")); //$NON-NLS-1$
                }
            }
        }

    }

    protected void addSelectionFromXmlNode(Query query, Element selectionElement) {

        NodeList viewnodes = selectionElement.getElementsByTagName("view"); //$NON-NLS-1$
        NodeList nodes = selectionElement.getElementsByTagName("column"); //$NON-NLS-1$
        if (nodes.getLength() == 0) {
            // should throw exception here
            return;
        }
        String columnId = XMLHandler.getNodeValue(nodes.item(0));
        String viewId = null;
        Category category = null;
        if (viewnodes.getLength() != 0) {
            // this is due to legacy reasons, the query doesn't really need the category.
            viewId = XMLHandler.getNodeValue(viewnodes.item(0));
            category = query.getLogicalModel().findCategory(viewId);
        }
        LogicalColumn column = null;
        if (category != null) {
            column = category.findLogicalColumn(columnId);
        } else {
            column = query.getLogicalModel().findLogicalColumnInCategories(columnId);
        }
        if (column != null) {
            AggregationType aggsetting = null;
            NodeList aggnodes = selectionElement.getElementsByTagName("aggregation"); //$NON-NLS-1$
            if (aggnodes.getLength() > 0) {
                String aggvalue = XMLHandler.getNodeValue(aggnodes.item(0));
                AggregationType setting = AggregationType.valueOf(aggvalue.toUpperCase());
                if (setting == null) {
                    Messages.getErrorString("QueryXmlHelper.ERROR_0011_AGG_NOT_RECOGNIZED", columnId, aggvalue); //$NON-NLS-1$
                } else {
                    // verify that the setting is one of the options for this business column
                    if ((column.getAggregationType() == setting) || column.getAggregationList() != null
                            && column.getAggregationList().contains(setting)) {
                        aggsetting = setting;
                    } else {
                        Messages.getErrorString("QueryXmlHelper.ERROR_0012_INVALID_AGG_FOR_BUSINESS_COL", columnId, //$NON-NLS-1$
                                aggvalue);
                    }
                }
            }

            query.getSelections().add(new Selection(category, column, aggsetting));
        } else {
            // print a warning message
            Messages.getErrorString("QueryXmlHelper.ERROR_0013_BUSINESS_COL_NOT_FOUND", viewId, columnId); //$NON-NLS-1$
        }
    }

    protected void addConstraintFromXmlNode(Query query, Element constraintElement)
            throws PentahoMetadataException {

        NodeList nodes = constraintElement.getElementsByTagName("operator"); //$NON-NLS-1$
        String operator = null;
        if (nodes.getLength() > 0) {
            operator = XMLHandler.getNodeValue(nodes.item(0));
        }

        if ((operator == null) || (operator.trim().equals(""))) { //$NON-NLS-1$
            operator = "AND"; //$NON-NLS-1$
        }

        nodes = constraintElement.getElementsByTagName("condition"); //$NON-NLS-1$
        String cond = null;
        if (nodes.getLength() > 0) {
            cond = XMLHandler.getNodeValue(nodes.item(0));
        }

        nodes = constraintElement.getElementsByTagName("view_id"); //$NON-NLS-1$
        String view_id = null;
        if (nodes.getLength() > 0) {
            view_id = XMLHandler.getNodeValue(nodes.item(0));
        }

        nodes = constraintElement.getElementsByTagName("column_id"); //$NON-NLS-1$
        String column_id = null;
        if (nodes.getLength() > 0) {
            column_id = XMLHandler.getNodeValue(nodes.item(0));
        }

        if (cond == null) {
            throw new PentahoMetadataException(Messages.getErrorString("QueryXmlHelper.ERROR_0014_NULL_CONDITION")); //$NON-NLS-1$
        }

        if (view_id == null || column_id == null) {
            // new function support
            query.getConstraints()
                    .add(new Constraint(CombinationType.getCombinationType(operator.toUpperCase()), cond));
        } else {
            // backwards compatibility
            query.getConstraints().add(new Constraint(CombinationType.getCombinationType(operator.toUpperCase()),
                    "[" + view_id + "." + column_id + "] " + cond)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        }

    }

    protected void addOrderByFromXmlNode(Query query, Element orderElement) throws PentahoMetadataException {
        String view_id = null;
        String column_id = null;
        String aggregation = null;
        Order.Type orderType = Order.Type.ASC;
        NodeList nodes = orderElement.getElementsByTagName("direction"); //$NON-NLS-1$
        if (nodes.getLength() > 0) {
            String orderTypeStr = XMLHandler.getNodeValue(nodes.item(0));
            if (orderTypeStr != null) {
                orderType = Order.Type.valueOf(orderTypeStr.toUpperCase());
            }
        }
        nodes = orderElement.getElementsByTagName("view_id"); //$NON-NLS-1$
        if (nodes.getLength() > 0) {
            view_id = XMLHandler.getNodeValue(nodes.item(0));
        }
        nodes = orderElement.getElementsByTagName("column_id"); //$NON-NLS-1$
        if (nodes.getLength() > 0) {
            column_id = XMLHandler.getNodeValue(nodes.item(0));
        }

        nodes = orderElement.getElementsByTagName("aggregation"); //$NON-NLS-1$
        if (nodes.getLength() > 0) {
            aggregation = XMLHandler.getNodeValue(nodes.item(0));
        }

        if ((view_id != null) && (column_id != null)) {
            addOrderBy(query, view_id, column_id, aggregation, orderType);
        }
    }

    protected void addOrderBy(Query query, String categoryId, String columnId, String aggregation,
            Order.Type orderType) throws PentahoMetadataException {
        Category category = query.getLogicalModel().findCategory(categoryId);
        if (category == null) {
            throw new PentahoMetadataException(
                    Messages.getErrorString("QueryXmlHelper.ERROR_0015_BUSINESS_CATEGORY_NOT_FOUND", categoryId)); //$NON-NLS-1$ 
        }
        addOrderBy(query, category, columnId, aggregation, orderType);
    }

    protected void addOrderBy(Query query, Category category, String columnId, String aggregation,
            Order.Type orderType) throws PentahoMetadataException {

        if (category == null) {
            throw new PentahoMetadataException(
                    Messages.getErrorString("QueryXmlHelper.ERROR_0016_BUSINESS_CATEGORY_NULL")); //$NON-NLS-1$ 
        }

        LogicalColumn column = category.findLogicalColumn(columnId);
        if (column == null) {
            throw new PentahoMetadataException(Messages.getErrorString(
                    "QueryXmlHelper.ERROR_0013_BUSINESS_COL_NOT_FOUND", category.getId(), columnId)); //$NON-NLS-1$ 
        }

        // this code verifies the aggregation setting provided is a
        // valid option
        AggregationType aggsetting = null;
        if (aggregation != null) {
            AggregationType setting = AggregationType.valueOf(aggregation.toUpperCase());
            if ((column.getAggregationType() == setting)
                    || column.getAggregationList() != null && column.getAggregationList().contains(setting)) {
                aggsetting = setting;
            }
        }

        query.getOrders().add(new Order(new Selection(category, column, aggsetting), orderType));
    }

    protected String getElementText(Element ele, String name) {
        try {
            return ele.getElementsByTagName(name).item(0).getFirstChild().getNodeValue();
        } catch (Exception e) {
            return null;
        }
    }

    protected String getElementText(Document doc, String name) {
        try {
            return doc.getElementsByTagName(name).item(0).getFirstChild().getNodeValue();
        } catch (Exception e) {
            return null;
        }
    }

}