All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.impetus.kundera.query.KunderaQuery Maven / Gradle / Ivy

There is a newer version: 3.13
Show newest version
/*******************************************************************************
 * * 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.Arrays;
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 java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.el.ExpressionFactory;
import javax.persistence.Parameter;
import javax.persistence.PersistenceException;
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;

    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 sortOrders;

    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();

    private boolean isDeleteUpdate;

    private Queue updateClauseQueue = new LinkedList();

    private TypedParameter typedParameter;

    private Map parametersMap = new HashMap();

    boolean isNativeQuery;

    private String jpaQuery;

    private final KunderaMetadata kunderaMetadata;

    private JPQLExpression jpqlExpression;

    private ExpressionFactory expressionFactory;

    private SelectStatement selectStatement;

    private UpdateStatement updateStatement;

    private DeleteStatement deleteStatement;

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

    /**
     * @return the jpqlExpression
     */
    public JPQLExpression getJpqlExpression()
    {
        return jpqlExpression;
    }

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

    private void initiateJPQLObject(final String jpaQuery)
    {
        JPQLGrammar jpqlGrammar = EclipseLinkJPQLGrammar2_4.instance();
        this.jpqlExpression = new JPQLExpression(jpaQuery, jpqlGrammar, "ql_statement", true);
        setKunderaQueryTypeObject();
    }

    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());
        }

    }

    /**
     * @return the selectStatement
     */
    public SelectStatement getSelectStatement()
    {
        return selectStatement;
    }

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

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

    /**
     * @return the updateStatement
     */
    public UpdateStatement getUpdateStatement()
    {
        return updateStatement;
    }

    /**
     * @return the deleteStatement
     */
    public DeleteStatement getDeleteStatement()
    {
        return deleteStatement;
    }

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

    public boolean isSelectStatement()
    {
        return this.getJpqlExpression().getQueryStatement().getClass().isAssignableFrom(SelectStatement.class);

    }

    public boolean isDeleteStatement()
    {
        return this.getJpqlExpression().getQueryStatement().getClass().isAssignableFrom(DeleteStatement.class);
    }

    public boolean isUpdateStatement()
    {
        return this.getJpqlExpression().getQueryStatement().getClass().isAssignableFrom(UpdateStatement.class);
    }

    /**
     * @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;
    }

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

    /**
     * @return Query contains aggregation or not
     */
    public boolean isAggregated()
    {
        return isAggregate;
    }

    /**
     * @param isAggregated
     */
    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 getOrdering()
    {
        return sortOrders;
    }

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

    /**
     * @return Map of query parameters.
     */
    public Map 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> getParameters()
    {
        return typedParameter != null ? typedParameter.jpaParameters : null;
    }

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

    /**
     * Returns clause value for supplied parameter.
     * 
     * @param paramString
     * @return
     */
    public List getClauseValue(String paramString)
    {
        if (typedParameter != null && typedParameter.getParameters() != null)
        {
            List 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 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 clauses = typedParameter.getParameters().get(":" + p.getName());
                        if (clauses != null)
                        {
                            return clauses.get(0).getValue();
                        }
                    }
                    else
                    {
                        List 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 value = new ArrayList();
                                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();
    }

    /**
     * 
     */
    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 != 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 clauses = new ArrayList();
            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);
    }

    private void addDiscriminatorClause(List 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 tokens
     *            tokens
     * @param filterClause
     *            filter clauses.
     */
    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 tokens
     *            tokens
     * @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!");
            }
        }
    }

    /**
     * @param type
     * @param name
     * @param fieldName
     */
    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));
        }
    }

    /**
     * @param fieldName
     * @return
     */
    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);
    }

    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 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;
    }

    public final String getEntityAlias()
    {
        return this.entityAlias;
    }

    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;

        /**
         * @return the fieldName
         */
        public String getFieldName()
        {
            return fieldName;
        }

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

        /**
         * The Constructor.
         * 
         * @param property
         *            the property
         * @param condition
         *            the condition
         * @param value
         *            the value
         */
        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 getValue()
        {
            return value;
        }

        /**
         * Sets the value.
         * 
         * @param value
         *            the value to set
         */
        protected void setValue(Object value)
        {
            List valObjects = new ArrayList();
            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();
        }
    }

    public final class UpdateClause
    {
        private String property;

        private Object value;

        public UpdateClause(final String property, final Object value)
        {
            this.property = property;
            this.value = KunderaQuery.getValue(value);
        }

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

        /**
         * @return the value
         */
        public Object getValue()
        {
            return 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.
     * 
     * @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();
        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);
                    }
                    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;
    }

    /**
     * @return the updateClauseQueue
     */
    public Queue getUpdateClauseQueue()
    {
        return updateClauseQueue;
    }

    public boolean isUpdateClause()
    {
        return !updateClauseQueue.isEmpty();
    }

    /**
     * @param property
     * @param 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);
    }

    /**
     * @param property
     * @param condition
     * @param value
     * @param fieldName
     */
    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);
        }
    }

    /**
     * @param filterClause
     */
    public void addFilterClause(Object filterClause)
    {

        filtersQueue.add(filterClause);

    }

    /**
     * @param b
     */
    public void setIsDeleteUpdate(boolean b)
    {
        this.isDeleteUpdate = b;
    }

    public boolean isDeleteUpdate()
    {
        return isDeleteUpdate;
    }

    public String getJPAQuery()
    {
        return this.jpaQuery;
    }

    private class TypedParameter
    {
        private Type type;

        private Set> jpaParameters = new HashSet>();

        private Map> parameters;

        private Map updateParameters;

        /**
         * 
         */
        public TypedParameter(Type type)
        {
            this.type = type;
        }

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

        /**
         * @return the parameters
         */
        Map> getParameters()
        {
            return parameters;
        }

        /**
         * @return the parameters
         */
        Map getUpdateParameters()
        {
            return updateParameters;
        }

        void addParameters(String key, FilterClause clause)
        {
            if (parameters == null)
            {
                parameters = new HashMap>();
            }
            if (!parameters.containsKey(key))
            {
                parameters.put(key, new ArrayList());
            }
            parameters.get(key).add(clause);
        }

        void addParameters(String key, UpdateClause clause)
        {
            if (updateParameters == null)
            {
                updateParameters = new HashMap();
            }

            updateParameters.put(key, clause);
        }

        void addJPAParameter(Parameter param)
        {
            jpaParameters.add(param);
        }
    }

    private enum Type
    {
        INDEXED, NAMED
    }

    /*
     * JPA Parameter type
     */
    private class JPAParameter implements Parameter
    {
        private String name;

        private Integer position;

        private Class type;

        /**
         * 
         */
        JPAParameter(String name, Integer position, Class 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 getParameterType()
        {
            return type;
        }

        @Override
        public int hashCode()
        {
            return HashCodeBuilder.reflectionHashCode(this);
        }

        @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;
        }

        @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;
    }

}