com.wavemaker.runtime.ws.salesforce.SalesforceSupport.java Source code

Java tutorial

Introduction

Here is the source code for com.wavemaker.runtime.ws.salesforce.SalesforceSupport.java

Source

/*
 * Copyright (C) 2012-2013 CloudJee, Inc. All rights reserved.
 *
 *  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.wavemaker.runtime.ws.salesforce;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.apache.commons.collections.map.MultiValueMap;

import com.wavemaker.common.WMRuntimeException;
import com.wavemaker.common.util.CastUtils;
import com.wavemaker.common.util.Tuple;
import com.wavemaker.common.util.TypeConversionUtils;
import com.wavemaker.json.JSONArray;
import com.wavemaker.json.JSONObject;
import com.wavemaker.runtime.RuntimeAccess;
import com.wavemaker.runtime.WMAppContext;
import com.wavemaker.runtime.data.util.QueryHandler;
import com.wavemaker.runtime.service.PagingOptions;
import com.wavemaker.runtime.ws.salesforce.gen.DescribeGlobal;
import com.wavemaker.runtime.ws.salesforce.gen.DescribeGlobalResponse;
import com.wavemaker.runtime.ws.salesforce.gen.DescribeGlobalSObjectResultType;
import com.wavemaker.runtime.ws.salesforce.gen.DescribeSObject;
import com.wavemaker.runtime.ws.salesforce.gen.DescribeSObjectResponse;
import com.wavemaker.runtime.ws.salesforce.gen.FieldType;
import com.wavemaker.runtime.ws.salesforce.gen.PicklistEntryType;
import com.wavemaker.runtime.ws.salesforce.gen.QueryResultType;
import com.wavemaker.runtime.ws.salesforce.gen.SObjectType;
import com.wavemaker.runtime.ws.salesforce.gen.SessionHeader;
import com.wavemaker.runtime.ws.salesforce.gen.SforceService;

/**
 * Helper class for Salesforce.
 * 
 * @author Seung Lee
 */
public class SalesforceSupport {

    private static final String[] SYS_MAINT_FIELDS = { "CreatedBy", "CreatedById", "CreatedDate", "LastModifiedBy",
            "LastModifiedById", "LastModifiedDate", "SystemModstamp", "IsDeleted", "FieldsToNulls" };

    private static final String[] OPTIONAL_FIELDS = { "ActivityHistories", "Attachments", "Events",
            "FeedSubscriptionsForEntity", "Histories", "LastActivityDate", "Notes", "NotesAndAttachments",
            "OpenActivities", "Owner", "OwnerId", "ProcessInstances", "ProcessSteps", "Tasks" };

    private Map<String, List<FieldType>> fieldsMap = null;

    private final LoginService loginSvcBean = (LoginService) RuntimeAccess.getInstance()
            .getSpringBean("sfLoginService");

    private int lastQryId = 0;

    private final Map<Integer, SingleQuery> queryMap = new TreeMap<Integer, SingleQuery>(); // id, single query obj

    private final MultiValueMap parentQueryMap = new MultiValueMap(); // parent id, single query obj

    private MultiValueMap resultRows; // key = row (List<Object>), value = Object(Rec ID + SObject)

    private Map<List<Object>, JSONObject> resultJsonObjs;

    Long recId;

    public JSONObject getPickLists(String objClassName, JSONObject fieldAndValuePairs) throws Exception {
        JSONObject carrier = new JSONObject();
        String objName = getSalesforceObjName(objClassName);

        Set<Map.Entry<String, Object>> entries = fieldAndValuePairs.entrySet();

        for (Map.Entry e : entries) {
            String fieldName = (String) e.getKey();
            if (carrier.containsKey(fieldName)) {
                continue;
            }

            FieldType field = getField(objName, fieldName);

            // for boolean, we don't need to get the pick list. boolean is only needed as controller.
            if (field.getType().value().equals("boolean")) {
                continue;
            }

            JSONObject result = getPickList(objName, field, fieldAndValuePairs, carrier);
            carrier.put(fieldName, result);
        }

        return carrier;
    }

    public JSONObject getPickList(String objName, FieldType currentField, JSONObject fieldAndValuePairs,
            JSONObject carrier) throws Exception {

        List<FieldType> fields = getFields(objName);

        Map<String, FieldType> fieldMap = new HashMap<String, FieldType>();

        for (FieldType fld : fields) {
            fieldMap.put(fld.getName(), fld);
        }

        List<FieldType> controlPath = getControlPath(currentField, fieldMap, null);

        // if no controller fields are found, get the pick list for the current field and return.
        if (controlPath == null) {
            List<PicklistEntryType> picklistValues = currentField.getPicklistValues();
            if (picklistValues != null && picklistValues.size() > 0) {
                return prepareJSONObject(picklistValues, fieldAndValuePairs, currentField);
            } else {
                return null;
            }
        }

        Collections.reverse(controlPath);

        if (carrier == null) {
            carrier = new JSONObject();
        }

        JSONObject plist = null;
        for (int i = 0; i < controlPath.size(); i++) {
            FieldType fld = controlPath.get(i);

            if (fld.getType().value().equals("boolean")) {
                continue;
            }
            if (carrier.containsKey(downShiftFirstChar(fld.getName()))) {
                continue;
            }

            FieldType controller;

            // the first field does not have any controller
            controller = i == 0 ? null : controlPath.get(i - 1);
            plist = getRestrictedPickList(objName, fld, controller, fieldAndValuePairs);

            // do not add the last entry to carrier bcz it will be added in the caller (getPiclLists)
            if (i < controlPath.size() - 1) {
                carrier.put(downShiftFirstChar(currentField.getName()), plist);
            }
        }

        return plist;
    }

    private void setupFields(String objName) throws Exception {
        DescribeSObject parameters = new DescribeSObject();
        parameters.setSObjectType(getSalesforceObjName(objName));
        this.loginSvcBean.logIn("sammysm@wavemaker.com", "Silver77Surfer");
        SessionHeader sessionHeader = LoginService.getSessionHeader();
        SforceService service = LoginService.getSforceService();

        DescribeSObjectResponse dresponse = service.describeSObject(parameters, sessionHeader, null, null, null);
        List<FieldType> flds = dresponse.getResult().getFields();
        List<FieldType> fields = null;

        if (flds != null && flds.size() > 0) {
            fields = new ArrayList<FieldType>();
            for (FieldType fld : flds) {
                if (isSystemMaintained(fld.getName()) || isOptional(fld.getName())) {
                    continue;
                }
                fields.add(fld);
            }
        }

        if (this.fieldsMap == null) {
            this.fieldsMap = new HashMap<String, List<FieldType>>();
        }
        this.fieldsMap.put(objName, fields);
    }

    private List<FieldType> getFields(String objName) throws Exception {
        List<FieldType> fields;
        if (this.fieldsMap == null) {
            setupFields(objName);
            fields = this.fieldsMap.get(objName);
        } else {
            fields = this.fieldsMap.get(objName);
            if (fields == null) {
                setupFields(objName);
                fields = this.fieldsMap.get(objName);
            }
        }

        return fields;
    }

    public static String getSalesforceObjName(String className) {
        String name = className;
        int pos = name.lastIndexOf(".");
        if (pos > 0) {
            name = name.substring(pos + 1);
        }

        pos = name.lastIndexOf("Type");
        if (pos > 0) {
            name = name.substring(0, pos);
            if (name.substring(pos - 1).equals("C")) { // custom object
                name = name.substring(0, pos - 1) + "__c";
            }
        }

        return name;
    }

    public List<List<Object>> runQuery(Map<String, Class<?>> types, Object... input) {

        executeQuery(types, "executeSforceQueryFromEditor", input);

        List<List<Object>> rtn = new ArrayList<List<Object>>();
        TreeMap<Long, List<Object>> tmap = new TreeMap<Long, List<Object>>();
        Set<Map.Entry<List<Object>, List<Tuple.Two<Long, Object>>>> entries = CastUtils
                .cast(this.resultRows.entrySet());
        for (Map.Entry<List<Object>, List<Tuple.Two<Long, Object>>> entry : entries) {
            List<Tuple.Two<Long, Object>> list = entry.getValue();
            tmap.put(list.get(0).v1, entry.getKey());
        }

        Set<Map.Entry<Long, List<Object>>> tmapEntries = tmap.entrySet();
        for (Map.Entry<Long, List<Object>> entry : tmapEntries) {
            rtn.add(entry.getValue());
        }

        return rtn;
    }

    public List<JSONObject> runNamedQuery(Map<String, Class<?>> types, Object... input) {

        executeQuery(types, "executeSforceQuery", input);

        List<JSONObject> rtn = new ArrayList<JSONObject>();
        TreeMap<Long, List<Object>> tmap = new TreeMap<Long, List<Object>>();
        Set<Map.Entry<List<Object>, List<Tuple.Two<Long, Object>>>> entries = CastUtils
                .cast(this.resultRows.entrySet());
        for (Map.Entry<List<Object>, List<Tuple.Two<Long, Object>>> entry : entries) {
            List<Tuple.Two<Long, Object>> list = entry.getValue();
            tmap.put(list.get(0).v1, entry.getKey());
        }

        Set<Map.Entry<Long, List<Object>>> tmapEntries = tmap.entrySet();
        for (Map.Entry<Long, List<Object>> entry : tmapEntries) {
            rtn.add(this.resultJsonObjs.get(entry.getValue()));
        }

        return rtn;
    }

    private void executeQuery(Map<String, Class<?>> types, String merhodName, Object... input) {

        try {
            String qry = buildQueryString(types, input);

            this.lastQryId = 0;

            splitQueries(0, qry, 0);

            int len = input.length;
            PagingOptions po = (PagingOptions) input[len - 1];
            Long psize = po.getMaxResults();
            long firstrec = po.getFirstResult();

            Class cls;
            List<SObjectType> sobjs;
            try {
                cls = Class.forName("com.sforce.SalesforceCalls");
                Object obj = cls.newInstance();
                // String mn = "executeSforceQuery";
                Method method = cls.getMethod(merhodName,
                        new Class[] { java.lang.String.class, java.util.Map.class, java.lang.Object[].class });
                Object[] args = { qry, types, input };
                sobjs = (List<SObjectType>) method.invoke(obj, args);
            } catch (Exception e) {
                e.printStackTrace();
                return;
            }

            long lastrec;
            if (psize != null) {
                lastrec = psize > sobjs.size() - firstrec ? sobjs.size() : psize + firstrec;
            } else {
                lastrec = sobjs.size();
            }

            List<Object> row;
            JSONObject jsonVal;
            this.resultRows = new MultiValueMap();
            this.resultJsonObjs = new HashMap<List<Object>, JSONObject>();
            // resultRowJsonObjMap = new HashMap();

            this.recId = 0L;

            for (long k = firstrec; k < lastrec; k++) {
                int ik = new Long(k).intValue();
                Object sobj = sobjs.get(ik);

                SingleQuery sq = this.queryMap.get(1);
                sq.thisObjects.add(sobj);
                // row = getFieldValues(sq, sobj);
                Tuple.Two<List<Object>, JSONObject> tval = getFieldValues(sq, sobj);
                row = tval.v1;
                jsonVal = tval.v2;
                this.recId++;
                Tuple.Two<Long, Object> t = new Tuple.Two<Long, Object>(this.recId, sobj);
                this.resultRows.put(row, t);
                this.resultJsonObjs.put(row, jsonVal);
                setRowsForSubQueries(sq, sobj);
            }
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }

    }

    // pobj: the parent object
    // psq: the parent query object
    private void setRowsForSubQueries(SingleQuery psq, Object pobj) throws Exception {
        List<SingleQuery> subQueries = getSubqueries(psq);

        if (subQueries != null && subQueries.size() > 0) {
            for (SingleQuery subq : subQueries) {
                setQueryResult(subq, pobj); // set rows and thisObjects
            }

            setSiblingQueryResult(pobj, subQueries); // modify rows based on rows of the parent and other siblings

            for (SingleQuery subq : subQueries) {
                for (Object obj : subq.thisObjects) {
                    setRowsForSubQueries(subq, obj);
                }
            }
        }
    }

    private List<List<Object>> getRowsForSforceObject(Object obj) {
        List<List<Object>> rtn = new ArrayList<List<Object>>();
        Set<Map.Entry<List<Object>, List<Tuple.Two<Long, Object>>>> entries = CastUtils
                .cast(this.resultRows.entrySet());
        for (Map.Entry<List<Object>, List<Tuple.Two<Long, Object>>> entry : entries) {
            List<Tuple.Two<Long, Object>> list = entry.getValue();
            for (Tuple.Two<Long, Object> elem : list) {
                if (obj.equals(elem.v2)) {
                    rtn.add(entry.getKey());
                    break;
                }
            }
        }

        return rtn;
    }

    private void setSiblingQueryResult(Object pobj, List<SingleQuery> siblings) {

        List<List<Object>> parentRows = getRowsForSforceObject(pobj);

        List<List<Object>> temprows = new ArrayList<List<Object>>();

        for (List<Object> parentRow : parentRows) {
            List<List<Object>> rowsArray = new ArrayList<List<Object>>();

            List<Object> temprow = new ArrayList<Object>();
            for (Object val : parentRow) {
                temprow.add(val);
            }

            temprows.add(temprow);

            int startIndx = 0, indx = 0;

            for (SingleQuery qry : siblings) {
                startIndx = indx;
                int cfp = getCumFieldPosition(qry, siblings, 0);
                for (List<Object> trow : temprows) {

                    // Don't we need to get rows only for specific object??
                    // List<List<Object>> subqRows = qry.rows.get(pobj);
                    List<Tuple.Two<List<Object>, JSONObject>> subqRows = qry.rows.get(pobj);
                    if (subqRows != null) {
                        for (Tuple.Two<List<Object>, JSONObject> subqRow : subqRows) {
                            List<Object> rec = new ArrayList<Object>();
                            for (Object ival : trow) {
                                rec.add(ival);
                            }
                            for (Object val : subqRow.v1) {
                                rec.add(cfp, val);
                            }
                            rowsArray.add(rec);
                            indx++;
                            Collection<Object> objs = CastUtils.cast(this.resultRows.getCollection(trow));
                            this.recId++;
                            for (Object obj : objs) {
                                // recId++;
                                Tuple.Two<Long, Object> t = new Tuple.Two<Long, Object>(this.recId, obj);
                                this.resultRows.put(rec, t);
                                this.resultJsonObjs.put(rec, subqRow.v2);
                            }

                            // recId++;
                            Tuple.Two<Long, Object> t = new Tuple.Two<Long, Object>(this.recId,
                                    qry.rowObject.get(subqRow));
                            this.resultRows.put(rec, t);
                            this.resultJsonObjs.put(rec, subqRow.v2);
                        }
                        this.resultRows.remove(trow);
                        this.resultJsonObjs.remove(trow);
                    }
                }
                temprows = new ArrayList<List<Object>>();
                for (int i = startIndx; i < indx; i++) {
                    temprows.add(rowsArray.get(i));
                }
            }
        }
    }

    // Get all parent position and add them up
    private int getCumFieldPosition(SingleQuery sq, List<SingleQuery> siblings, int pos) {
        pos = traverseParentFieldPosition(sq, pos);

        // once the cum position is calculated for all parents, calculate the cum position for all preceding siblings.
        for (SingleQuery qry : siblings) {
            if (qry.equals(sq)) {
                break;
            }
            pos = pos + qry.fieldList.size() - 1;
        }

        return pos;
    }

    private int traverseParentFieldPosition(SingleQuery sq, int pos) {
        pos = pos + sq.fieldPosition;
        SingleQuery psq = this.queryMap.get(sq.parentQryId);
        if (psq != null) {
            pos = traverseParentFieldPosition(psq, pos);
        }

        return pos;
    }

    private void setQueryResult(SingleQuery sq, Object pobj) throws Exception {

        SingleQuery parentsq = this.queryMap.get(sq.parentQryId);
        Class cls = getSforceObjectClass(parentsq.objectName);

        String myObjName = getAPISforceObjectShortName(sq.objectName);
        String methodName = "get" + myObjName.substring(0, 1).toUpperCase() + myObjName.substring(1);
        Method method = cls.getMethod(methodName, new Class[] {});
        QueryResultType qryResult = (QueryResultType) method.invoke(pobj, new Object[] {});
        if (qryResult != null) {
            List<SObjectType> rsobjs = qryResult.getRecords();

            // List<List<Object>> rows = new ArrayList<List<Object>>();
            List<Tuple.Two<List<Object>, JSONObject>> rows = new ArrayList<Tuple.Two<List<Object>, JSONObject>>();
            for (SObjectType rsobj : rsobjs) {
                sq.thisObjects.add(rsobj);
                Tuple.Two<List<Object>, JSONObject> tval = getFieldValues(sq, rsobj);
                // rows.add(getFieldValues(sq, rsobj));
                rows.add(tval);
            }
            sq.rows.put(pobj, rows);
        }
    }

    private List<SingleQuery> getSubqueries(SingleQuery sq) {
        List list = (List) this.parentQueryMap.get(sq.qryId);
        return CastUtils.cast(list);
    }

    private String getSforceAPIObjectName(String objName) {

        int indx = objName.lastIndexOf(".");
        if (indx > 0) {
            objName = objName.substring(indx + 1);
        }

        objName = objName.replace("__c", "C");
        objName = objName.replace("__r", "R");

        // FeedTrackedChanges and FeedComments are exceptions
        if (objName.equals("FeedTrackedChanges")) {
            objName = "FeedTrackedChange";
        } else if (objName.equals("FeedComments")) {
            objName = "FeedComment";
        }

        return objName;
    }

    /*
     * private String getSforceObjectClassname(String objName) { objName = getSforceAPIObjectName(objName);
     * 
     * return "com.sforce.soap.enterprise.salesforceservice." + objName + "Type"; }
     */

    private Class getSforceObjectClass(String objName) throws Exception {
        String apiObjName = getSforceAPIObjectName(objName);
        Class cls;

        String className = "com.sforce.soap.enterprise.salesforceservice." + apiObjName + "Type";

        try {
            cls = Class.forName(className);
        } catch (ClassNotFoundException ex) {
            this.loginSvcBean.logIn("sammysm@wavemaker.com", "Silver77Surfer");
            SessionHeader sessionHeader = LoginService.getSessionHeader();
            SforceService service = LoginService.getSforceService();
            DescribeGlobal parameters = new DescribeGlobal();
            DescribeGlobalResponse dresponse = service.describeGlobal(parameters, sessionHeader, null, null);
            List<DescribeGlobalSObjectResultType> sObjects = dresponse.getResult().getSobjects();

            boolean found = false;
            /*
             * int len = objName.length(); if (len > 3 && (objName.substring(len-3).equalsIgnoreCase("__c") ||
             * objName.substring(len-3).equalsIgnoreCase("__r"))) { objName = objName.substring(0, len-3); }
             */
            objName = getAPISforceObjectShortName(objName);

            for (DescribeGlobalSObjectResultType obj : sObjects) {
                if (objName.equals(obj.getLabelPlural())) {
                    found = true;
                    // apiObjName = obj.getName();
                    apiObjName = getAPISforceObjectShortName(obj.getName());
                    /*
                     * if (len > 3 && (apiObjName.substring(len-3).equalsIgnoreCase("__c") ||
                     * apiObjName.substring(len-3).equalsIgnoreCase("__r"))) { apiObjName = apiObjName.substring(0,
                     * len-3); }
                     */
                }
            }

            if (!found) {
                throw new ClassNotFoundException();
            }

            /*
             * len = apiObjName.length(); if (len > 3 && (apiObjName.substring(len-3).equalsIgnoreCase("__c"))) {
             * apiObjName = apiObjName.substring(0, len-3) + "C"; }
             */

            className = "com.sforce.soap.enterprise.salesforceservice." + apiObjName + "Type";

            cls = Class.forName(className);
        }

        return cls;
    }

    // private List<Object> getFieldValues(SingleQuery sq, Object sobj) throws Exception {
    private Tuple.Two<List<Object>, JSONObject> getFieldValues(SingleQuery sq, Object sobj) throws Exception {
        Class cls = getSforceObjectClass(sq.objectName);
        List<Object> values = new ArrayList<Object>();
        JSONObject jsonValues = new JSONObject();
        for (String fld : sq.fieldList) {
            String apifld = getAPIFieldName(fld);
            String methodName = "get" + apifld.substring(0, 1).toUpperCase() + apifld.substring(1);
            Method method = cls.getMethod(methodName, new Class[] {});
            Object val = method.invoke(sobj, new Object[] {});
            if (val != null) {
                values.add(val.toString());
                jsonValues.put(fld, val);
            } else {
                values.add(null);
                jsonValues.put(fld, null);
            }
        }

        if (sq.relFieldList != null && sq.relFieldList.size() > 0) {
            Set<Map.Entry<String, List<String>>> entries = sq.relFieldList.entrySet();
            for (Map.Entry<String, List<String>> fldEntry : entries) {
                String objName = getAPISforceObjectShortName(fldEntry.getKey());
                String methodName = "get" + objName.substring(0, 1).toUpperCase() + objName.substring(1);
                Method method = cls.getMethod(methodName, new Class[] {});
                Object obj = method.invoke(sobj, new Object[] {});

                List<String> fldList = fldEntry.getValue();
                if (obj != null) {
                    Class subcls = obj.getClass();
                    for (String fld : fldList) {
                        String apifld = getAPIFieldName(fld);
                        methodName = "get" + apifld.substring(0, 1).toUpperCase() + apifld.substring(1);
                        method = subcls.getMethod(methodName, new Class[] {});
                        Object val = method.invoke(obj, new Object[] {});
                        if (val != null) {
                            values.add(val.toString());
                            jsonValues.put(fld, val);
                        } else {
                            values.add(null);
                            jsonValues.put(fld, val);
                        }
                    }
                } else { // Even though obj is null, we need to add null
                    for (String fld : fldList) {
                        values.add(null);
                        jsonValues.put(fld, null);
                    }
                }
            }
        }

        sq.rowObject.put(values, sobj);

        Tuple.Two<List<Object>, JSONObject> rtn = new Tuple.Two<List<Object>, JSONObject>(values, jsonValues);

        // return values;
        return rtn;
    }

    private void splitQueries(int parentId, String qry, int pfnum) {

        Tuple.Two<Integer, List<Tuple.Two<String, Integer>>> iqueries = getInitSubQueries(parentId, qry, pfnum);

        int pid = iqueries.v1;
        List<Tuple.Two<String, Integer>> subqueries = iqueries.v2;

        for (Tuple.Two<String, Integer> subqry : subqueries) {
            splitQueries(pid, subqry.v1, subqry.v2); // recursive call
        }
    }

    private Tuple.Two<Integer, List<Tuple.Two<String, Integer>>> getInitSubQueries(int parentId, String qry,
            int pfnum) {
        List<String> words = QueryHandler.parseSQL(qry);
        List<Tuple.Two<String, Integer>> qryList = new ArrayList<Tuple.Two<String, Integer>>();
        List<String> fieldList = new ArrayList<String>();
        MultiValueMap relFieldList = new MultiValueMap();
        this.lastQryId = this.lastQryId + 1;
        String objName = null;
        int depth = 0;
        int remainingOpens = 0;
        int counter = 0;
        int qryStartPos = 0;
        boolean inSubQuery = false;
        boolean inFrom = false;
        StringBuffer subQry = new StringBuffer();

        for (String word : words) {
            if (word.equalsIgnoreCase("select")) {
                if (!inSubQuery) {
                    if (depth == 0) {
                        depth++;
                    } else { // select statement of a subquery
                        qryStartPos = counter;
                        subQry.append(word);
                        subQry.append(" ");
                        inSubQuery = true;
                    }
                }
            } else if (word.equals(")")) {
                if (inSubQuery) {
                    if (remainingOpens == 0) {
                        Tuple.Two<String, Integer> elem = new Tuple.Two<String, Integer>(subQry.toString(),
                                qryStartPos);
                        qryList.add(elem);
                        counter++;
                        subQry.setLength(0);
                        inSubQuery = false;
                    } else {
                        remainingOpens--;
                    }
                }
            } else if (word.equals("(")) {
                if (inSubQuery) {
                    remainingOpens++;
                }
            } else if (word.equalsIgnoreCase("from")) {
                if (!inSubQuery) {
                    inFrom = true;
                } else {
                    subQry.append(word);
                    subQry.append(" ");
                }
            } else {
                if (inSubQuery) {
                    subQry.append(word);
                    subQry.append(" ");
                } else {
                    if (!QueryHandler.isDelimiter(word)) {
                        if (inFrom) {
                            objName = word;
                            inFrom = false;
                            break;
                        } else {
                            int indx = word.indexOf(".");
                            if (indx < 0) {
                                fieldList.add(word);
                            } else {
                                relFieldList.put(word.substring(0, indx), word.substring(indx + 1));
                            }
                            counter++;
                        }
                    }
                }
            }
        }

        SingleQuery sq = new SingleQuery(this.lastQryId, parentId, objName, fieldList, relFieldList, pfnum);
        this.queryMap.put(this.lastQryId, sq);
        this.parentQueryMap.put(parentId, sq);

        Tuple.Two<Integer, List<Tuple.Two<String, Integer>>> rtn = new Tuple.Two<Integer, List<Tuple.Two<String, Integer>>>(
                this.lastQryId, qryList);

        return rtn;
    }

    class SingleQuery {

        int parentQryId;

        int qryId;

        String objectName;

        MultiValueMap relFieldList; // parent object name, field name

        List<String> fieldList;

        List<Object> thisObjects = new ArrayList<Object>();

        // Map<Object, List<List<Object>>> rows = new HashMap<Object, List<List<Object>>>();
        Map<Object, List<Tuple.Two<List<Object>, JSONObject>>> rows = new HashMap<Object, List<Tuple.Two<List<Object>, JSONObject>>>();

        Map<List<Object>, Object> rowObject = new HashMap<List<Object>, Object>();

        int fieldPosition; // position of this subquery in the immediate parent field list

        private SingleQuery(int qid, int pqid, String objname, List<String> fldlist, MultiValueMap relfldlist,
                int pfnum) {
            this.qryId = qid;
            this.parentQryId = pqid;
            this.objectName = objname;
            this.relFieldList = relfldlist;
            this.fieldList = fldlist;
            this.fieldPosition = pfnum;
        }

        public List<String> getQueryFields() {
            List<String> rtn = new ArrayList<String>();
            for (String fld : this.fieldList) {
                rtn.add(fld);
            }

            Set<Map.Entry<String, List<String>>> entries = this.relFieldList.entrySet();
            for (Map.Entry<String, List<String>> entry : entries) {
                List<String> relflds = entry.getValue();
                for (String fld : relflds) {
                    rtn.add(fld);
                }
            }

            return rtn;
        }
    }

    public static String getAPIFieldName(String fieldName) {
        String rtn = fieldName.replace("__c", "C");
        rtn = rtn.replace("__C", "C");
        return rtn;
    }

    public static String getAPISforceObjectShortName(String objName) {
        int indx = objName.lastIndexOf(".");
        if (indx > 0) {
            objName = objName.substring(indx + 1);
        }

        objName = objName.replace("__c", "C");
        objName = objName.replace("__r", "R");
        objName = objName.replace("__C", "C");
        objName = objName.replace("__R", "R");
        return objName;
    }

    public static String buildQueryString(Map<String, Class<?>> types, Object... input) {
        PagingOptions po = (PagingOptions) input[input.length - 1];

        Long firstResult = po.getFirstResult();
        Long maxResult = po.getMaxResults();
        if (maxResult != null) {
            maxResult = maxResult - 1L;
            if (maxResult < 0L) {
                maxResult = 0L;
            }
        }
        List<String> orderBys = po.getOrderBy();

        String qry = (String) input[0];
        if (input.length > 2) {
            boolean isField = true;
            String field = null;
            for (int i = 1; i < input.length - 1; i++) {
                if (isField) {
                    field = (String) input[i];
                    isField = false;
                } else {
                    Class<?> type = types.get(field);
                    String val = TypeConversionUtils.getValueString(type, input[i].toString());
                    qry = qry.replace(":" + field, val);
                    isField = true;
                }
            }
        }

        if (orderBys.size() > 0) {
            boolean first = true;
            qry = qry + " order by ";
            for (String orderby : orderBys) {
                if (first) {
                    qry = qry + orderby;
                    first = false;
                } else {
                    qry = qry + ", " + orderby;
                }
            }
        }

        if (maxResult != null) {
            qry = qry + " limit " + maxResult + firstResult;
        }

        return qry;
    }

    private String downShiftFirstChar(String val) {
        val = val.substring(0, 1).toLowerCase() + val.substring(1);

        return val;
    }

    // Find the control field of the current field. Find the next control field of the control field just found.
    // Repeat this until there is no more control field found. Store control fields in an array list.
    private List<FieldType> getControlPath(FieldType field, Map<String, FieldType> fieldMap, List<FieldType> list) {
        Boolean dependent = field.getDependentPicklist();
        if (dependent == null || !dependent) {
            return list;
        }

        FieldType controller = fieldMap.get(field.getControllerName());

        if (controller == null) {
            return list;
        }

        if (list == null) {
            list = new ArrayList<FieldType>();
            list.add(field);
        }
        list.add(controller);

        return getControlPath(controller, fieldMap, list);
    }

    public static boolean isSystemMaintained(String field) {
        boolean rtn = false;
        for (String name : SYS_MAINT_FIELDS) {
            if (field.equalsIgnoreCase(name)) {
                rtn = true;
                break;
            }
        }

        return rtn;
    }

    public static boolean isOptional(String field) {
        boolean rtn = false;
        for (String name : OPTIONAL_FIELDS) {
            if (field.equalsIgnoreCase(name)) {
                rtn = true;
                break;
            }
        }

        return rtn;
    }

    private FieldType getField(String objName, String fieldName) throws Exception {
        List<FieldType> fields = getFields(objName);
        for (FieldType field : fields) {
            if (field.getName().equalsIgnoreCase(fieldName)) {
                return field;
            }
        }

        throw new Exception("Undefined field " + fieldName + " in " + objName);
    }

    private JSONObject getRestrictedPickList(String objName, FieldType currentField, FieldType controller,
            JSONObject fieldAndValuePairs) throws Exception {

        // helper class to decode a "validFor" bitset
        class Bitset {

            byte[] data;

            public Bitset(byte[] data) {
                this.data = data == null ? new byte[0] : data;
            }

            public boolean testBit(int n) {
                return (this.data[n >> 3] & 0x80 >> n % 8) != 0;
            }

            public int size() {
                return this.data.length * 8;
            }
        }

        List<PicklistEntryType> picklistValues = currentField.getPicklistValues();

        if (picklistValues == null || picklistValues.size() == 0) {
            return null;
        }

        // picklist is not dependent on a controller.

        if (controller == null) {
            return prepareJSONObject(picklistValues, fieldAndValuePairs, currentField);
        }

        // picklist is dependent on a controller.

        List<FieldType> fields = getFields(objName);

        if (fields == null) {
            return null;
        }

        List<PicklistEntryType> validValues = new ArrayList<PicklistEntryType>();

        String controlValue = (String) fieldAndValuePairs.get(downShiftFirstChar(controller.getName()));
        if (controlValue == null) {
            controlValue = getDefaultControlValue(controller);
        }

        for (PicklistEntryType picklistValue : picklistValues) {
            Bitset validFor = new Bitset(picklistValue.getValidFor());
            if ("picklist".equals(controller.getType().value())) {
                // if the controller is a picklist, list all
                // controlling values for which this entry is valid
                for (int k = 0; k < validFor.size(); k++) {
                    if (validFor.testBit(k)) {
                        // if bit k is set, this entry is valid for the
                        // for the controlling entry at index k
                        String val = controller.getPicklistValues().get(k).getValue();
                        if (controlValue.equals(val)) {
                            validValues.add(picklistValue);
                            break;
                        }

                    }
                }
            } else if ("boolean".equals(controller.getType().value())) {
                // the controller is a checkbox
                // if bit 1 is set this entry is valid if the controller is checked
                // if bit 0 is set this entry is valid if the controller is unchecked
                if (validFor.testBit(1) && "true".equals(controlValue)
                        || validFor.testBit(0) && "false".equals(controlValue)) {
                    validValues.add(picklistValue);
                }
            }
        }

        return prepareJSONObject(validValues, fieldAndValuePairs, currentField);
    }

    private String getDefaultControlValue(FieldType controller) {

        String rtn = null;

        if ("boolean".equals(controller.getType().value())) {
            rtn = "false";
        } else { // the field type is picklist
            List<PicklistEntryType> picklistValues = controller.getPicklistValues();
            for (PicklistEntryType entry : picklistValues) {
                if (entry.isDefaultValue()) {
                    rtn = entry.getValue();
                    break;
                }
            }

            // if no default value is flagged, choose the first entry as the default.
            if (rtn == null) {
                rtn = picklistValues.get(0).getValue();
            }
        }

        return rtn;
    }

    private JSONObject prepareJSONObject(List<PicklistEntryType> picklistValues, JSONObject fieldAndValuePairs,
            FieldType currentField) {
        JSONObject rtn;
        JSONArray valArray;

        rtn = new JSONObject();
        valArray = new JSONArray();
        JSONObject defaultObj = null;
        for (PicklistEntryType picklistValue : picklistValues) {
            JSONObject obj = new JSONObject();
            obj.put("name", picklistValue.getLabel());
            obj.put("dataValue", picklistValue.getValue());
            valArray.add(obj);
            if (picklistValue.isDefaultValue()) {
                defaultObj = obj;
            }
        }

        rtn.put("options", valArray);
        if (defaultObj == null) {
            defaultObj = (JSONObject) valArray.get(0);
        }
        rtn.put("default", defaultObj);

        // Because the current field's picklist values have been reset and because it can be used as a controller for
        // other picklist, "fieldAndValuePairs" must be updated with the default value of the current picklist values.
        String defaultValue = defaultObj == null ? picklistValues.get(0).getValue()
                : (String) defaultObj.get("dataValue");
        fieldAndValuePairs.put(downShiftFirstChar(currentField.getName()), defaultValue);

        return rtn;
    }

    public static Object convertToQueryReturnType(Class cls, List<JSONObject> list) {
        List<Object> rtn = new ArrayList<Object>();
        Method[] methods = cls.getMethods();
        try {
            for (JSONObject jsonObj : list) {
                Collection<Object> vals = jsonObj.values();
                int i = 0;
                Object obj = cls.newInstance();
                for (Object val : vals) {
                    Method method = getSetMethod(i, methods);
                    method.invoke(obj, val);
                    i++;
                }
                rtn.add(obj);
            }
        } catch (Exception ex) {
            throw new WMRuntimeException(ex);
        }
        return rtn;
    }

    private static Method getSetMethod(int i, Method[] methods) {
        String name = "setC" + i;
        Method rtn = null;
        for (Method method : methods) {
            String mn = method.getName();
            if (mn.equals(name)) {
                rtn = method;
                break;
            }
        }

        return rtn;
    }

    public List<String> getColumns(String qry) {
        List<String> rtn = new ArrayList<String>();

        splitQueries(0, qry, 0);

        Set<Map.Entry<Integer, SingleQuery>> entries = this.queryMap.entrySet();
        for (Map.Entry<Integer, SingleQuery> entry : entries) {
            SingleQuery sq = entry.getValue();
            rtn.addAll(sq.getQueryFields());
        }

        return rtn;
    }

    public List<String> getColumnTypes(String qry) throws Exception {
        List<String> rtn = new ArrayList<String>();

        splitQueries(0, qry, 0);

        Set<Map.Entry<Integer, SingleQuery>> entries = this.queryMap.entrySet();
        for (Map.Entry<Integer, SingleQuery> entry : entries) {
            SingleQuery sq = entry.getValue();
            setupFields(sq.objectName);
            String apiObjName = getSforceAPIObjectName(sq.objectName);

            org.json.JSONObject jsonObj = WMAppContext.getInstance().getTypesObject();
            org.json.JSONObject classesObj = jsonObj.getJSONObject("types");
            String className = "com.sforce.soap.enterprise.salesforceservice." + apiObjName + "Type";
            org.json.JSONObject classObj = classesObj.getJSONObject(className);
            org.json.JSONObject fieldsObj = classObj.getJSONObject("fields");

            for (String fld : sq.getQueryFields()) {
                fld = getAPIFieldName(fld);
                fld = fld.substring(0, 1).toLowerCase() + fld.substring(1);
                org.json.JSONObject fieldObj = fieldsObj.getJSONObject(fld);
                String type = fieldObj.getString("type");
                rtn.add(type);
            }
        }

        return rtn;
    }
}