gov.nih.nci.cagrid.sdk4query.processor.PublicDataCQL2ParameterizedHQL.java Source code

Java tutorial

Introduction

Here is the source code for gov.nih.nci.cagrid.sdk4query.processor.PublicDataCQL2ParameterizedHQL.java

Source

/*L
 *  Copyright SAIC
 *  Copyright SAIC-Frederick
 *
 *  Distributed under the OSI-approved BSD 3-Clause License.
 *  See http://ncip.github.com/cananolab/LICENSE.txt for details.
 */

package gov.nih.nci.cagrid.sdk4query.processor;

import gov.nih.nci.cagrid.cqlquery.Association;
import gov.nih.nci.cagrid.cqlquery.Attribute;
import gov.nih.nci.cagrid.cqlquery.CQLQuery;
import gov.nih.nci.cagrid.cqlquery.Group;
import gov.nih.nci.cagrid.cqlquery.LogicalOperator;
import gov.nih.nci.cagrid.cqlquery.Object;
import gov.nih.nci.cagrid.cqlquery.Predicate;
import gov.nih.nci.cagrid.cqlquery.QueryModifier;
import gov.nih.nci.cagrid.data.QueryProcessingException;
import gov.nih.nci.cagrid.sdkquery4.beans.domaininfo.DomainTypesInformation;
import gov.nih.nci.cagrid.sdkquery4.processor.CQL2ParameterizedHQL;
import gov.nih.nci.cagrid.sdkquery4.processor.DomainTypesInformationUtil;
import gov.nih.nci.cagrid.sdkquery4.processor.ParameterizedHqlQuery;
import gov.nih.nci.cagrid.sdkquery4.processor.RoleNameResolver;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * CQL2ParameterizedHQL Converter utility to turn CQL into HQL using positional
 * parameters compatible with Hibernate 3.2.0ga for use with caCORE SDK 4
 *
 * @author David Ervin
 *
 * @created Mar 2, 2007 10:26:47 AM
 * @version $Id: CQL2ParameterizedHQL.java,v 1.13 2009/01/05 14:44:11 dervin Exp $
 */

/**
 * Modified by Sue Pan to update HQLs that involved query modifies to filter out
 * non- public data.
 */
public class PublicDataCQL2ParameterizedHQL {
    public static final String TARGET_ALIAS = "__TargetAlias__";

    private static Log LOG = LogFactory.getLog(CQL2ParameterizedHQL.class);

    // maps a CQL predicate to its HQL string representation
    private Map<Predicate, String> predicateValues = null;

    private DomainTypesInformationUtil typesInfoUtil = null;
    private RoleNameResolver roleNameResolver = null;
    private boolean caseInsensitive;

    public PublicDataCQL2ParameterizedHQL(DomainTypesInformation typesInfo, RoleNameResolver roleNameResolver,
            boolean caseInsensitive) {
        this.typesInfoUtil = new DomainTypesInformationUtil(typesInfo);
        this.roleNameResolver = roleNameResolver;
        this.caseInsensitive = caseInsensitive;
        initPredicateValues();
    }

    private void initPredicateValues() {
        predicateValues = new HashMap<Predicate, String>();
        predicateValues.put(Predicate.EQUAL_TO, "=");
        predicateValues.put(Predicate.GREATER_THAN, ">");
        predicateValues.put(Predicate.GREATER_THAN_EQUAL_TO, ">=");
        predicateValues.put(Predicate.LESS_THAN, "<");
        predicateValues.put(Predicate.LESS_THAN_EQUAL_TO, "<=");
        predicateValues.put(Predicate.LIKE, "LIKE");
        predicateValues.put(Predicate.NOT_EQUAL_TO, "!=");
        predicateValues.put(Predicate.IS_NOT_NULL, "is not null");
        predicateValues.put(Predicate.IS_NULL, "is null");
    }

    /**
     * Converts CQL to parameterized HQL suitable for use with Hibernate
     * v3.2.0ga and the caCORE SDK version 4.0
     *
     * @param query
     *            The query to convert
     * @return A parameterized HQL Query representing the CQL query
     * @throws QueryProcessingException
     */
    public ParameterizedHqlQuery convertToHql(CQLQuery query) throws QueryProcessingException {
        // create a string builder to build up the HQL
        StringBuilder rawHql = new StringBuilder();

        // create the list in which parameters will be placed
        List<java.lang.Object> parameters = new LinkedList<java.lang.Object>();

        // determine if the target has subclasses
        List<String> subclasses = typesInfoUtil.getSubclasses(query.getTarget().getName());
        boolean hasSubclasses = !(subclasses == null || subclasses.size() == 0);
        LOG.debug(query.getTarget().getName()
                + (hasSubclasses ? " has " + subclasses.size() + " subclasses" : " has no subclasse"));

        // begin processing at the target level
        //
        // processTarget(query.getTarget(), rawHql, parameters, hasSubclasses);
        // modified by Sue Pan to turn off checking of subclasses. There is a
        // reported
        // bug GForge Bug #18649 related to generating the correct HQL for
        // subclasses mapped by table per
        // hierarchy inheritance.
        processTarget(query.getTarget(), rawHql, parameters, false);

        // apply query modifiers
        if (query.getQueryModifier() != null) {
            handleQueryModifier(query.getQueryModifier(), rawHql);
        } else {
            // select only unique objects
            rawHql.insert(0, "Select distinct (" + TARGET_ALIAS + ") ");
        }

        // build the final query object
        ParameterizedHqlQuery hqlQuery = new ParameterizedHqlQuery(rawHql.toString(), parameters);
        return hqlQuery;
    }

    /**
     * Applies query modifiers to the HQL query
     *
     * @param mods
     *            The modifiers to apply
     * @param hql
     *            The HQL to apply the modifications to
     *
     *            Modified by Sue Pan to always return id or name (the attribute
     *            stored in CSM as the protected data) as the first item, to
     *            filter out non-public data
     *
     */
    private void handleQueryModifier(QueryModifier mods, StringBuilder hql) {
        // data in caNanoLab were secured based on ids
        String secureAttribute = "id";
        StringBuilder prepend = new StringBuilder();
        if (mods.isCountOnly()) {
            prepend.append("select ");
            if (mods.getDistinctAttribute() != null) {
                prepend.append("distinct ").append(secureAttribute).append(", ")
                        .append(mods.getDistinctAttribute());
            } else {
                prepend.append("distinct ").append(secureAttribute);
            }
        } else {
            prepend.append("select ");
            if (mods.getDistinctAttribute() != null) {
                prepend.append("distinct ").append(secureAttribute).append(", ")
                        .append(mods.getDistinctAttribute());
            } else {
                prepend.append(secureAttribute).append(", ");
                for (int i = 0; i < mods.getAttributeNames().length; i++) {
                    prepend.append(mods.getAttributeNames(i));
                    if (i + 1 < mods.getAttributeNames().length) {
                        prepend.append(", ");
                    }
                }
            }
        }

        prepend.append(' ');

        hql.insert(0, prepend.toString());
    }

    /**
     * Processes the target object of a CQL query
     *
     * @param target
     *            The target of a CQL query
     * @param hql
     *            The hql string builder to append to
     * @param parameters
     *            The list of positional parameter values
     * @param avoidSubclasses
     *            A flag to indicate the target has subclasses, which we should
     *            not return
     * @throws QueryProcessingException
     */
    private void processTarget(Object target, StringBuilder hql, List<java.lang.Object> parameters,
            boolean avoidSubclasses) throws QueryProcessingException {
        LOG.debug("Processing target " + target.getName());

        // the stack of associations processed at the current depth of the query
        Stack<Association> associationStack = new Stack<Association>();

        // start the query
        hql.append("From ").append(target.getName()).append(" as ").append(TARGET_ALIAS).append(' ');

        if (target.getAssociation() != null) {
            hql.append("where ");
            processAssociation(target.getAssociation(), hql, parameters, associationStack, target, TARGET_ALIAS);
        }
        if (target.getAttribute() != null) {
            hql.append("where ");
            processAttribute(target.getAttribute(), hql, parameters, target, TARGET_ALIAS);
        }
        if (target.getGroup() != null) {
            hql.append("where ");
            processGroup(target.getGroup(), hql, parameters, associationStack, target, TARGET_ALIAS);
        }

        if (avoidSubclasses) {
            boolean mustAddWhereClause = target.getAssociation() == null && target.getAttribute() == null
                    && target.getGroup() == null;
            if (mustAddWhereClause) {
                hql.append(" where ");
            } else {
                hql.append(" and ");
            }
            hql.append(TARGET_ALIAS).append(".class = ?");
            // 0 is the targeted class, 1 is the first subclass, 2 is the
            // next...
            parameters.add(Integer.valueOf(0));
        }
    }

    /**
     * Processes a CQL query attribute into HQL
     *
     * @param attribute
     *       The CQL attribute
     * @param hql
     *       The HQL statement fragment
      * @param parameters
      *      The positional parameters list
     * @param associationTrace
     *       The trace of associations
     * @param objectClassName
     *       The class name of the object to which this association belongs
     * @throws QueryProcessingException
     */
    private void processAttribute(Attribute attribute, StringBuilder hql, List<java.lang.Object> parameters,
            Object queryObject, String queryObjectAlias) throws QueryProcessingException {
        LOG.debug("Processing attribute " + queryObject.getName() + "." + attribute.getName());

        // determine the Java type of the field being processed
        String attributeFieldType = typesInfoUtil.getAttributeJavaType(queryObject.getName(), attribute.getName());
        if (attributeFieldType == null) {
            throw new QueryProcessingException("Field type of " + queryObject.getName() + "." + attribute.getName()
                    + " could not be determined");
        }
        LOG.debug("Attribute found to be of type " + attributeFieldType);

        // determine some flags needed for proper query construction
        boolean isBoolAttribute = attributeFieldType.equals(Boolean.class.getName());
        boolean unaryPredicate = attribute.getPredicate().equals(Predicate.IS_NOT_NULL)
                || attribute.getPredicate().equals(Predicate.IS_NULL);

        // construct the query fragment
        // TODO: does Hibernate 3.2.0ga not allow lower() for booleans?
        // If it allows it, can get rid of the isBoolAttribute checking
        if (caseInsensitive && !isBoolAttribute) {
            hql.append("lower(");
        }

        // append the path to the attribute itself
        hql.append(queryObjectAlias).append('.').append(attribute.getName());

        // close up case insensitivity
        if (caseInsensitive && !isBoolAttribute) {
            hql.append(')');
        }

        // append the predicate
        hql.append(' ');
        String predicateAsString = predicateValues.get(attribute.getPredicate());
        if (!unaryPredicate) {
            hql.append(predicateAsString).append(' ');

            // add a placeholder parameter to the HQL query
            hql.append('?');

            // convert the attribtue value to the specific data type of the attribute
            java.lang.Object value = valueToObject(attributeFieldType,
                    caseInsensitive ? attribute.getValue().toLowerCase() : attribute.getValue());

            // add a positional parameter value to the list
            parameters.add(value);
        } else {
            // binary predicates just get appended w/o values associated with them
            hql.append(predicateAsString);
        }
    }

    /**
     * Processes CQL associations into HQL
     *
     * @param association
     *       The CQL association
     * @param hql
     *       The HQL fragment which will be edited
      * @param parameters
      *      The positional HQL query parameters
     * @param associationTrace
     *       The trace of associations
     * @param sourceClassName
     *       The class name of the type to which this association belongs
     * @throws QueryProcessingException
     */
    private void processAssociation(Association association, StringBuilder hql, List<java.lang.Object> parameters,
            Stack<Association> associationStack, Object sourceQueryObject, String sourceAlias)
            throws QueryProcessingException {
        LOG.debug("Processing association " + sourceQueryObject.getName() + " to " + association.getName());

        // get the association's role name
        String roleName = roleNameResolver.getRoleName(sourceQueryObject.getName(), association);
        if (roleName == null) {
            // still null?? no association to the object!
            // TODO: should probably be malformed query exception
            throw new QueryProcessingException("Association from type " + sourceQueryObject.getName() + " to type "
                    + association.getName() + " does not exist.  Use only direct associations");
        }
        LOG.debug("Role name determined to be " + roleName);

        // determine the alias for this association
        String alias = getAssociationAlias(sourceQueryObject.getName(), association.getName(), roleName);
        LOG.debug("Association alias determined to be " + alias);

        // add this association to the stack
        associationStack.push(association);

        // flag indicates the query is only verifying the association is populated
        boolean simpleNullCheck = true;
        if (association.getAssociation() != null) {
            simpleNullCheck = false;
            // add clause to select things from this association
            hql.append(sourceAlias).append('.').append(roleName);
            hql.append(".id in (select ").append(alias).append(".id from ");
            hql.append(association.getName()).append(" as ").append(alias).append(" where ");
            processAssociation(association.getAssociation(), hql, parameters, associationStack, association, alias);
            hql.append(") ");
        }
        if (association.getAttribute() != null) {
            simpleNullCheck = false;
            processAttribute(association.getAttribute(), hql, parameters, association,
                    sourceAlias + "." + roleName);
        }
        if (association.getGroup() != null) {
            simpleNullCheck = false;
            hql.append(sourceAlias).append('.').append(roleName);
            hql.append(".id in (select ").append(alias).append(".id from ");
            hql.append(association.getName()).append(" as ").append(alias).append(" where ");
            processGroup(association.getGroup(), hql, parameters, associationStack, association, alias);
            hql.append(") ");
        }

        if (simpleNullCheck) {
            // query is checking for the association to exist and be non-null
            hql.append(sourceAlias).append('.').append(roleName).append(".id is not null ");
        }

        // pop this association off the stack
        associationStack.pop();
        LOG.debug(associationStack.size() + " associations remain on the stack");
    }

    /**
     * Processes a CQL group into HQL
     *
     * @param group
     *       The CQL Group
     * @param hql
     *       The HQL fragment which will be edited
      * @param parameters
      *      The positional HQL query parameters
     * @param associationTrace
     *       The trace of associations
     * @param sourceClassName
     *       The class to which this group belongs
     * @throws QueryProcessingException
     */
    private void processGroup(Group group, StringBuilder hql, List<java.lang.Object> parameters,
            Stack<Association> associationStack, Object sourceQueryObject, String sourceAlias)
            throws QueryProcessingException {
        LOG.debug("Processing group on " + sourceQueryObject.getName());

        String logic = convertLogicalOperator(group.getLogicRelation());
        boolean mustAddLogic = false;

        // open the group
        hql.append('(');

        if (group.getAssociation() != null) {
            for (int i = 0; i < group.getAssociation().length; i++) {
                mustAddLogic = true;
                processAssociation(group.getAssociation(i), hql, parameters, associationStack, sourceQueryObject,
                        sourceAlias);
                if (i + 1 < group.getAssociation().length) {
                    hql.append(' ').append(logic).append(' ');
                }
            }
        }
        if (group.getAttribute() != null) {
            if (mustAddLogic) {
                hql.append(' ').append(logic).append(' ');
            }
            for (int i = 0; i < group.getAttribute().length; i++) {
                mustAddLogic = true;
                processAttribute(group.getAttribute(i), hql, parameters, sourceQueryObject, sourceAlias);
                if (i + 1 < group.getAttribute().length) {
                    hql.append(' ').append(logic).append(' ');
                }
            }
        }
        if (group.getGroup() != null) {
            if (mustAddLogic) {
                hql.append(' ').append(logic).append(' ');
            }
            for (int i = 0; i < group.getGroup().length; i++) {
                processGroup(group.getGroup(i), hql, parameters, associationStack, sourceQueryObject, sourceAlias);
                if (i + 1 < group.getGroup().length) {
                    hql.append(' ').append(logic).append(' ');
                }
            }
        }

        // close the group
        hql.append(')');
    }

    /**
     * Converts a logical operator to its HQL string equiavalent.
     *
     * @param op
     *       The logical operator to convert
     * @return
     *       The CQL logical operator as HQL
     */
    private String convertLogicalOperator(LogicalOperator op) throws QueryProcessingException {
        if (op.getValue().equals(LogicalOperator._AND)) {
            return "AND";
        } else if (op.getValue().equals(LogicalOperator._OR)) {
            return "OR";
        }
        throw new QueryProcessingException("Logical operator '" + op.getValue() + "' is not recognized.");
    }

    // uses the class type to convert the value to a typed object
    private java.lang.Object valueToObject(String className, String value) throws QueryProcessingException {
        LOG.debug("Converting \"" + value + "\" to object of type " + className);
        if (className.equals(String.class.getName())) {
            return value;
        }
        if (className.equals(Integer.class.getName())) {
            return Integer.valueOf(value);
        }
        if (className.equals(Long.class.getName())) {
            return Long.valueOf(value);
        }
        if (className.equals(Double.class.getName())) {
            return Double.valueOf(value);
        }
        if (className.equals(Float.class.getName())) {
            return Float.valueOf(value);
        }
        if (className.equals(Boolean.class.getName())) {
            return Boolean.valueOf(value);
        }
        if (className.equals(Character.class.getName())) {
            if (value.length() == 1) {
                return Character.valueOf(value.charAt(0));
            } else {
                throw new QueryProcessingException(
                        "The value \"" + value + "\" of length " + value.length() + " is not a valid character");
            }
        }
        if (className.equals(Date.class.getName())) {
            // try time, then dateTime, then just date
            List<SimpleDateFormat> formats = new ArrayList<SimpleDateFormat>(3);
            formats.add(new SimpleDateFormat("HH:mm:ss"));
            formats.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"));
            formats.add(new SimpleDateFormat("yyyy-MM-dd"));

            Date date = null;
            Iterator<SimpleDateFormat> formatIter = formats.iterator();
            while (date == null && formatIter.hasNext()) {
                SimpleDateFormat formatter = formatIter.next();
                try {
                    date = formatter.parse(value);
                } catch (ParseException ex) {
                    LOG.debug(value + " was not parsable by pattern " + formatter.toPattern());
                }
            }
            if (date == null) {
                throw new QueryProcessingException("Unable to parse date value \"" + value + "\"");
            }

            return date;
        }

        throw new QueryProcessingException("No conversion for type " + className);
    }

    private String getAssociationAlias(String parentClassName, String associationClassName, String roleName) {
        int dotIndex = parentClassName.lastIndexOf('.');
        String parentShortName = dotIndex != -1 ? parentClassName.substring(dotIndex + 1) : parentClassName;
        dotIndex = associationClassName.lastIndexOf('.');
        String associationShortName = dotIndex != -1 ? associationClassName.substring(dotIndex + 1)
                : associationClassName;
        String alias = "__" + parentShortName + "_" + associationShortName + "_" + roleName;
        return alias;
    }
}