org.eclipse.birt.data.oda.mongodb.internal.impl.QueryProperties.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.birt.data.oda.mongodb.internal.impl.QueryProperties.java

Source

/*
 *************************************************************************
 * Copyright (c) 2013 Actuate Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *  Actuate Corporation - initial API and implementation
 *  
 *************************************************************************
 */

package org.eclipse.birt.data.oda.mongodb.internal.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.birt.data.oda.mongodb.impl.MongoDBDriver;
import org.eclipse.birt.data.oda.mongodb.impl.MongoDBDriver.ReadPreferenceChoice;
import org.eclipse.birt.data.oda.mongodb.internal.impl.MDbMetaData.FieldMetaData;
import org.eclipse.birt.data.oda.mongodb.nls.Messages;
import org.eclipse.datatools.connectivity.oda.OdaException;

import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.ReadPreference;
import com.mongodb.util.JSON;

/**
 * Represents the properties that define a MongoDB ODA data set query.
 */
public class QueryProperties {
    private static final String MONGO_PROP_PREFIX = DriverUtil.EMPTY_STRING; // no prefix

    // query components
    private static final String COLLECTION_NAME_PROP = MONGO_PROP_PREFIX.concat("collectionName"); //$NON-NLS-1$
    private static final String QUERY_OPERATION_TYPE_PROP = MONGO_PROP_PREFIX.concat("operationType"); //$NON-NLS-1$
    private static final String QUERY_OPERATION_EXPR_PROP = MONGO_PROP_PREFIX.concat("operationExpr"); //$NON-NLS-1$
    private static final String SELECTED_FIELDS_PROP = MONGO_PROP_PREFIX.concat("selectedFields"); //$NON-NLS-1$
    private static final String FIND_QUERY_EXPR_PROP = MONGO_PROP_PREFIX.concat("findQueryExpr"); //$NON-NLS-1$
    private static final String SORT_EXPR_PROP = MONGO_PROP_PREFIX.concat("sortExpr"); //$NON-NLS-1$    

    // advanced runtime properties
    private static final String QUERY_READ_PREF_PROP = MONGO_PROP_PREFIX.concat("queryReadPreference"); //$NON-NLS-1$
    private static final String QUERY_READ_PREF_TAGS_PROP = MONGO_PROP_PREFIX.concat("queryReadPreferenceTags"); //$NON-NLS-1$
    private static final String RT_META_DATA_SEARCH_LIMIT = MONGO_PROP_PREFIX.concat("rtMDSearchLimit"); //$NON-NLS-1$
    private static final String CURSOR_BATCH_SIZE_PROP = MONGO_PROP_PREFIX.concat("batchSize"); //$NON-NLS-1$
    private static final String SKIP_NUM_DOCS_PROP = MONGO_PROP_PREFIX.concat("numSkipDocuments"); //$NON-NLS-1$
    private static final String AUTO_FLATTENING_PROP = MONGO_PROP_PREFIX.concat("flattenCollections"); //$NON-NLS-1$
    private static final String INDEX_HINTS_PROP = MONGO_PROP_PREFIX.concat("indexHints"); //$NON-NLS-1$
    private static final String NO_TIMEOUT_PROP = MONGO_PROP_PREFIX.concat("noTimeOut"); //$NON-NLS-1$
    private static final String PARTIAL_RESULTS_PROP = MONGO_PROP_PREFIX.concat("allowsPartialResults"); //$NON-NLS-1$

    public static final int DEFAULT_RUNTIME_METADATA_SEARCH_LIMIT = 10;
    public static final int DEFAULT_CURSOR_BATCH_SIZE = 101; // default used by Mongo

    private static final String DOC_ID_FIELD_NAME = QueryModel.DOC_ID_FIELD_NAME;
    private static final String ARRAY_BEGIN_MARKER = "["; //$NON-NLS-1$
    private static final String ARRAY_END_MARKER = "]"; //$NON-NLS-1$

    public enum CommandOperationType {
        DYNAMIC_QUERY, AGGREGATE, MAP_REDUCE, RUN_DB_COMMAND;

        private CommandOperationType() {
        }

        public static CommandOperationType getType(String operationTypeLiteral) {
            if (operationTypeLiteral == null || operationTypeLiteral.isEmpty())
                return DYNAMIC_QUERY;
            if (operationTypeLiteral.equals(AGGREGATE.displayName())
                    || operationTypeLiteral.equals(AGGREGATE.name()))
                return AGGREGATE;
            if (operationTypeLiteral.equals(MAP_REDUCE.displayName())
                    || operationTypeLiteral.equals(MAP_REDUCE.name()))
                return MAP_REDUCE;
            if (operationTypeLiteral.equals(RUN_DB_COMMAND.displayName())
                    || operationTypeLiteral.equals(RUN_DB_COMMAND.name()))
                return RUN_DB_COMMAND;

            return DYNAMIC_QUERY; // default
        }

        public String displayName() {
            if (this == DYNAMIC_QUERY)
                return DriverUtil.EMPTY_STRING;
            if (this == AGGREGATE)
                return Messages.queryProperties_aggrCmdName;
            if (this == MAP_REDUCE)
                return Messages.queryProperties_mapReduceCmdName;
            if (this == RUN_DB_COMMAND)
                return Messages.queryProperties_dbCmdName;
            return DriverUtil.EMPTY_STRING;
        }
    }

    private static final Map<String, Object> sm_defaultPropsMap = (new QueryProperties()).getPropertiesMap();

    private Map<String, Object> m_propertiesMap;

    public QueryProperties(String collectionName) {
        setCollectionName(collectionName);
    }

    QueryProperties(Map<String, Object> propertiesMap) {
        setValues(propertiesMap);
    }

    private QueryProperties() {
        setDefaultValues();
    }

    public static QueryProperties defaultValues() {
        return new QueryProperties(sm_defaultPropsMap);
    }

    static QueryProperties copy(QueryProperties fromProps) {
        if (fromProps == null)
            return null; // done; nothing to copy
        return new QueryProperties(fromProps.getPropertiesMap());
    }

    @SuppressWarnings("unchecked")
    public static QueryProperties deserialize(String serializedContent) throws OdaException {
        if (serializedContent == null || serializedContent.trim().isEmpty())
            return new QueryProperties((Map<String, Object>) null);

        Exception caughtEx = null;
        try {
            DBObject parsedObj = parseExprToDBObject(serializedContent);
            if (parsedObj instanceof Map<?, ?>)
                return new QueryProperties((Map<String, Object>) parsedObj);
        } catch (Exception ex) {
            caughtEx = ex;
        }

        // not able to de-serialize
        OdaException odaEx = new OdaException(
                Messages.bind(Messages.queryProperties_errDeSerializeDBObject, serializedContent));
        if (caughtEx != null)
            odaEx.initCause(caughtEx);
        throw odaEx;
    }

    public String serialize() {
        Map<String, Object> definedPropsMap = copyNonDefaultProperties(getPropertiesMap());

        // convert property values not serializable by BSON serializer
        externalizePropValues(definedPropsMap);

        return JSON.serialize(definedPropsMap);
    }

    private void setDefaultValues() {
        setOperationType(CommandOperationType.DYNAMIC_QUERY);
        setQueryReadPreference(ReadPreferenceChoice.DEFAULT_PREFERENCE);
        setRuntimeMetaDataSearchLimit(DEFAULT_RUNTIME_METADATA_SEARCH_LIMIT);
        setBatchSize(DEFAULT_CURSOR_BATCH_SIZE);
        setNumDocsToSkip(0);
        setAutoFlattening(false);
        setNoTimeOut(false);
        setPartialResultsOk(true);
    }

    private static Object getDefaultPropValue(String propKey) {
        return sm_defaultPropsMap.get(propKey);
    }

    private void setValues(Map<String, Object> propertiesMap) {
        if (propertiesMap == null || propertiesMap.isEmpty())
            return; // done; nothing to put

        // override existing values with the specified Map entries
        setInternalProperties(propertiesMap);
    }

    void setNonNullValues(Map<String, Object> propertiesMap) {
        Map<String, Object> nonNullPropsMap = copyNonNullProperties(propertiesMap);
        // override existing value with the specified Map entry value
        setInternalProperties(nonNullPropsMap);
    }

    private void setInternalProperties(Map<String, Object> propertiesMap) {
        getPropertiesMap().putAll(propertiesMap);
        internalizePropValues();
    }

    private void internalizePropValues() {
        // convert prop value to internal format (to minimize conversion for internal processing)
        Object propValue = getPropertiesMap().get(QUERY_READ_PREF_PROP);
        if (propValue instanceof String)
            setQueryReadPreference((String) propValue);

        propValue = getPropertiesMap().get(QUERY_OPERATION_TYPE_PROP);
        if (propValue instanceof String)
            setOperationType((String) propValue);
    }

    private static void externalizePropValues(Map<String, Object> propertiesMap) {
        // convert property value object not serializable by BSON serializer
        // to its string representation
        Object propValue = propertiesMap.get(QUERY_READ_PREF_PROP);
        if (propValue instanceof ReadPreference)
            propertiesMap.put(QUERY_READ_PREF_PROP, ((ReadPreference) propValue).getName());

        propValue = propertiesMap.get(QUERY_OPERATION_TYPE_PROP);
        if (propValue instanceof CommandOperationType)
            propertiesMap.put(QUERY_OPERATION_TYPE_PROP, propValue.toString());
    }

    private static Map<String, Object> copyNonNullProperties(Map<String, Object> propertiesMap) {
        if (propertiesMap == null || propertiesMap.isEmpty())
            return Collections.emptyMap(); // done; nothing to copy

        Map<String, Object> propsCopy = new HashMap<String, Object>(propertiesMap.size());
        for (Entry<String, Object> propEntry : propertiesMap.entrySet()) {
            Object propValue = propEntry.getValue();
            // copy the specified Map entry value if value is not null
            if (propValue != null)
                propsCopy.put(propEntry.getKey(), propValue);
        }
        return propsCopy;
    }

    private static Map<String, Object> copyNonDefaultProperties(Map<String, Object> propertiesMap) {
        if (propertiesMap == null || propertiesMap.isEmpty())
            return Collections.emptyMap(); // done; nothing to copy

        // copy map entries that contains non-null and non-default values
        Map<String, Object> propsCopy = new HashMap<String, Object>(propertiesMap.size());
        for (Entry<String, Object> propEntry : propertiesMap.entrySet()) {
            Object propValue = propEntry.getValue();
            if (propValue == null)
                continue; // skip null value in entry

            // check if entry is of the default value
            String propKey = propEntry.getKey();
            Object defaultValue = getDefaultPropValue(propKey);
            if (propValue instanceof String) {
                if (((String) propValue).isEmpty())
                    continue; // skip empty value in entry

                if (defaultValue instanceof Boolean || defaultValue instanceof Integer
                        || defaultValue instanceof ReadPreference)
                    defaultValue = defaultValue.toString();
            }
            if (propValue.equals(defaultValue))
                continue; // skip default value in entry

            // copy the specified Map entry value
            propsCopy.put(propKey, propValue);
        }

        return propsCopy;
    }

    Map<String, Object> getPropertiesMap() {
        if (m_propertiesMap == null)
            m_propertiesMap = new HashMap<String, Object>();
        return m_propertiesMap;
    }

    public void setCollectionName(String collectionName) {
        getPropertiesMap().put(COLLECTION_NAME_PROP, collectionName);
    }

    public String getCollectionName() {
        return getStringPropOrEmptyValue(COLLECTION_NAME_PROP);
    }

    public void setOperationType(String opTypeLiteral) {
        setOperationType(CommandOperationType.getType(opTypeLiteral.trim()));
    }

    public void setOperationType(CommandOperationType opType) {
        getPropertiesMap().put(QUERY_OPERATION_TYPE_PROP, opType);
    }

    public CommandOperationType getOperationType() {
        CommandOperationType value = getOperationTypeImpl(getPropertiesMap());
        if (value == null)
            value = getOperationTypeImpl(sm_defaultPropsMap); // get default value instead
        return value;
    }

    private static CommandOperationType getOperationTypeImpl(Map<String, Object> propMap) {
        Object propValue = propMap.get(QUERY_OPERATION_TYPE_PROP);
        if (propValue instanceof CommandOperationType)
            return (CommandOperationType) propValue;
        if (propValue instanceof String)
            return CommandOperationType.getType(((String) propValue).trim());
        return null; // non-recognized data type
    }

    public boolean hasValidCommandOperation() {
        return hasAggregateCommand() || hasMapReduceCommand() || hasRunCommand();
    }

    public boolean hasAggregateCommand() {
        return hasValidCommand(CommandOperationType.AGGREGATE);
    }

    public boolean hasMapReduceCommand() {
        return hasValidCommand(CommandOperationType.MAP_REDUCE);
    }

    public boolean hasRunCommand() {
        return hasValidCommand(CommandOperationType.RUN_DB_COMMAND);
    }

    private boolean hasValidCommand(CommandOperationType opType) {
        if (getOperationType() != opType)
            return false;
        return !getOperationExpression().isEmpty();
    }

    public void setOperationExpression(String opExpr) {
        getPropertiesMap().put(QUERY_OPERATION_EXPR_PROP, opExpr);
    }

    public String getOperationExpression() {
        return getStringPropOrEmptyValue(QUERY_OPERATION_EXPR_PROP);
    }

    DBObject getOperationExprAsParsedObject(boolean addArrayMarkers) throws OdaException {
        String cmdOpExprText = getOperationExpression();
        if (cmdOpExprText.isEmpty())
            return null;

        // add array markers for operation pipeline
        if (addArrayMarkers)
            cmdOpExprText = addArrayMarkers(cmdOpExprText);

        return parseExprToDBObject(cmdOpExprText);
    }

    static String addArrayMarkers(String expr) {
        if (!expr.startsWith(ARRAY_BEGIN_MARKER) && !expr.endsWith(ARRAY_END_MARKER)) {
            StringBuilder strBldr = new StringBuilder(expr.length() + 2);
            strBldr.append(ARRAY_BEGIN_MARKER).append(expr).append(ARRAY_END_MARKER);
            return strBldr.toString();
        }

        return expr;
    }

    static DBObject getFirstObjectSet(DBObject exprObj) {
        if (exprObj == null)
            return null;

        DBObject firstObj = null;
        if (exprObj instanceof BasicDBList) {
            BasicDBList objList = (BasicDBList) exprObj;
            if (objList.size() >= 1) {
                Object value = objList.get(0);
                if (value instanceof DBObject)
                    firstObj = (DBObject) value;
                else // log and ignore
                    logInvalidTagValue(value);
            }
        } else
            firstObj = exprObj;

        return firstObj;
    }

    static DBObject[] getSecondaryObjectSets(DBObject exprObj) {
        if (!(exprObj instanceof BasicDBList))
            return null; // no secondary element(s)

        BasicDBList objList = (BasicDBList) exprObj;
        if (objList.size() <= 1)
            return null;

        // return the second and remaining DBObject(s) from the list
        List<DBObject> secondaryObjList = new ArrayList<DBObject>(objList.size() - 1);
        for (int i = 1; i < objList.size(); i++) {
            Object value = objList.get(i);
            if (value instanceof DBObject)
                secondaryObjList.add((DBObject) value);
            else // ignore elements that are not DBObject
                logInvalidTagValue(value);
        }

        if (secondaryObjList.isEmpty())
            return null;
        return (DBObject[]) secondaryObjList.toArray(new DBObject[secondaryObjList.size()]);
    }

    private static final void logInvalidTagValue(Object tagValue) {
        getLogger().info(Messages.bind(
                "Ignoring the tag value ({0}).  A Read Preference Tag Set must be specified as a document.", //$NON-NLS-1$
                tagValue));
    }

    public void setSelectedFields(List<FieldMetaData> selectedFields) {
        List<String> fieldNames = new ArrayList<String>(selectedFields.size());
        for (FieldMetaData fieldMd : selectedFields) {
            fieldNames.add(fieldMd.getFullName());
        }

        setSelectedFieldNames(fieldNames);
    }

    private void setSelectedFieldNames(List<String> selectedFieldList) {
        getPropertiesMap().put(SELECTED_FIELDS_PROP, selectedFieldList);
    }

    /**
     * Returns the full names of selected fields in an ordered list.
     */
    @SuppressWarnings("unchecked")
    public List<String> getSelectedFieldNames() {
        Object propValue = getPropertiesMap().get(SELECTED_FIELDS_PROP);
        if (propValue instanceof List<?>)
            return (List<String>) propValue;

        if (propValue instanceof String) {
            DBObject projectionKeys = null;
            try {
                projectionKeys = parseExprToDBObject((String) propValue);
            } catch (OdaException ex) {
                // log and ignore
                getLogger().log(Level.INFO,
                        Messages.bind("Ignoring invalid Selected Fields expression: {0}", propValue), ex); //$NON-NLS-1$
                return Collections.emptyList();
            }

            // extract the included fields to a list of selected fields
            List<String> selectedFieldNames = new ArrayList<String>(projectionKeys.keySet().size());
            for (String key : projectionKeys.keySet()) {
                Object value = projectionKeys.get(key);
                if (value instanceof Integer) {
                    if ((Integer) value == 0) // field is being excluded
                        continue;
                } else if (value instanceof Boolean && (Boolean) value == Boolean.FALSE) // field is being excluded
                    continue;

                selectedFieldNames.add(key);
            }
            return selectedFieldNames;
        }

        return Collections.emptyList(); // null or non-recognized data type
    }

    DBObject getSelectedFieldsAsProjectionKeys() throws OdaException {
        Object propValue = getPropertiesMap().get(SELECTED_FIELDS_PROP);
        if (propValue instanceof List<?>) {
            BasicDBObject keys = new BasicDBObject();
            @SuppressWarnings("unchecked")
            List<String> fieldNames = (List<String>) propValue;
            for (String field : fieldNames) {
                keys.append(field, 1);
            }

            // explicitly exclude docId field if not in projected list
            if (!keys.containsField(DOC_ID_FIELD_NAME))
                keys.append(DOC_ID_FIELD_NAME, 0);
            return keys;
        }

        if (propValue instanceof String) {
            // user-defined projection expression
            return parseExprToDBObject((String) propValue);
        }

        // non-recognized data type; log and ignore
        if (propValue != null)
            getLogger().log(Level.INFO,
                    Messages.bind("Unexpected data type ({0}) in Selected Fields property value.", //$NON-NLS-1$
                            propValue.getClass().getName()));

        return new BasicDBObject();
    }

    public void setQueryReadPreference(String readPrefLiteral) {
        ReadPreference readPref = toReadPreference(readPrefLiteral);
        setQueryReadPreference(readPref);
    }

    public void setQueryReadPreference(ReadPreference readPref) {
        getPropertiesMap().put(QUERY_READ_PREF_PROP, readPref);
    }

    public ReadPreference getQueryReadPreference() {
        Object propValue = getPropertiesMap().get(QUERY_READ_PREF_PROP);
        if (propValue instanceof String)
            propValue = toReadPreference(((String) propValue));
        if (propValue instanceof ReadPreference) {
            // return explicit read preference mode to prevent confusion
            return (ReadPreference) propValue;
        }
        return ReadPreferenceChoice.DEFAULT_PREFERENCE; // non-recognized data type; use default
    }

    private static ReadPreference toReadPreference(String readPrefChoiceLiteral) {
        return MongoDBDriver.ReadPreferenceChoice.getMongoReadPreference(readPrefChoiceLiteral);
    }

    public void setQueryReadPreferenceTags(String tagsExpr) {
        getPropertiesMap().put(QUERY_READ_PREF_TAGS_PROP, tagsExpr);
    }

    public String getQueryReadPreferenceTags() {
        return getStringPropOrEmptyValue(QUERY_READ_PREF_TAGS_PROP);
    }

    DBObject getReadPreferenceTagsAsParsedObject() {
        String tagsExpr = getQueryReadPreferenceTags();
        if (tagsExpr.isEmpty())
            return null;
        try {
            return parseExprToDBObject(tagsExpr);
        } catch (OdaException ex) {
            // log and ignore
            getLogger().log(Level.INFO,
                    Messages.bind("Ignoring invalid Read Preference Tags expression: {0}", tagsExpr), ex); //$NON-NLS-1$
        }
        return null;
    }

    ReadPreference getTaggableReadPreference() {
        ReadPreference readPref = getQueryReadPreference();
        if (readPref == ReadPreference.primary())
            return readPref; // primary read preference mode does not apply tags

        DBObject tagSets = getReadPreferenceTagsAsParsedObject();
        DBObject firstTagSet = getFirstObjectSet(tagSets);
        if (firstTagSet == null)
            return readPref; // no tags in read preference

        DBObject[] remainingTagSets = getSecondaryObjectSets(tagSets);

        try {
            return (remainingTagSets != null)
                    ? ReadPreference.valueOf(readPref.getName(), firstTagSet, remainingTagSets)
                    : ReadPreference.valueOf(readPref.getName(), firstTagSet);
        } catch (RuntimeException ex) {
            // log and ignore tags
            getLogger().info(ex.getLocalizedMessage());
        }
        return readPref;
    }

    public void setRuntimeMetaDataSearchLimit(Integer searchLimit) {
        getPropertiesMap().put(RT_META_DATA_SEARCH_LIMIT, searchLimit);
    }

    public Integer getRuntimeMetaDataSearchLimit() {
        return getIntPropOrDefaultValue(RT_META_DATA_SEARCH_LIMIT);
    }

    public boolean hasRuntimeMetaDataSearchLimit() {
        return hasIntPropertyValue(getPropertiesMap(), RT_META_DATA_SEARCH_LIMIT);
    }

    public void setBatchSize(Integer batchSize) {
        getPropertiesMap().put(CURSOR_BATCH_SIZE_PROP, batchSize);
    }

    public Integer getBatchSize() {
        return getIntPropOrDefaultValue(CURSOR_BATCH_SIZE_PROP);
    }

    public boolean hasBatchSize() {
        return hasIntPropertyValue(getPropertiesMap(), CURSOR_BATCH_SIZE_PROP);
    }

    public void setNumDocsToSkip(Integer numDocsToSkip) {
        getPropertiesMap().put(SKIP_NUM_DOCS_PROP, numDocsToSkip);
    }

    public Integer getNumDocsToSkip() {
        return getIntPropOrDefaultValue(SKIP_NUM_DOCS_PROP);
    }

    public boolean hasNumDocsToSkip() {
        return hasIntPropertyValue(getPropertiesMap(), SKIP_NUM_DOCS_PROP);
    }

    public void setAutoFlattening(Boolean isAutoFlattening) {
        getPropertiesMap().put(AUTO_FLATTENING_PROP, isAutoFlattening);
    }

    public boolean isAutoFlattening() {
        return getBooleanPropOrDefaultValue(AUTO_FLATTENING_PROP);
    }

    public void setIndexHints(String indexHints) {
        getPropertiesMap().put(INDEX_HINTS_PROP, indexHints);
    }

    public String getIndexHints() {
        return getStringPropOrEmptyValue(INDEX_HINTS_PROP);
    }

    DBObject getIndexHintsAsParsedObject() {
        String hintValue = getIndexHints();
        if (hintValue.isEmpty())
            return null;
        try {
            return parseExprToDBObject(hintValue);
        } catch (OdaException ex) {
            // log and ignore
            getLogger().log(Level.INFO, Messages.bind("Ignoring invalid Index Hint expression: {0}", hintValue), //$NON-NLS-1$
                    ex);
        }
        return null;
    }

    public void setNoTimeOut(Boolean hasNoTimeOut) {
        getPropertiesMap().put(NO_TIMEOUT_PROP, hasNoTimeOut);
    }

    public boolean hasNoTimeOut() {
        return getBooleanPropOrDefaultValue(NO_TIMEOUT_PROP);
    }

    public void setPartialResultsOk(Boolean isPartialResultsOk) {
        getPropertiesMap().put(PARTIAL_RESULTS_PROP, isPartialResultsOk);
    }

    public boolean isPartialResultsOk() {
        return getBooleanPropOrDefaultValue(PARTIAL_RESULTS_PROP);
    }

    public void setFindQueryExpr(String findQueryExpr) {
        getPropertiesMap().put(FIND_QUERY_EXPR_PROP, findQueryExpr);
    }

    public String getFindQueryExpr() {
        return getStringPropOrEmptyValue(FIND_QUERY_EXPR_PROP);
    }

    DBObject getFindQueryExprAsParsedObject() throws OdaException {
        String queryExprText = getFindQueryExpr();
        if (queryExprText.isEmpty())
            return null;

        return parseExprToDBObject(queryExprText);
    }

    public void setSortExpr(String sortExpr) {
        getPropertiesMap().put(SORT_EXPR_PROP, sortExpr);
    }

    public String getSortExpr() {
        return getStringPropOrEmptyValue(SORT_EXPR_PROP);
    }

    DBObject getSortExprAsParsedObject() throws OdaException {
        String sortExprText = getSortExpr();
        if (sortExprText.isEmpty())
            return null;

        return parseExprToDBObject(sortExprText);
    }

    // Utility methods

    private String getStringPropOrEmptyValue(String propName) {
        String value = getStringPropertyValue(getPropertiesMap(), propName);
        return value != null ? value : DriverUtil.EMPTY_STRING;
    }

    private static String getStringPropertyValue(Map<String, Object> propertiesMap, String propertyName) {
        Object propValue = propertiesMap.get(propertyName);
        return propValue instanceof String ? ((String) propValue).trim() : null;

    }

    private Boolean getBooleanPropOrDefaultValue(String propName) {
        Boolean value = getBooleanPropertyValue(getPropertiesMap(), propName);
        if (value == null) // no property value defined, get default value instead
            value = getBooleanPropertyValue(sm_defaultPropsMap, propName);
        return value;
    }

    private static Boolean getBooleanPropertyValue(Map<String, Object> propertiesMap, String propertyName) {
        Object propValue = propertiesMap.get(propertyName);
        if (propValue instanceof Boolean)
            return (Boolean) propValue;
        if (propValue instanceof String && !((String) propValue).isEmpty())
            return Boolean.valueOf((String) propValue);
        return null;
    }

    private Integer getIntPropOrDefaultValue(String propName) {
        Integer value = getIntPropertyValue(getPropertiesMap(), propName);
        if (value == null) // no property value defined, get default value instead
            value = getIntPropertyValue(sm_defaultPropsMap, propName);
        return value;
    }

    private static Integer getIntPropertyValue(Map<String, Object> propertiesMap, String propertyName) {
        Object propValue = propertiesMap.get(propertyName);
        try {
            if (propValue instanceof Integer)
                return (Integer) propValue;
            if (propValue instanceof String && !((String) propValue).isEmpty())
                return Integer.valueOf((String) propValue);
        } catch (NumberFormatException ex) {
            // log and ignore
            getLogger().log(Level.INFO, Messages.bind("Invalid integer value ({0}) found in the {1} property.", //$NON-NLS-1$
                    propValue, propertyName), ex);
        }
        return null;
    }

    private static boolean hasIntPropertyValue(Map<String, Object> propertiesMap, String propertyName) {
        return getIntPropertyValue(propertiesMap, propertyName) != null;
    }

    private static final DBObject parseExprToDBObject(String jsonExpr) throws OdaException {
        return DriverUtil.parseExprToDBObject(jsonExpr);
    }

    private static final Logger getLogger() {
        return DriverUtil.getLogger();
    }

}