com.impetus.kundera.query.KunderaQuery.java Source code

Java tutorial

Introduction

Here is the source code for com.impetus.kundera.query.KunderaQuery.java

Source

/*******************************************************************************
 * * Copyright 2012 Impetus Infotech.
 *  *
 *  * Licensed under the Apache License, Version 2.0 (the "License");
 *  * you may not use this file except in compliance with the License.
 *  * You may obtain a copy of the License at
 *  *
 *  *      http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  * Unless required by applicable law or agreed to in writing, software
 *  * distributed under the License is distributed on an "AS IS" BASIS,
 *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  * See the License for the specific language governing permissions and
 *  * limitations under the License.
 ******************************************************************************/
package com.impetus.kundera.query;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.StringTokenizer;

import javax.el.ExpressionFactory;
import javax.persistence.Parameter;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.Metamodel;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.eclipse.persistence.jpa.jpql.parser.DeleteStatement;
import org.eclipse.persistence.jpa.jpql.parser.EclipseLinkJPQLGrammar2_4;
import org.eclipse.persistence.jpa.jpql.parser.JPQLExpression;
import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar;
import org.eclipse.persistence.jpa.jpql.parser.SelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.UpdateStatement;
import org.eclipse.persistence.jpa.jpql.parser.WhereClause;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.impetus.kundera.KunderaException;
import com.impetus.kundera.metadata.KunderaMetadataManager;
import com.impetus.kundera.metadata.model.EntityMetadata;
import com.impetus.kundera.metadata.model.MetamodelImpl;
import com.impetus.kundera.metadata.model.type.AbstractManagedType;
import com.impetus.kundera.persistence.EntityManagerFactoryImpl.KunderaMetadata;

/**
 * The Class KunderaQuery.
 */
public class KunderaQuery {
    /** The Constant SINGLE_STRING_KEYWORDS. */
    public static final String[] SINGLE_STRING_KEYWORDS = { "SELECT", "UPDATE", "SET", "DELETE", "UNIQUE", "FROM",
            "WHERE", "GROUP BY", "HAVING", "ORDER BY" };

    /** The Constant INTER_CLAUSE_OPERATORS. */
    public static final String[] INTER_CLAUSE_OPERATORS = { "AND", "OR", "BETWEEN", "(", ")" };

    /** The Constant INTRA_CLAUSE_OPERATORS. */
    public static final String[] INTRA_CLAUSE_OPERATORS = { "=", "LIKE", "IN", ">", ">=", "<", "<=", "<>",
            "NOT IN" };

    /** The logger. */
    private static Logger logger = LoggerFactory.getLogger(KunderaQuery.class);

    /** The result. */
    private String[] result;

    /** The aggregation result. */
    private String[] aggregationResult;

    /** The from. */
    private String from;

    /** The filter. */
    private String filter;

    /** The ordering. */
    private String ordering;

    /** The entity name. */
    private String entityName;

    /** The entity alias. */
    private String entityAlias;

    /** The entity class. */
    private Class<?> entityClass;

    /** The sort orders. */
    private List<SortOrdering> sortOrders;

    /** The is aggregate. */
    private boolean isAggregate;

    /** Persistence Unit(s). */
    private String persistenceUnit;

    // contains a Queue of alternate FilterClause object and Logical Strings
    // (AND, OR etc.)
    /** The filters queue. */
    private Queue filtersQueue = new LinkedList();

    /** The is delete update. */
    private boolean isDeleteUpdate;

    /** The update clause queue. */
    private Queue<UpdateClause> updateClauseQueue = new LinkedList<UpdateClause>();

    /** The typed parameter. */
    private TypedParameter typedParameter;

    /** The parameters map. */
    private Map<String, Object> parametersMap = new HashMap<String, Object>();

    /** The is native query. */
    boolean isNativeQuery;

    /** The jpa query. */
    private String jpaQuery;

    /** The kundera metadata. */
    private final KunderaMetadata kunderaMetadata;

    /** The jpql expression. */
    private JPQLExpression jpqlExpression;

    /** The expression factory. */
    private ExpressionFactory expressionFactory;

    /** The select statement. */
    private SelectStatement selectStatement;

    /** The update statement. */
    private UpdateStatement updateStatement;

    /** The delete statement. */
    private DeleteStatement deleteStatement;

    /**
     * Sets the expression factory.
     * 
     * @param expressionFactory
     *            the expressionFactory to set
     */
    public void setExpressionFactory(ExpressionFactory expressionFactory) {
        this.expressionFactory = expressionFactory;
    }

    /**
     * Gets the jpql expression.
     * 
     * @return the jpqlExpression
     */
    public JPQLExpression getJpqlExpression() {
        return jpqlExpression;
    }

    /**
     * Instantiates a new kundera query.
     * 
     * @param jpaQuery
     *            the jpa query
     * @param kunderaMetadata
     *            the kundera metadata
     */
    public KunderaQuery(final String jpaQuery, final KunderaMetadata kunderaMetadata) {
        this.jpaQuery = jpaQuery;
        this.kunderaMetadata = kunderaMetadata;
        initiateJPQLObject(jpaQuery);
    }

    /**
     * Initiate jpql object.
     * 
     * @param jpaQuery
     *            the jpa query
     */
    private void initiateJPQLObject(final String jpaQuery) {
        JPQLGrammar jpqlGrammar = EclipseLinkJPQLGrammar2_4.instance();
        this.jpqlExpression = new JPQLExpression(jpaQuery, jpqlGrammar, "ql_statement", true);
        setKunderaQueryTypeObject();
    }

    /**
     * Sets the kundera query type object.
     */
    private void setKunderaQueryTypeObject() {

        try {
            if (isSelectStatement()) {

                this.setSelectStatement((SelectStatement) (this.getJpqlExpression().getQueryStatement()));

            } else if (isUpdateStatement()) {

                this.setUpdateStatement((UpdateStatement) (this.getJpqlExpression().getQueryStatement()));

            } else if (isDeleteStatement()) {
                this.setDeleteStatement((DeleteStatement) (this.getJpqlExpression().getQueryStatement()));

            }
        } catch (ClassCastException cce) {
            throw new JPQLParseException("Bad query format : " + cce.getMessage());
        }

    }

    /**
     * Gets the select statement.
     * 
     * @return the selectStatement
     */
    public SelectStatement getSelectStatement() {
        return selectStatement;
    }

    /**
     * Sets the select statement.
     * 
     * @param selectStatement
     *            the selectStatement to set
     */
    public void setSelectStatement(SelectStatement selectStatement) {
        this.selectStatement = selectStatement;
    }

    /**
     * Sets the update statement.
     * 
     * @param updateStatement
     *            the updateStatement to set
     */
    public void setUpdateStatement(UpdateStatement updateStatement) {
        this.updateStatement = updateStatement;
    }

    /**
     * Gets the update statement.
     * 
     * @return the updateStatement
     */
    public UpdateStatement getUpdateStatement() {
        return updateStatement;
    }

    /**
     * Gets the delete statement.
     * 
     * @return the deleteStatement
     */
    public DeleteStatement getDeleteStatement() {
        return deleteStatement;
    }

    /**
     * Sets the delete statement.
     * 
     * @param deleteStatement
     *            the deleteStatement to set
     */
    public void setDeleteStatement(DeleteStatement deleteStatement) {
        this.deleteStatement = deleteStatement;
    }

    /**
     * Checks if is select statement.
     * 
     * @return true, if is select statement
     */
    public boolean isSelectStatement() {
        return this.getJpqlExpression().getQueryStatement().getClass().isAssignableFrom(SelectStatement.class);

    }

    /**
     * Checks if is delete statement.
     * 
     * @return true, if is delete statement
     */
    public boolean isDeleteStatement() {
        return this.getJpqlExpression().getQueryStatement().getClass().isAssignableFrom(DeleteStatement.class);
    }

    /**
     * Checks if is update statement.
     * 
     * @return true, if is update statement
     */
    public boolean isUpdateStatement() {
        return this.getJpqlExpression().getQueryStatement().getClass().isAssignableFrom(UpdateStatement.class);
    }

    /**
     * Gets the expression factory.
     * 
     * @return the expressionFactory
     */
    public ExpressionFactory getExpressionFactory() {
        return expressionFactory;
    }

    /**
     * Sets the grouping.
     * 
     * @param groupingClause
     *            the new grouping
     */
    public void setGrouping(String groupingClause) {
    }

    /**
     * Sets the result.
     * 
     * @param result
     *            the new result
     */
    public final void setResult(String[] result) {
        this.result = result;
    }

    /**
     * Sets the aggregation result.
     * 
     * @param aggResult
     *            the new result
     */
    public final void setAggregationResult(String[] aggResult) {
        this.aggregationResult = aggResult;
    }

    /**
     * Gets the agg result.
     * 
     * @return Aggregation result set
     */
    public final String[] getAggResult() {
        return aggregationResult;
    }

    /**
     * Checks if is aggregated.
     * 
     * @return Query contains aggregation or not
     */
    public boolean isAggregated() {
        return isAggregate;
    }

    /**
     * Sets the aggregated.
     * 
     * @param isAggregated
     *            the new aggregated
     */
    public void setAggregated(boolean isAggregated) {
        this.isAggregate = isAggregated;
    }

    /**
     * Sets the from.
     * 
     * @param from
     *            the new from
     */
    public final void setFrom(String from) {
        this.from = from;
    }

    /**
     * Sets the filter.
     * 
     * @param filter
     *            the new filter
     */
    public final void setFilter(String filter) {
        this.filter = filter;
    }

    /**
     * Sets the ordering.
     * 
     * @param ordering
     *            the new ordering
     */
    public final void setOrdering(String ordering) {
        this.ordering = ordering;
        parseOrdering(this.ordering);
    }

    /**
     * Gets the filter.
     * 
     * @return the filter
     */
    public final String getFilter() {
        return filter;
    }

    /**
     * Gets the from.
     * 
     * @return the from
     */
    public final String getFrom() {
        return from;
    }

    /**
     * Gets the ordering.
     * 
     * @return the ordering
     */
    public final List<SortOrdering> getOrdering() {
        return sortOrders;
    }

    /**
     * Gets the result.
     * 
     * @return the result
     */
    public final String[] getResult() {
        return result;
    }

    /**
     * Gets the parameters map.
     * 
     * @return Map of query parameters.
     */
    public Map<String, Object> getParametersMap() {
        return parametersMap;
    }

    /**
     * Method to check if required result is to get complete entity or a select
     * scalar value.
     * 
     * @return true, if it result is for complete alias.
     * 
     */
    public final boolean isAliasOnly() {
        // TODO
        return result != null && (result[0].indexOf(".") == -1);
    }

    /**
     * Returns set of parameters.
     * 
     * @return jpaParameters
     */
    public Set<Parameter<?>> getParameters() {
        return typedParameter != null ? typedParameter.jpaParameters : null;
    }

    /**
     * Parameter is bound if it holds any value, else will return false.
     * 
     * @param param
     *            the param
     * @return true, if is bound
     */
    public boolean isBound(Parameter param) {
        return getClauseValue(param) != null;
    }

    /**
     * Returns clause value for supplied parameter.
     * 
     * @param paramString
     *            the param string
     * @return the clause value
     */
    public List<Object> getClauseValue(String paramString) {
        if (typedParameter != null && typedParameter.getParameters() != null) {
            List<FilterClause> clauses = typedParameter.getParameters().get(paramString);
            if (clauses != null) {
                return clauses.get(0).getValue();
            } else {
                throw new IllegalArgumentException("parameter is not a parameter of the query");
            }
        }

        logger.error("Parameter {} is not a parameter of the query.", paramString);
        throw new IllegalArgumentException("Parameter is not a parameter of the query.");
    }

    /**
     * Returns specific clause value.
     * 
     * @param param
     *            parameter
     * 
     * @return clause value.
     */
    public List<Object> getClauseValue(Parameter param) {
        Parameter match = null;
        if (typedParameter != null && typedParameter.jpaParameters != null) {
            for (Parameter p : typedParameter.jpaParameters) {
                if (p.equals(param)) {
                    match = p;
                    if (typedParameter.getType().equals(Type.NAMED)) {
                        List<FilterClause> clauses = typedParameter.getParameters().get(":" + p.getName());
                        if (clauses != null) {
                            return clauses.get(0).getValue();
                        }
                    } else {
                        List<FilterClause> clauses = typedParameter.getParameters().get("?" + p.getPosition());
                        if (clauses != null) {
                            return clauses.get(0).getValue();
                        } else {
                            UpdateClause updateClause = typedParameter.getUpdateParameters()
                                    .get("?" + p.getPosition());
                            if (updateClause != null) {
                                List<Object> value = new ArrayList<Object>();
                                value.add(updateClause.getValue());
                                return value;
                            }

                        }
                    }
                    break;
                }
            }
            if (match == null) {
                throw new IllegalArgumentException("parameter is not a parameter of the query");
            }
        }

        logger.error("parameter{} is not a parameter of the query", param);
        throw new IllegalArgumentException("parameter is not a parameter of the query");
    }

    // must be executed after parse(). it verifies and populated the query
    // predicates.
    /**
     * Post parsing init.
     */
    protected void postParsingInit() {
        initEntityClass();
        initFilter();
        initUpdateClause();
    }

    /**
     * Inits the update clause.
     */
    private void initUpdateClause() {
        for (UpdateClause updateClause : updateClauseQueue) {

            onTypedParameter(updateClause.getValue(), updateClause, updateClause.getProperty().trim());
        }

    }

    /**
     * Inits the entity class.
     */
    private void initEntityClass() {
        if (from == null) {
            throw new JPQLParseException("Bad query format FROM clause is mandatory for SELECT queries");
        }
        String fromArray[] = from.split(" ");

        if (!this.isDeleteUpdate) {
            if (fromArray.length == 3 && fromArray[1].equalsIgnoreCase("as")) {
                fromArray = new String[] { fromArray[0], fromArray[2] };
            }

            if (fromArray.length != 2) {
                throw new JPQLParseException("Bad query format: " + from
                        + ". Identification variable is mandatory in FROM clause for SELECT queries");
            }

            // TODO
            StringTokenizer tokenizer = new StringTokenizer(result[0], ",");
            while (tokenizer.hasMoreTokens()) {
                String token = tokenizer.nextToken();
                if (!StringUtils.containsAny(fromArray[1] + ".", token)) {
                    throw new QueryHandlerException("bad query format with invalid alias:" + token);
                }
            }
        }

        this.entityName = fromArray[0];
        if (fromArray.length == 2)
            this.entityAlias = fromArray[1];

        persistenceUnit = kunderaMetadata.getApplicationMetadata().getMappedPersistenceUnit(entityName);

        // Get specific metamodel.
        MetamodelImpl model = getMetamodel(persistenceUnit);

        if (model != null) {
            entityClass = model.getEntityClass(entityName);
        }

        if (null == entityClass) {
            logger.error(
                    "No entity {} found, please verify it is properly annotated with @Entity and not a mapped Super class",
                    entityName);
            throw new QueryHandlerException("No entity found by the name: " + entityName);
        }

        EntityMetadata metadata = model.getEntityMetadata(entityClass);

        if (metadata != null && !metadata.isIndexable()) {
            throw new QueryHandlerException(entityClass + " is not indexed. Not possible to run a query on it."
                    + " Check whether it was properly annotated for indexing.");
        }
    }

    /**
     * Inits the filter.
     */
    private void initFilter() {
        EntityMetadata metadata = KunderaMetadataManager.getEntityMetadata(kunderaMetadata, entityClass);
        Metamodel metaModel = kunderaMetadata.getApplicationMetadata().getMetamodel(getPersistenceUnit());
        EntityType entityType = metaModel.entity(entityClass);

        if (null == filter) {
            List<String> clauses = new ArrayList<String>();
            addDiscriminatorClause(clauses, entityType);
            return;
        }
        WhereClause whereClause = KunderaQueryUtils.getWhereClause(getJpqlExpression());

        KunderaQueryUtils.traverse(whereClause.getConditionalExpression(), metadata, kunderaMetadata, this, false);

        for (Object filterClause : filtersQueue) {

            if (!(filterClause instanceof String)) {
                onTypedParameter(((FilterClause) filterClause));
            }

        }

        addDiscriminatorClause(null, entityType);
    }

    /**
     * Adds the discriminator clause.
     * 
     * @param clauses
     *            the clauses
     * @param entityType
     *            the entity type
     */
    private void addDiscriminatorClause(List<String> clauses, EntityType entityType) {
        if (((AbstractManagedType) entityType).isInherited()) {
            String discrColumn = ((AbstractManagedType) entityType).getDiscriminatorColumn();
            String discrValue = ((AbstractManagedType) entityType).getDiscriminatorValue();

            if (discrColumn != null && discrValue != null) {
                if (clauses != null && !clauses.isEmpty()) {
                    filtersQueue.add("AND");
                }

                FilterClause filterClause = new FilterClause(discrColumn, "=", discrValue, discrColumn);
                filtersQueue.add(filterClause);
            }
        }
    }

    /**
     * Depending upon filter value, if it starts with ":" then it is NAMED
     * parameter, else if starts with "?", it will be INDEXED parameter.
     * 
     * @param value
     *            the value
     * @param updateClause
     *            the update clause
     * @param fieldName
     *            the field name
     */
    private void onTypedParameter(Object value, UpdateClause updateClause, String fieldName) {
        String token = value.toString();
        if (token != null && token.startsWith(":")) {
            addTypedParameter(Type.NAMED, token, updateClause);
            filterJPAParameterInfo(Type.NAMED, token.substring(1), fieldName);
        } else if (token != null && token.startsWith("?")) {
            addTypedParameter(Type.INDEXED, token, updateClause);
            filterJPAParameterInfo(Type.INDEXED, token.substring(1), fieldName);
        }
    }

    /**
     * Depending upon filter value, if it starts with ":" then it is NAMED
     * parameter, else if starts with "?", it will be INDEXED parameter.
     * 
     * @param filterClause
     *            filter clauses.
     */
    private void onTypedParameter(FilterClause filterClause) {

        if (filterClause.value != null && filterClause.value.get(0).toString().startsWith(":")) {
            addTypedParameter(Type.NAMED, filterClause.value.get(0).toString(), filterClause);
            filterJPAParameterInfo(Type.NAMED, filterClause.value.get(0).toString().substring(1),
                    filterClause.fieldName);
        } else if (filterClause.value.toString() != null && filterClause.value.get(0).toString().startsWith("?")) {
            addTypedParameter(Type.INDEXED, filterClause.value.get(0).toString(), filterClause);
            filterJPAParameterInfo(Type.INDEXED, filterClause.value.get(0).toString().substring(1),
                    filterClause.fieldName);
        }
    }

    /**
     * Adds typed parameter to {@link TypedParameter}.
     * 
     * @param type
     *            type of parameter(e.g. NAMED/INDEXED)
     * @param parameter
     *            parameter name.
     * @param clause
     *            filter clause.
     */
    private void addTypedParameter(Type type, String parameter, FilterClause clause) {
        if (typedParameter == null) {
            typedParameter = new TypedParameter(type);
        }

        if (typedParameter.getType().equals(type)) {
            typedParameter.addParameters(parameter, clause);
        } else {
            logger.warn("Invalid type provided, it can either be name or indexes!");
        }
    }

    /**
     * Adds typed parameter to {@link TypedParameter}.
     * 
     * @param type
     *            type of parameter(e.g. NAMED/INDEXED)
     * @param parameter
     *            parameter name.
     * @param clause
     *            filter clause.
     */
    private void addTypedParameter(Type type, String parameter, UpdateClause clause) {
        if (type != null) {
            if (typedParameter == null) {
                typedParameter = new TypedParameter(type);
            }

            if (typedParameter.getType().equals(type)) {
                typedParameter.addParameters(parameter, clause);
            } else {
                logger.warn("Invalid type provided, it can either be name or indexes!");
            }
        }
    }

    /**
     * Filter jpa parameter info.
     * 
     * @param type
     *            the type
     * @param name
     *            the name
     * @param fieldName
     *            the field name
     */
    private void filterJPAParameterInfo(Type type, String name, String fieldName) {
        String attributeName = getAttributeName(fieldName);
        Attribute entityAttribute = ((MetamodelImpl) kunderaMetadata.getApplicationMetadata()
                .getMetamodel(persistenceUnit)).getEntityAttribute(entityClass, attributeName);
        Class fieldType = entityAttribute.getJavaType();

        if (type.equals(Type.INDEXED)) {
            typedParameter.addJPAParameter(new JPAParameter(null, Integer.valueOf(name), fieldType));
        } else {
            typedParameter.addJPAParameter(new JPAParameter(name, null, fieldType));
        }
    }

    /**
     * Gets the attribute name.
     * 
     * @param fieldName
     *            the field name
     * @return the attribute name
     */
    private String getAttributeName(String fieldName) {
        String attributeName = fieldName;
        if (fieldName.indexOf(".") != -1) {
            attributeName = fieldName.substring(0, fieldName.indexOf("."));
        }
        return attributeName;
    }

    /**
     * Sets the parameter.
     * 
     * @param name
     *            the name
     * @param value
     *            the value
     */
    public final void setParameter(String name, Object value) {
        setParameterValue(":" + name, value);
        parametersMap.put(":" + name, value);
    }

    /**
     * Sets the parameter.
     * 
     * @param position
     *            the position
     * @param value
     *            the value
     */
    public final void setParameter(int position, Object value) {
        setParameterValue("?" + position, value);
        parametersMap.put("?" + position, value);
    }

    /**
     * Sets parameter value into filterClause, depending upon {@link Type}.
     * 
     * @param name
     *            parameter name.
     * @param value
     *            parameter value.
     */
    private void setParameterValue(String name, Object value) {
        if (typedParameter != null) {
            List<FilterClause> clauses = typedParameter.getParameters() != null
                    ? typedParameter.getParameters().get(name)
                    : null;
            if (clauses != null) {
                for (FilterClause clause : clauses) {
                    clause.setValue(value);
                }
            } else {
                if (typedParameter.getUpdateParameters() != null) {
                    UpdateClause updateClause = typedParameter.getUpdateParameters().get(name);
                    updateClause.setValue(value);
                } else {
                    logger.error("Error while setting parameter.");
                    throw new QueryHandlerException("named parameter : " + name + " not found!");
                }
            }
        } else {
            throw new QueryHandlerException("No named parameter present for query");
        }
    }

    /**
     * Gets the entity class.
     * 
     * @return the entityClass
     */
    public final Class getEntityClass() {
        return entityClass;
    }

    /**
     * Gets the entity alias.
     * 
     * @return the entity alias
     */
    public final String getEntityAlias() {
        return this.entityAlias;
    }

    /**
     * Checks if is native.
     * 
     * @return true, if is native
     */
    public boolean isNative() {
        return isNativeQuery;
    }

    /**
     * Gets the entity metadata.
     * 
     * @return the entity metadata
     */
    public final EntityMetadata getEntityMetadata() {
        EntityMetadata metadata = null;
        try {
            metadata = KunderaMetadataManager.getEntityMetadata(kunderaMetadata, entityClass);
        } catch (KunderaException e) {
            logger.info("No Entity class provided, Proceeding as Scalar Query");
        }
        if (!this.isNativeQuery && metadata == null) {
            throw new KunderaException("Unable to load entity metadata for : " + entityClass);
        }
        return metadata;
    }

    /**
     * Gets the filter clause queue.
     * 
     * @return the filters
     */
    public final Queue getFilterClauseQueue() {
        return filtersQueue;
    }

    /**
     * The FilterClause class to hold a where clause predicate.
     */
    public final class FilterClause {

        /** The property. */
        private String property;

        /** The condition. */
        private String condition;

        /** The condition. */
        private String fieldName;

        /**
         * Gets the field name.
         * 
         * @return the fieldName
         */
        public String getFieldName() {
            return fieldName;
        }

        /** The value. */
        private List<Object> value = new ArrayList<Object>();

        /**
         * The Constructor.
         * 
         * @param property
         *            the property
         * @param condition
         *            the condition
         * @param value
         *            the value
         * @param fieldName
         *            the field name
         */
        public FilterClause(String property, String condition, Object value, String fieldName) {
            super();
            this.property = property;
            this.condition = condition.trim();
            this.fieldName = fieldName;
            if (value instanceof Collection) {
                for (Object valueObject : (Collection) value) {
                    this.value.add(KunderaQuery.getValue(valueObject));
                }
            } else {
                this.value.add(KunderaQuery.getValue(value));
            }
        }

        /**
         * Gets the property.
         * 
         * @return the property
         */
        public final String getProperty() {
            return property;
        }

        /**
         * Gets the condition.
         * 
         * @return the condition
         */
        public final String getCondition() {
            return condition;
        }

        /**
         * Gets the value.
         * 
         * @return the value
         */
        public final List<Object> getValue() {
            return value;
        }

        /**
         * Sets the value.
         * 
         * @param value
         *            the value to set
         */
        protected void setValue(Object value) {
            List<Object> valObjects = new ArrayList<Object>();
            if (value instanceof Collection) {
                for (Object valueObject : (Collection) value) {
                    valObjects.add(KunderaQuery.getValue(valueObject));
                }
            } else {
                valObjects.add(KunderaQuery.getValue(value));
            }

            this.value = valObjects;
        }

        /* @see java.lang.Object#toString() */
        /*
         * (non-Javadoc)
         * 
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("FilterClause [property=");
            builder.append(property);
            builder.append(", condition=");
            builder.append(condition);
            builder.append(", value=");
            builder.append(value);
            builder.append(", fieldName=");
            builder.append(fieldName);
            builder.append("]");
            return builder.toString();
        }
    }

    /**
     * The Class UpdateClause.
     */
    public final class UpdateClause {

        /** The property. */
        private String property;

        /** The value. */
        private Object value;

        /**
         * Instantiates a new update clause.
         * 
         * @param property
         *            the property
         * @param value
         *            the value
         */
        public UpdateClause(final String property, final Object value) {
            this.property = property;
            this.value = KunderaQuery.getValue(value);
        }

        /**
         * Gets the property.
         * 
         * @return the property
         */
        public String getProperty() {
            return property;
        }

        /**
         * Gets the value.
         * 
         * @return the value
         */
        public Object getValue() {
            return value;
        }

        /**
         * Sets the value.
         * 
         * @param value
         *            the value to set
         */
        public void setValue(Object value) {
            this.value = KunderaQuery.getValue(value);
        }

    }

    /* @see java.lang.Object#clone() */
    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#clone()
     */
    @Override
    public final Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    /* @see java.lang.Object#toString() */
    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public final String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("KunderaQuery [entityName=");
        builder.append(entityName);
        builder.append(", entityAlias=");
        builder.append(entityAlias);
        builder.append(", filtersQueue=");
        builder.append(filtersQueue);
        builder.append("]");
        return builder.toString();
    }

    /**
     * Gets the metamodel.
     * 
     * @param pu
     *            the pu
     * @return the metamodel
     */
    private MetamodelImpl getMetamodel(String pu) {
        return KunderaMetadataManager.getMetamodel(kunderaMetadata, pu);
    }

    /**
     * Gets the persistence units.
     * 
     * @return the persistenceUnits
     */
    public String getPersistenceUnit() {
        return persistenceUnit;
    }

    /**
     * Sets the persistence unit.
     * 
     * @param persistenceUnit
     *            the new persistence unit
     */
    public void setPersistenceUnit(String persistenceUnit) {
        this.persistenceUnit = persistenceUnit;
    }

    /**
     * Parses the ordering @See Order By Clause.
     * 
     * @param ordering
     *            the ordering
     */
    private void parseOrdering(String ordering) {
        final String comma = ",";
        final String space = " ";

        StringTokenizer tokenizer = new StringTokenizer(ordering, comma);

        sortOrders = new ArrayList<KunderaQuery.SortOrdering>();
        while (tokenizer.hasMoreTokens()) {
            String order = (String) tokenizer.nextElement();
            StringTokenizer token = new StringTokenizer(order, space);
            SortOrder orderType = SortOrder.ASC;

            String colName = (String) token.nextElement();
            while (token.hasMoreElements()) {
                String nextOrder = (String) token.nextElement();

                // more spaces given.
                if (StringUtils.isNotBlank(nextOrder)) {
                    try {
                        orderType = SortOrder.valueOf(nextOrder.toUpperCase());
                    } catch (IllegalArgumentException e) {
                        logger.error("Error while parsing order by clause:");
                        throw new JPQLParseException("Invalid sort order provided:" + nextOrder);
                    }
                }
            }
            sortOrders.add(new SortOrdering(colName, orderType));
        }
    }

    /**
     * Containing SortOrder.
     */
    public class SortOrdering {

        /** The column name. */
        String columnName;

        /** The order. */
        SortOrder order;

        /**
         * Instantiates a new sort ordering.
         * 
         * @param columnName
         *            the column name
         * @param order
         *            the order
         */
        public SortOrdering(String columnName, SortOrder order) {
            this.columnName = columnName;
            this.order = order;
        }

        /**
         * Gets the column name.
         * 
         * @return the column name
         */
        public String getColumnName() {
            return columnName;
        }

        /**
         * Gets the order.
         * 
         * @return the order
         */
        public SortOrder getOrder() {
            return order;
        }
    }

    /**
     * The Enum SortOrder.
     */
    public enum SortOrder {
        /** The ASC. */
        ASC,
        /** The DESC. */
        DESC;
    }

    /**
     * Gets the update clause queue.
     * 
     * @return the updateClauseQueue
     */
    public Queue<UpdateClause> getUpdateClauseQueue() {
        return updateClauseQueue;
    }

    /**
     * Checks if is update clause.
     * 
     * @return true, if is update clause
     */
    public boolean isUpdateClause() {
        return !updateClauseQueue.isEmpty();
    }

    /**
     * Adds the update clause.
     * 
     * @param property
     *            the property
     * @param value
     *            the value
     */
    public void addUpdateClause(final String property, final String value) {
        UpdateClause updateClause = new UpdateClause(property.trim(), value.trim());
        updateClauseQueue.add(updateClause);
        addTypedParameter(
                value.trim().startsWith("?") ? Type.INDEXED : value.trim().startsWith(":") ? Type.NAMED : null,
                property, updateClause);
    }

    /**
     * Adds the filter clause.
     * 
     * @param property
     *            the property
     * @param condition
     *            the condition
     * @param value
     *            the value
     * @param fieldName
     *            the field name
     */
    public void addFilterClause(final String property, final String condition, final Object value,
            final String fieldName) {
        if (property != null && condition != null) {
            FilterClause filterClause = new FilterClause(property.trim(), condition.trim(), value, fieldName);
            filtersQueue.add(filterClause);
        } else {
            filtersQueue.add(property);
        }
    }

    /**
     * Adds the filter clause.
     * 
     * @param filterClause
     *            the filter clause
     */
    public void addFilterClause(Object filterClause) {

        filtersQueue.add(filterClause);

    }

    /**
     * Sets the checks if is delete update.
     * 
     * @param b
     *            the new checks if is delete update
     */
    public void setIsDeleteUpdate(boolean b) {
        this.isDeleteUpdate = b;
    }

    /**
     * Checks if is delete update.
     * 
     * @return true, if is delete update
     */
    public boolean isDeleteUpdate() {
        return isDeleteUpdate;
    }

    /**
     * Gets the JPA query.
     * 
     * @return the JPA query
     */
    public String getJPAQuery() {
        return this.jpaQuery;
    }

    /**
     * The Class TypedParameter.
     */
    private class TypedParameter {

        /** The type. */
        private Type type;

        /** The jpa parameters. */
        private Set<Parameter<?>> jpaParameters = new HashSet<Parameter<?>>();

        /** The parameters. */
        private Map<String, List<FilterClause>> parameters;

        /** The update parameters. */
        private Map<String, UpdateClause> updateParameters;

        /**
         * Instantiates a new typed parameter.
         * 
         * @param type
         *            the type
         */
        public TypedParameter(Type type) {
            this.type = type;
        }

        /**
         * Gets the type.
         * 
         * @return the type
         */
        private Type getType() {
            return type;
        }

        /**
         * Gets the parameters.
         * 
         * @return the parameters
         */
        Map<String, List<FilterClause>> getParameters() {
            return parameters;
        }

        /**
         * Gets the update parameters.
         * 
         * @return the parameters
         */
        Map<String, UpdateClause> getUpdateParameters() {
            return updateParameters;
        }

        /**
         * Adds the parameters.
         * 
         * @param key
         *            the key
         * @param clause
         *            the clause
         */
        void addParameters(String key, FilterClause clause) {
            if (parameters == null) {
                parameters = new HashMap<String, List<FilterClause>>();
            }
            if (!parameters.containsKey(key)) {
                parameters.put(key, new ArrayList<KunderaQuery.FilterClause>());
            }
            parameters.get(key).add(clause);
        }

        /**
         * Adds the parameters.
         * 
         * @param key
         *            the key
         * @param clause
         *            the clause
         */
        void addParameters(String key, UpdateClause clause) {
            if (updateParameters == null) {
                updateParameters = new HashMap<String, UpdateClause>();
            }

            updateParameters.put(key, clause);
        }

        /**
         * Adds the jpa parameter.
         * 
         * @param param
         *            the param
         */
        void addJPAParameter(Parameter param) {
            jpaParameters.add(param);
        }
    }

    /**
     * The Enum Type.
     */
    private enum Type {

        /** The indexed. */
        INDEXED,
        /** The named. */
        NAMED
    }

    /*
     * JPA Parameter type
     */
    /**
     * The Class JPAParameter.
     * 
     * @param <T>
     *            the generic type
     */
    private class JPAParameter<T> implements Parameter<T> {

        /** The name. */
        private String name;

        /** The position. */
        private Integer position;

        /** The type. */
        private Class<T> type;

        /**
         * Instantiates a new JPA parameter.
         * 
         * @param name
         *            the name
         * @param position
         *            the position
         * @param type
         *            the type
         */
        JPAParameter(String name, Integer position, Class<T> type) {
            this.name = name;
            this.position = position;
            this.type = type;
        }

        /*
         * (non-Javadoc)
         * 
         * @see javax.persistence.Parameter#getName()
         */
        @Override
        public String getName() {
            return name;
        }

        /*
         * (non-Javadoc)
         * 
         * @see javax.persistence.Parameter#getPosition()
         */
        @Override
        public Integer getPosition() {
            return position;
        }

        /*
         * (non-Javadoc)
         * 
         * @see javax.persistence.Parameter#getParameterType()
         */
        @Override
        public Class<T> getParameterType() {
            return type;
        }

        /*
         * (non-Javadoc)
         * 
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this);
        }

        /*
         * (non-Javadoc)
         * 
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (!obj.getClass().equals(this.getClass())) {
                return false;
            }

            Parameter<?> typed = (Parameter<?>) obj;

            if (typed.getParameterType().equals(this.getParameterType())) {
                if (this.getName() == null && typed.getName() == null) {
                    return this.getPosition() != null && this.getPosition().equals(typed.getPosition());
                } else {
                    return this.getName() != null && this.getName().equals(typed.getName());
                }

            }

            return false;
        }

        /*
         * (non-Javadoc)
         * 
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            StringBuilder strBuilder = new StringBuilder();
            strBuilder.append("[ name = " + this.getName() + "]");
            strBuilder.append("[ position = " + this.getPosition() + "]");
            strBuilder.append("[ type = " + this.getParameterType() + "]");
            return strBuilder.toString();
        }
    }

    /**
     * Method to skip string literal as per JPA specification. if literal starts
     * is enclose within "''" then skip "'" and include "'" in case of "''"
     * replace it with "'".
     * 
     * @param value
     *            value.
     * 
     * @return replaced string in case of string, else will return original
     *         value.
     */
    private static Object getValue(Object value) {
        if (value != null && value.getClass().isAssignableFrom(String.class)) {
            return ((String) value).replaceAll("^'", "").replaceAll("'$", "").replaceAll("''", "'");
        }

        return value;
    }

}