com.klistret.cmdb.utility.hibernate.XPathAggregation.java Source code

Java tutorial

Introduction

Here is the source code for com.klistret.cmdb.utility.hibernate.XPathAggregation.java

Source

/**
 ** This file is part of Klistret. Klistret is free software: you can
 ** redistribute it and/or modify it under the terms of the GNU General
 ** Public License as published by the Free Software Foundation, either
 ** version 3 of the License, or (at your option) any later version.
    
 ** Klistret 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
 ** General Public License for more details. You should have received a
 ** copy of the GNU General Public License along with Klistret. If not,
 ** see <http://www.gnu.org/licenses/>
 */
package com.klistret.cmdb.utility.hibernate;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.criterion.CriteriaQuery;
import org.hibernate.criterion.SimpleProjection;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.function.SQLFunction;
import org.hibernate.type.StringType;
import org.hibernate.type.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.klistret.cmdb.exception.ApplicationException;
import com.klistret.cmdb.utility.saxon.BaseExpression;
import com.klistret.cmdb.utility.saxon.Expr;
import com.klistret.cmdb.utility.saxon.IrresoluteExpr;
import com.klistret.cmdb.utility.saxon.Step;
import com.klistret.cmdb.utility.saxon.StepExpr;

/**
 * 
 * @author Matthew Young
 * 
 */
@SuppressWarnings("serial")
public class XPathAggregation extends SimpleProjection {
    private static final Logger logger = LoggerFactory.getLogger(XPathAggregation.class);

    /**
     * 
     */
    private final String propertyName;

    /**
     * 
     */
    private final String functionName;

    /**
     * 
     */
    private final Step step;

    /**
     * Variable reference that associates the database column to the XPath.
     */
    private static final String variableReference = "this";

    /**
     * VARCHAR size limitation
     */
    private static int varcharLimit = 255;

    /**
     * Single Quotes pattern
     */
    private static final Pattern singleQuotes = Pattern.compile("'((?:[^']+|'')*)'");

    /**
     * Constructor
     * 
     * @param functionName
     * @param propertyName
     * @param step
     */
    public XPathAggregation(String functionName, String propertyName, Step step) {
        this.functionName = functionName;
        this.propertyName = propertyName;
        this.step = step;
    }

    public String getFunctionName() {
        return functionName;
    }

    public String getPropertyName() {
        return propertyName;
    }

    public Step getStep() {
        return step;
    }

    public String toSqlString(Criteria criteria, int position, CriteriaQuery criteriaQuery)
            throws HibernateException {
        final String functionFragment = getFunction(criteriaQuery).render(StringType.INSTANCE,
                buildFunctionParameterList(criteria, criteriaQuery), criteriaQuery.getFactory());
        return functionFragment + " as y" + position + '_';
    }

    /**
     * Copied from AggregateProjection
     */
    public Type[] getTypes(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
        return new Type[] {
                getFunction(criteriaQuery).getReturnType(StringType.INSTANCE, criteriaQuery.getFactory()) };
    }

    /**
     * Copied from AggregateProjection
     */
    protected SQLFunction getFunction(CriteriaQuery criteriaQuery) {
        return getFunction(getFunctionName(), criteriaQuery);
    }

    /**
     * Copied from AggregateProjection
     */
    protected SQLFunction getFunction(String functionName, CriteriaQuery criteriaQuery) {
        SQLFunction function = criteriaQuery.getFactory().getSqlFunctionRegistry().findSQLFunction(functionName);
        if (function == null) {
            throw new HibernateException("Unable to locate mapping for function named [" + functionName + "]");
        }
        return function;
    }

    private List<String> buildFunctionParameterList(Criteria criteria, CriteriaQuery criteriaQuery) {
        Dialect dialect = criteriaQuery.getFactory().getDialect();
        String[] columns = criteriaQuery.getColumnsUsingProjection(criteria, propertyName);

        if (columns.length != 1) {
            logger.error("XMLQUERY may only be used with single-column properties [property: {}]", propertyName);
            throw new HibernateException("XMLQUERY may only be used with single-column properties");
        }

        BaseExpression be = step.getRelativePath().getBaseExpression();

        /**
         * Property type is generalized to wild-card "*" leaving only the
         * predicate
         */
        String axis = String.format("%s:%s", step.getQName().getPrefix(), step.getQName().getLocalPart());

        /**
         * Passing clause
         */
        String passingClause = String.format("PASSING %s AS \"%s\"", columns[0], variableReference);

        /**
         * Setup XPath first with the variable reference (acts a root), the axis
         * is replaced and then XPath is built up again either from generated
         * string or the raw XPath for each step (depending on if it is a
         * readable step or irresolute).
         */
        String xpath = String.format("$%s", variableReference);

        for (Expr expr : step.getRelativePath().getSteps()) {
            if (expr instanceof Step) {
                if (((Step) expr).getDepth() >= step.getDepth()) {
                    if (expr instanceof StepExpr) {
                        xpath = String.format("%s/%s", xpath, expr.getXPath(true));
                    }
                    if (expr instanceof IrresoluteExpr) {
                        xpath = String.format("%s/%s", xpath,
                                step.getRelativePath().getRawXPath(((Step) expr).getDepth()));
                    }
                }
            }
        }

        xpath = xpath.replaceFirst(axis, "*");
        logger.debug("XPath [{}] prior prefixing default function declaration and namespace declarations", xpath);

        /**
         * Concatenate namespace declarations
         */
        for (String namespace : be.getNamespaces())
            xpath = namespace.concat(xpath);

        /**
         * Concatenate default element namespace declaration
         */
        if (be.getDefaultElementNamespace() != null)
            xpath = be.getDefaultElementNamespace().concat(xpath);

        /**
         * Dialect controlls
         */
        if (dialect instanceof DB2Dialect) {
            /**
             * DB2 only allows SQL with double quotes (or at least that is the
             * extend of my knowledge)
             */
            Matcher sq = singleQuotes.matcher(xpath);
            if (sq.find())
                throw new ApplicationException(String
                        .format("XPath [%s] contains surrounding single quotes which DB2 does not allow", xpath),
                        new UnsupportedOperationException());
        }

        /**
         * Return the XMLQuery predicate
         */
        String[] results = {
                String.format("XMLCAST(XMLQUERY(\'%s\' %s) AS VARCHAR(%d))", xpath, passingClause, varcharLimit) };
        return Arrays.asList(results);
    }
}