org.openmrs.module.chica.DynamicFormAccess.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.chica.DynamicFormAccess.java

Source

/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.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://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.module.chica;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Concept;
import org.openmrs.ConceptName;
import org.openmrs.Encounter;
import org.openmrs.FieldType;
import org.openmrs.Form;
import org.openmrs.FormField;
import org.openmrs.Location;
import org.openmrs.Obs;
import org.openmrs.Patient;
import org.openmrs.api.APIException;
import org.openmrs.api.EncounterService;
import org.openmrs.api.FormService;
import org.openmrs.api.LocationService;
import org.openmrs.api.context.Context;
import org.openmrs.logic.LogicService;
import org.openmrs.logic.result.Result;
import org.openmrs.module.atd.FormWriter;
import org.openmrs.module.atd.ParameterHandler;
import org.openmrs.module.atd.datasource.FormDatasource;
import org.openmrs.module.atd.hibernateBeans.PatientATD;
import org.openmrs.module.atd.hibernateBeans.Statistics;
import org.openmrs.module.atd.service.ATDService;
import org.openmrs.module.atd.xmlBeans.Field;
import org.openmrs.module.atd.xmlBeans.Records;
import org.openmrs.module.chica.service.ChicaService;
import org.openmrs.module.chirdlutil.threadmgmt.ChirdlRunnable;
import org.openmrs.module.chirdlutil.threadmgmt.ThreadManager;
import org.openmrs.module.chirdlutil.util.ChirdlUtilConstants;
import org.openmrs.module.chirdlutil.util.IOUtil;
import org.openmrs.module.chirdlutil.util.Util;
import org.openmrs.module.chirdlutil.util.XMLUtil;
import org.openmrs.module.chirdlutilbackports.hibernateBeans.FormInstance;
import org.openmrs.module.chirdlutilbackports.hibernateBeans.PatientState;
import org.openmrs.module.chirdlutilbackports.service.ChirdlUtilBackportsService;
import org.openmrs.module.dss.DssElement;
import org.openmrs.module.dss.DssManager;
import org.openmrs.module.dss.hibernateBeans.Rule;
import org.openmrs.module.dss.hibernateBeans.RuleEntry;
import org.openmrs.module.dss.service.DssService;

/**
 * Class used to access the specific portions of form on the fly.
 * 
 * @author Steve McKee
 */
public class DynamicFormAccess {

    private Log log = LogFactory.getLog(this.getClass());

    /**
     * Default Constructor
     */
    public DynamicFormAccess() {
    }

    /**
     * Returns the results of running rules for the form fields marked as "Prioritized Merge Field". 
     * 
     * @param formId The identifier of the form.
     * @param formInstanceId The form instance Identifier of the form.
     * @param encounterId The encounter identifier associated with the form.
     * @param maxElements The maximum number of elements to populate.
     * @return List of Field objects containing the field information as well as the result information from the rules.
     */
    public List<Field> getPrioritizedElements(Integer formId, Integer formInstanceId, Integer encounterId,
            Integer maxElements) {
        List<Field> fields = new ArrayList<Field>(maxElements);
        EncounterService encounterService = Context.getEncounterService();
        Encounter encounter = encounterService.getEncounter(encounterId);
        Integer locationId = encounter.getLocation().getLocationId();

        LocationService locationService = Context.getLocationService();
        Location location = locationService.getLocation(locationId);

        FormService formService = Context.getFormService();
        Form form = formService.getForm(formId);
        String formName = form.getName();

        FormInstance formInstance = new FormInstance(locationId, formId, formInstanceId);
        PatientState patientState = org.openmrs.module.atd.util.Util
                .getProducePatientStateByFormInstanceAction(formInstance);
        Integer locationTagId = patientState.getLocationTagId();

        HashMap<String, Object> parameters = new HashMap<String, Object>();
        parameters.put("sessionId", patientState.getSessionId());
        parameters.put("formInstance", new FormInstance(locationId, formId, formInstanceId));
        parameters.put("locationTagId", locationTagId);
        parameters.put("locationId", locationId);
        parameters.put("location", location.getName());
        parameters.put("encounterId", encounterId);
        parameters.put("mode", "PRODUCE");

        Patient patient = patientState.getPatient();
        DssManager dssManager = new DssManager(patient);

        FieldType mergeType = getFieldType("Merge Field");
        FieldType priorMergeType = getFieldType("Prioritized Merge Field");
        LinkedHashMap<FormField, Object> fieldToResult = new LinkedHashMap<FormField, Object>();
        Map<String, Integer> dssMergeCounters = new HashMap<String, Integer>();
        List<FieldType> fieldTypes = new ArrayList<FieldType>();
        fieldTypes.add(mergeType);
        fieldTypes.add(priorMergeType);
        List<FormField> formFields = Context.getService(ChirdlUtilBackportsService.class).getFormFields(form,
                fieldTypes, true);
        List<Integer> fieldIds = new ArrayList<Integer>();
        for (FormField formField : formFields) {
            fieldIds.add(formField.getField().getFieldId());
        }

        Map<Integer, PatientATD> fieldIdToPatientATDMap = org.openmrs.module.atd.util.Util
                .getPatientATDs(formInstance, fieldIds);
        List<DssElement> elementList = new ArrayList<DssElement>();
        ATDService atdService = Context.getService(ATDService.class);
        Integer startPriority = getStartPriority(atdService, formInstanceId, locationId, formName);
        Map<Integer, PatientATD> waitForScanFieldIdToAtdMap = new HashMap<Integer, PatientATD>();

        String numPrompts = org.openmrs.module.chirdlutilbackports.util.Util.getFormAttributeValue(formId,
                "numPrompts", locationTagId, locationId);
        if (numPrompts == null) {
            log.error("Missing form attribute 'numPrompts' for form: " + formId);
            return fields;
        }

        int populatedElements = 0;
        int maxDssElements = 0;
        try {
            maxDssElements = Integer.parseInt(numPrompts);
        } catch (NumberFormatException e) {
        }

        for (FormField currFormField : formFields) {
            org.openmrs.Field currField = currFormField.getField();
            String defaultValue = currField.getDefaultValue();
            if (defaultValue == null) {
                continue;
            }

            FieldType currFieldType = currField.getFieldType();
            HashMap<String, Object> ruleParameters = new HashMap<String, Object>(parameters);

            //fix lazy initialization error
            //currField = formService.getField(currField.getFieldId());

            if (currFieldType.equals(priorMergeType)) {
                // check to see if the current field has already been populated
                PatientATD patientATD = fieldIdToPatientATDMap.get(currField.getFieldId());
                if (patientATD != null) {
                    if (++populatedElements == maxDssElements) {
                        break;
                    }

                    // check to see if it's been answered yet
                    List<Statistics> stats = atdService.getStatByIdAndRule(formInstanceId,
                            patientATD.getRule().getRuleId(), formName, locationId);
                    if (stats != null && stats.size() > 0) {
                        Statistics stat = stats.get(0);
                        if (stat.getScannedTimestamp() != null) {
                            // add null as a place keeper so we can keep the position for the atd statistics.
                            elementList.add(null);
                            continue;
                        }

                        waitForScanFieldIdToAtdMap.put(currField.getFieldId(), patientATD);
                    }
                }

                //---------start set concept rule parameter
                Concept concept = currField.getConcept();
                if (concept != null) {
                    try {
                        String elementString = ((ConceptName) concept.getNames().toArray()[0]).getName();
                        ruleParameters.put("concept", elementString);
                    } catch (Exception e) {
                        ruleParameters.put("concept", null);
                    }
                } else {
                    ruleParameters.put("concept", null);
                }
                //---------end set concept rule parameter

                //process prioritized merge type fields
                String ruleType = defaultValue;
                Integer dssMergeCounter = dssMergeCounters.get(ruleType);
                if (dssMergeCounter == null) {
                    dssMergeCounter = 0;
                }

                //fill in the dss elements for this type
                //(this will only get executed once even though it is called for each field)
                //We need to set the max number of prioritized elements to generate
                if (dssManager.getMaxDssElementsByType(ruleType) == 0) {
                    dssManager.setMaxDssElementsByType(ruleType, maxDssElements);
                }

                dssManager.getPrioritizedDssElements(ruleType, startPriority, maxElements, ruleParameters);
                //get the result for this field
                Result result = processDssElements(dssManager, dssMergeCounter, currField.getFieldId(), ruleType);
                //            if (result != null) {

                elementList.add(dssManager.getDssElement(dssMergeCounter, ruleType));
                //            }

                dssMergeCounter++;
                dssMergeCounters.put(ruleType, dssMergeCounter);

                fieldToResult.put(currFormField, result);
            } else if (currFieldType.equals(mergeType)) {
                //store the leaf index as the result if the
                //current field has a parent
                if (currFormField.getParent() != null) {
                    fieldToResult.put(currFormField, defaultValue);
                }
            }
        }

        //process Results
        LinkedHashMap<String, String> fieldNameResult = new LinkedHashMap<String, String>();
        for (FormField currFormField : fieldToResult.keySet()) {
            String resultString = null;

            //fix lazy initialization error
            //org.openmrs.Field currField = formService.getField(currFormField.getField().getFieldId());
            org.openmrs.Field currField = currFormField.getField();
            String fieldName = currField.getName();
            Object fieldResult = fieldToResult.get(currFormField);

            if (fieldResult == null) {
                continue;
            }

            if (fieldResult instanceof Result) {
                resultString = ((Result) fieldResult).get(0).toString();
            } else {
                //if the field has a parent, process the result as a leaf index
                //of the parent results
                if (currFormField.getParent() == null) {
                    resultString = (String) fieldResult;
                } else {
                    try {
                        int leafPos = Integer.parseInt((String) fieldResult);
                        FormField parentField = currFormField.getParent();
                        Result parentResult = (Result) fieldToResult.get(parentField);

                        if (parentResult != null) {
                            resultString = parentResult.get(leafPos).toString();
                        } else {
                            resultString = null;
                        }
                    } catch (NumberFormatException e) {
                    }
                }
            }

            if (resultString != null) {
                //remove everything at or after the @ symbol (i.e. @Spanish)
                int atIndex = resultString.indexOf("@");
                if (atIndex >= 0) {
                    resultString = resultString.substring(0, atIndex);
                }
            }

            fieldNameResult.put(fieldName, resultString);
        }

        for (String fieldName : fieldNameResult.keySet()) {
            String resultString = fieldNameResult.get(fieldName);
            if (resultString == null) {
                continue;
            }

            Field field = new Field();
            field.setId(fieldName);
            field.setValue(resultString);
            fields.add(field);
        }

        saveDssElementsToDatabase(patient, formInstance, elementList, encounter, locationTagId, formName,
                waitForScanFieldIdToAtdMap);
        serializeFields(formInstance, locationTagId, fields, new ArrayList<String>()); // DWE CHICA-430 Add new ArrayList<String>()

        return fields;
    }

    /**
     * Populate all the form fields marked as "Merge Field".
     * 
     * @param formId The form identifier.
     * @param formInstanceId The form instance identifier.
     * @param encounterId The encounter identifier associated with the form.
     * @return List of Field object containing field information as well the results of populating the fields.
     */
    public List<Field> getMergeElements(Integer formId, Integer formInstanceId, Integer encounterId) {
        List<Field> fields = new ArrayList<Field>();
        EncounterService encounterService = Context.getEncounterService();
        Encounter encounter = encounterService.getEncounter(encounterId);
        Integer locationId = encounter.getLocation().getLocationId();

        LocationService locationService = Context.getLocationService();
        Location location = locationService.getLocation(locationId);

        FormService formService = Context.getFormService();
        Form form = formService.getForm(formId);

        FormInstance formInstance = new FormInstance(locationId, formId, formInstanceId);
        PatientState patientState = org.openmrs.module.atd.util.Util
                .getProducePatientStateByFormInstanceAction(formInstance);
        Integer locationTagId = patientState.getLocationTagId();

        HashMap<String, Object> parameters = new HashMap<String, Object>();
        parameters.put("sessionId", patientState.getSessionId());
        parameters.put("formInstance", new FormInstance(locationId, formId, formInstanceId));
        parameters.put("locationTagId", locationTagId);
        parameters.put("locationId", locationId);
        parameters.put("location", location.getName());
        parameters.put("encounterId", encounterId);
        parameters.put("mode", "PRODUCE");

        Patient patient = patientState.getPatient();
        ATDService atdService = Context.getService(ATDService.class);
        FieldType mergeType = getFieldType("Merge Field");
        List<FieldType> fieldTypes = new ArrayList<FieldType>();
        fieldTypes.add(mergeType);
        LinkedHashMap<FormField, Object> fieldToResult = new LinkedHashMap<FormField, Object>();
        List<FormField> formFields = Context.getService(ChirdlUtilBackportsService.class).getFormFields(form,
                fieldTypes, false);
        for (FormField currFormField : formFields) {
            org.openmrs.Field currField = currFormField.getField();

            //fix lazy initialization error
            //currField = formService.getField(currField.getFieldId());

            String defaultValue = currField.getDefaultValue();
            if (defaultValue == null) {
                continue;
            }

            parameters.put("formFieldId", currFormField.getFormFieldId()); // DWE CHICA-437

            //store the leaf index as the result if the
            //current field has a parent
            if (currFormField.getParent() == null) {
                Result result = atdService.evaluateRule(defaultValue, patient, parameters);
                fieldToResult.put(currFormField, result);
            }
        }

        //process Results
        LinkedHashMap<String, String> fieldNameResult = new LinkedHashMap<String, String>();
        for (FormField currFormField : fieldToResult.keySet()) {
            String resultString = null;

            //fix lazy initialization error
            //org.openmrs.Field currField = formService.getField(currFormField.getField().getFieldId());
            org.openmrs.Field currField = currFormField.getField();
            String fieldName = currField.getName();
            Object fieldResult = fieldToResult.get(currFormField);

            if (fieldResult == null) {
                continue;
            }

            if (fieldResult instanceof Result) {
                resultString = ((Result) fieldResult).get(0).toString();
            } else {
                resultString = (String) fieldResult;
            }

            if (resultString != null) {
                //remove everything at or after the @ symbol (i.e. @Spanish)
                int atIndex = resultString.indexOf("@");
                if (atIndex >= 0) {
                    resultString = resultString.substring(0, atIndex);
                }
            }

            fieldNameResult.put(fieldName, resultString);
        }

        for (String fieldName : fieldNameResult.keySet()) {
            String resultString = fieldNameResult.get(fieldName);
            if (resultString == null) {
                continue;
            }

            Field field = new Field();
            field.setId(fieldName);
            field.setValue(resultString);
            fields.add(field);
        }

        //Process rules with null priority that have @ value in write action
        //These rules directly write results to a specific field
        DssService dssService = Context.getService(DssService.class);
        List<RuleEntry> nonPriorRuleEntries = dssService.getNonPrioritizedRuleEntries(form.getName());

        for (RuleEntry currRuleEntry : nonPriorRuleEntries) {
            Rule currRule = currRuleEntry.getRule();
            if (currRule.checkAgeRestrictions(patient)) {
                currRule.setParameters(parameters);
                Result result = dssService.runRule(patient, currRule);
                for (Result currResult : result) {
                    String resultString = currResult.toString();
                    mapResult(resultString, fields);
                }
            }
        }

        serializeFields(formInstance, locationTagId, fields, new ArrayList<String>()); // DWE CHICA-430 Add new ArrayList<String>()
        return fields;
    }

    /**
     * Save the results of the fields marked as "Export Field".
     * 
     * @param formInstance FormInstance object containing the relevant form information.
     * @param locationTagId The location tag identifier.
     * @param encounterId The encounter identifier associated with the form.
     * @param patient The patient the form belongs to.
     * @param formFieldMap Map from the HTTP request that contains the field name to values.
     * @param parameterHandler The parameterHandler used for rule execution.
     */
    public void saveExportElements(FormInstance formInstance, Integer locationTagId, Integer encounterId,
            Patient patient, Map<String, String[]> formFieldMap, ParameterHandler parameterHandler) {
        HashMap<String, Field> fieldMap = new HashMap<String, Field>();
        FormService formService = Context.getFormService();
        Form form = formService.getForm(formInstance.getFormId());
        LinkedHashMap<FormField, String> formFieldToValue = new LinkedHashMap<FormField, String>();
        FieldType exportType = getFieldType("Export Field");
        List<FieldType> fieldTypes = new ArrayList<FieldType>();
        fieldTypes.add(exportType);
        List<FormField> formFields = Context.getService(ChirdlUtilBackportsService.class).getFormFields(form,
                fieldTypes, false);
        List<Integer> fieldIds = new ArrayList<Integer>();
        for (FormField formField : formFields) {
            fieldIds.add(formField.getField().getFieldId());
        }

        Iterator<FormField> formFieldIterator = formFields.iterator();
        while (formFieldIterator.hasNext()) {
            FormField formField = formFieldIterator.next();
            org.openmrs.Field field = formField.getField();
            String fieldName = field.getName();
            if (!formFieldMap.containsKey(fieldName)) {
                continue;
            }

            Field valueField = new Field(fieldName);
            fieldMap.put(fieldName, valueField);
            String[] valueObj = formFieldMap.get(fieldName);
            if (valueObj == null || valueObj.length == 0) {
                formFieldToValue.put(formField, null);
                continue;
            }

            String value = valueObj[0];
            formFieldToValue.put(formField, value);
            valueField.setValue(value);
        }

        consume(formInstance, patient, locationTagId, encounterId, fieldMap, formFieldToValue, parameterHandler,
                form);
        Context.getService(ChicaService.class).saveAnswers(fieldMap, formInstance, encounterId, patient,
                formFieldToValue.keySet());

        fieldMap.clear();
        formFieldToValue.clear();
    }

    /**
     * Consume the information populated on a form.
     * 
     * @param formInstance FormInstance object containing the relevant form information.
     * @param patient The patient the form belongs to.
     * @param locationTagId The location tag identifier.
     * @param encounterId The associated encounter identifier.
     * @param fieldMap Map of field name to Field object.
     * @param formFieldToValue Map of FormField to field value.
     * @param parameterHandler The parameter handler used for rule execution.
     * @param form The form containing the data to consume.
     */
    private void consume(FormInstance formInstance, Patient patient, Integer locationTagId, Integer encounterId,
            HashMap<String, Field> fieldMap, LinkedHashMap<FormField, String> formFieldToValue,
            ParameterHandler parameterHandler, Form form) {
        ATDService atdService = Context.getService(ATDService.class);
        Integer locationId = formInstance.getLocationId();
        PatientState patientState = org.openmrs.module.atd.util.Util
                .getProducePatientStateByFormInstanceAction(formInstance);
        Integer sessionId = patientState.getSessionId();
        FieldType prioritizedMergeType = getFieldType("Prioritized Merge Field");

        String mode = "CONSUME";
        LinkedHashMap<String, LinkedHashMap<String, Rule>> rulesToRunByField = new LinkedHashMap<String, LinkedHashMap<String, Rule>>();
        LogicService logicService = Context.getLogicService();
        FormDatasource formDatasource = (FormDatasource) logicService.getLogicDataSource("form");
        try {
            formInstance = formDatasource.setFormFields(fieldMap, formInstance, locationTagId);
        } catch (Exception e) {
            this.log.error("Error setting form fields to be consumed");
            this.log.error(e.getMessage());
            this.log.error(Util.getStackTrace(e));
            return;
        }

        if (formInstance == null) {
            log.error("Form instance came back null");
            return;
        }

        Encounter encounter = Context.getEncounterService().getEncounter(encounterId);
        locationId = encounter.getLocation().getLocationId();
        Location location = Context.getLocationService().getLocation(locationId);
        String locationName = null;
        if (location != null) {
            locationName = location.getName();
        }

        List<Field> fieldsToAdd = new ArrayList<Field>();
        Map<Integer, PatientATD> fieldIdToPatientAtdMap = new HashMap<Integer, PatientATD>();
        for (FormField currField : formFieldToValue.keySet()) {
            org.openmrs.Field field = currField.getField();
            String fieldName = field.getName();
            Concept currConcept = field.getConcept();
            String ruleName = field.getDefaultValue();
            LinkedHashMap<String, Rule> rulesToRun = null;
            Map<String, Object> parameters = new HashMap<String, Object>();

            FormField parentField = currField.getParent();

            //if parent field is not null look at parent
            //field for rule to execute
            Rule rule = null;
            if (parentField != null) {
                FieldType currFieldType = field.getFieldType();

                if (currFieldType.equals(prioritizedMergeType)) {
                    ruleName = null;//no rule to execute unless patientATD finds one   
                }

                Integer fieldId = parentField.getField().getFieldId();
                PatientATD patientATD = fieldIdToPatientAtdMap.get(fieldId);
                if (patientATD == null) {
                    patientATD = atdService.getPatientATD(formInstance, fieldId);
                }

                if (patientATD != null) {
                    rule = patientATD.getRule();
                    ruleName = rule.getTokenName();
                    fieldIdToPatientAtdMap.put(fieldId, patientATD);
                }
            }

            String lookupFieldName = null;
            Integer formFieldId = null; // DWE CHICA-437 Get the form field id here so that it can be used to determine if obs records should be voided when rules are evaluated
            if (parentField != null) {
                lookupFieldName = parentField.getField().getName();
                formFieldId = parentField.getFormFieldId();
            } else {
                lookupFieldName = fieldName;
                formFieldId = currField.getFormFieldId();
            }

            if (ruleName != null) {
                rulesToRun = rulesToRunByField.get(lookupFieldName);
                if (rulesToRun == null) {
                    rulesToRun = new LinkedHashMap<String, Rule>();
                    rulesToRunByField.put(lookupFieldName, rulesToRun);
                }

                Rule ruleLookup = rulesToRun.get(ruleName);
                if (ruleLookup == null) {
                    if (rule != null) {
                        ruleLookup = rule;
                    } else {
                        ruleLookup = new Rule();
                        ruleLookup.setTokenName(ruleName);
                    }
                    ruleLookup.setParameters(parameters);
                    rulesToRun.put(ruleName, ruleLookup);
                } else {
                    parameters = ruleLookup.getParameters();
                }
            }

            //------------start set rule parameters
            parameters.put("sessionId", sessionId);
            parameters.put("formInstance", formInstance);
            parameters.put("locationTagId", locationTagId);
            parameters.put("locationId", locationId);
            parameters.put("location", locationName);
            parameters.put("mode", mode);
            parameters.put("encounterId", encounterId);
            if (rule != null) {
                parameters.put("ruleId", rule.getRuleId());
            }

            if (currConcept != null) {
                try {
                    String elementString = ((ConceptName) currConcept.getNames().toArray()[0]).getName();
                    parameters.put("concept", elementString);
                } catch (Exception e) {
                    parameters.put("concept", null);
                }
            } else {
                parameters.put("concept", null);
            }

            if (fieldName != null) {
                parameters.put("fieldName", lookupFieldName);
                String value = formFieldToValue.get(currField);
                parameters.put(lookupFieldName, value);
                Field saveField = new Field();
                saveField.setId(fieldName);
                saveField.setValue(value);
                fieldsToAdd.add(saveField);
            }

            // DWE CHICA-437 
            if (formFieldId != null) {
                parameters.put("formFieldId", formFieldId);
            }

            //----------end set rule parameters
        }

        HashMap<String, Integer> childIndex = new HashMap<String, Integer>();

        for (FormField currField : formFieldToValue.keySet()) {
            LinkedHashMap<String, Rule> rulesToRun = null;
            Map<String, Object> parameters = new HashMap<String, Object>();
            FormField parentField = currField.getParent();

            //look for parentField
            if (parentField != null) {
                FieldType parentFieldType = parentField.getField().getFieldType();

                String parentRuleName = parentField.getField().getDefaultValue();
                String parentFieldName = parentField.getField().getName();

                if (parentFieldType.equals(prioritizedMergeType)) {
                    parentRuleName = null;//no rule to execute unless patientATD finds one   
                }

                Integer fieldId = parentField.getField().getFieldId();
                PatientATD patientATD = fieldIdToPatientAtdMap.get(fieldId);
                if (patientATD == null) {
                    patientATD = atdService.getPatientATD(formInstance, fieldId);
                }

                if (patientATD != null) {
                    Rule rule = patientATD.getRule();
                    parentRuleName = rule.getTokenName();
                    fieldIdToPatientAtdMap.put(fieldId, patientATD);
                }
                //if there is a parent rule, add a parameter for the child's fieldname
                //add the parent rule if it is not in rules to run
                if (parentRuleName != null) {
                    rulesToRun = rulesToRunByField.get(parentFieldName);
                    if (rulesToRun == null) {
                        rulesToRun = new LinkedHashMap<String, Rule>();
                        rulesToRunByField.put(parentFieldName, rulesToRun);
                    }

                    Rule ruleLookup = rulesToRun.get(parentRuleName);

                    if (ruleLookup == null) {
                        ruleLookup = new Rule();
                        ruleLookup.setParameters(parameters);
                        ruleLookup.setTokenName(parentRuleName);
                        rulesToRun.put(parentRuleName, ruleLookup);
                    } else {
                        parameters = ruleLookup.getParameters();
                    }

                    String childFieldName = currField.getField().getName();
                    Integer index = childIndex.get(parentFieldName);
                    if (index == null) {
                        index = 0;
                    }
                    parameters.put("child" + index, childFieldName);
                    parameters.put(childFieldName, formFieldToValue.get(currField));
                    childIndex.put(parentFieldName, ++index);
                }
            }
        }

        //run all the consume rules
        Integer formInstanceId = formInstance.getFormInstanceId();
        String formName = form.getName();
        String formType = org.openmrs.module.chirdlutil.util.Util.getFormType(form.getFormId(), locationTagId,
                locationId); // CHICA-1234 Look up the formType
        for (LinkedHashMap<String, Rule> rulesToRun : rulesToRunByField.values()) {
            for (String currRuleName : rulesToRun.keySet()) {
                Rule rule = rulesToRun.get(currRuleName);
                Map<String, Object> parameters = rule.getParameters();
                parameterHandler.addParameters(parameters, fieldMap, formType); // CHICA-1234 Added formType parameter
                atdService.evaluateRule(currRuleName, patient, parameters);
                setScannedTimestamps(formInstanceId, rule.getRuleId(), formName, locationId);
            }
        }

        // DWE CHICA-430 Now that rules have run and obs records have been added/updated/voided
        // create the list of fields to remove from the xml
        List<String> elementsToRemoveList = createElementsToRemoveList(form, formInstanceId, encounter,
                locationTagId, locationId);

        fieldIdToPatientAtdMap.clear();
        serializeFields(formInstance, locationTagId, fieldsToAdd, elementsToRemoveList); // DWE CHICA-430 Add elementsToRemoveList
    }

    /**
     * Looks up an openmrs FieldType by name
     * 
     * @param fieldTypeName name of the field type
     * @return FieldType openmrs field type with the given name
     */
    public FieldType getFieldType(String fieldTypeName) {
        FormService formService = Context.getFormService();
        List<FieldType> fieldTypes = formService.getAllFieldTypes();
        Iterator<FieldType> iter = fieldTypes.iterator();
        FieldType currFieldType = null;

        while (iter.hasNext()) {
            currFieldType = iter.next();
            if (currFieldType.getName().equals(fieldTypeName)) {
                return currFieldType;
            }
        }
        return null;
    }

    /**
     * Return the Result object based Rule that was ran for a field.
     * 
     * @param dssManager DssManager handling the context information.
     * @param dssMergeCounter The position of the DssElement in the list.
     * @param fieldId The identifier of the field.
     * @param type The rule type.
     * @return Result object or null if one cannot be found.
     */
    private Result processDssElements(DssManager dssManager, int dssMergeCounter, Integer fieldId, String type) {
        DssElement dssElement = dssManager.getDssElement(dssMergeCounter, type);

        if (dssElement != null) {
            dssElement.addParameter("fieldId", fieldId);
            return dssElement.getResult();
        }

        return null;
    }

    /**
     * Saves the necessary information to the database for the rule execution.
     * 
     * @param patient The patient the rules were executed against.
     * @param formInstance The FormInstance object containing the relevant form information.
     * @param elementList List of DssElement object containing the Result information for the form fields.
     * @param encounter The encounter associated with the form.
     * @param locationTagId The location tag identifier.
     * @param formName The name of the form.
     * @param waitForScanFieldIdToAtdMap Map of field identifier to PatientATD object for fields that are awaiting scans.
     */
    private void saveDssElementsToDatabase(Patient patient, FormInstance formInstance, List<DssElement> elementList,
            Encounter encounter, Integer locationTagId, String formName,
            Map<Integer, PatientATD> waitForScanFieldIdToAtdMap) {
        Integer patientId = patient.getPatientId();
        ATDService atdService = Context.getService(ATDService.class);
        DssService dssService = Context.getService(DssService.class);
        Integer formInstanceId = formInstance.getFormInstanceId();
        Integer locationId = formInstance.getLocationId();
        Integer encounterId = encounter.getEncounterId();

        for (int i = 0; i < elementList.size(); i++) {
            DssElement currDssElement = elementList.get(i);
            if (currDssElement == null) {
                continue;
            }

            Integer fieldId = (Integer) currDssElement.getParameter("fieldId");
            PatientATD patientATD = waitForScanFieldIdToAtdMap.get(fieldId);
            if (patientATD == null) {
                atdService.addPatientATD(formInstance, currDssElement, encounterId, patientId);
                addStatistic(dssService, atdService, patient, currDssElement, formInstanceId, i, encounter,
                        formName, locationTagId, locationId);
            } else if (currDssElement.getRuleId() != patientATD.getRule().getRuleId()) {
                Integer ruleId = currDssElement.getRuleId();
                Rule rule = null;
                Integer priority = null;
                RuleEntry ruleEntry = dssService.getRuleEntry(ruleId, formName);
                if (ruleEntry != null) {
                    rule = ruleEntry.getRule();
                    priority = ruleEntry.getPriority();
                } else {
                    rule = dssService.getRule(ruleId);
                }

                patientATD.setRule(rule);
                Result result = currDssElement.getResult();
                if (result != null) {
                    if (result.get(0) != null) {
                        patientATD.setText(result.get(0).toString());
                    } else {
                        patientATD.setText(result.toString());
                    }
                }

                patientATD.setCreationTime(new Date());
                atdService.updatePatientATD(patientATD);

                List<Statistics> stats = atdService.getStatByIdAndRule(formInstanceId, ruleId, formName,
                        locationId);
                for (Statistics stat : stats) {
                    stat.setRuleId(ruleId);
                    stat.setPriority(priority);
                    stat.setPrintedTimestamp(new Date());
                    atdService.updateStatistics(stat);
                }
            }
        }
    }

    /**
     * Adds statistical information for field rule execution.
     * 
     * @param dssService DssService used to access Rule information.
     * @param atdService AtdService used to create and save statistical information.
     * @param patient The patient associated with the process.
     * @param currDssElement The current DssElement element to save statistical information.
     * @param formInstanceId The form instance identifier.
     * @param questionPosition The position of the question on the form.
     * @param encounter The encounter associated with the statistical information.
     * @param formName The name of the form.
     * @param locationTagId The location tag identifier.
     * @param locationId The location identifier.
     */
    private void addStatistic(DssService dssService, ATDService atdService, Patient patient,
            DssElement currDssElement, Integer formInstanceId, int questionPosition, Encounter encounter,
            String formName, Integer locationTagId, Integer locationId) {
        Integer ruleId = currDssElement.getRuleId();
        // Try to get rule entry to determine priority
        Integer priority = null;
        RuleEntry ruleEntry = dssService.getRuleEntry(ruleId, formName);
        if (ruleEntry != null) {
            priority = ruleEntry.getPriority();
        }

        Statistics statistics = new Statistics();
        statistics.setAgeAtVisit(Util.adjustAgeUnits(patient.getBirthdate(), null));
        statistics.setPriority(priority);
        statistics.setFormInstanceId(formInstanceId);
        statistics.setLocationTagId(locationTagId);
        statistics.setPosition(questionPosition + 1);

        statistics.setRuleId(ruleId);
        statistics.setPatientId(patient.getPatientId());
        statistics.setFormName(formName);
        statistics.setEncounterId(encounter.getEncounterId());
        statistics.setLocationId(locationId);
        statistics.setPrintedTimestamp(new Date());

        atdService.createStatistics(statistics);
    }

    /**
     * Retrieves the start priority of the rule execution based on other rules that have already been executed.
     * 
     * @param atdService ATDService object used to access statistical information.
     * @param formInstanceId The form instance identifier.
     * @param locationId The location identifier.
     * @param formName The form name.
     * @return The start priority of the rule execution.
     */
    private Integer getStartPriority(ATDService atdService, Integer formInstanceId, Integer locationId,
            String formName) {
        Integer startPriority = new Integer(-1);
        List<Statistics> stats = atdService.getStatByFormInstance(formInstanceId, formName, locationId);
        for (Statistics stat : stats) {
            Integer priority = stat.getPriority();
            Date scannedDate = stat.getScannedTimestamp();
            if (priority == null || scannedDate == null) {
                continue;
            }

            if (priority > startPriority) {
                startPriority = priority;
            }
        }

        return startPriority + 1;
    }

    /**
     * Sets the scanned timestamps for a specific form and rule.
     * 
     * @param formInstanceId The form instance identifier.
     * @param ruleId The rule identifier.
     * @param formName The name of the form.
     * @param locationId The location identifier.
     */
    private void setScannedTimestamps(Integer formInstanceId, Integer ruleId, String formName, Integer locationId) {
        if (ruleId == null) {
            return;
        }

        ATDService atdService = Context.getService(ATDService.class);
        List<Statistics> stats = atdService.getStatByIdAndRule(formInstanceId, ruleId, formName, locationId);
        if (stats == null) {
            return;
        }

        for (Statistics stat : stats) {
            if (stat.getScannedTimestamp() == null) {
                stat.setScannedTimestamp(new Date());
                atdService.updateStatistics(stat);
            }
        }
    }

    /**
     * Parses a rule result string into a Field object and adds it to the provided list of fields.
     * 
     * @param resultString The result string to parse.
     * @param fields The list to add the parsed result string to.
     */
    private void mapResult(String resultString, List<Field> fields) {

        StringTokenizer tokenizer = new StringTokenizer(resultString, ",");
        while (tokenizer.hasMoreTokens()) {
            String currResult = tokenizer.nextToken();

            int atIndex = currResult.indexOf("@");
            if (atIndex >= 0 && atIndex + 1 < currResult.length()) {
                String fieldName = currResult.substring(atIndex + 1, currResult.length()).trim();
                currResult = currResult.substring(0, atIndex).trim();
                if (currResult.length() > 0) {
                    Field field = new Field();
                    field.setId(fieldName);
                    field.setValue(currResult);
                    fields.add(field);
                }
            }
        }

    }

    /**
     * Serializes the fields to disk.
     * 
     * @param formInstance FormInstance object to identify the form that will have the fields add/updated.
     * @param locationTagId The locationTagId where the form was created.
     * @param fields The fields to add/update.
     * @param elementsToRemoveList - DWE CHICA-430 Added parameter that contains a list of xml elements to remove
     */
    private void serializeFields(FormInstance formInstance, Integer locationTagId, List<Field> fields,
            List<String> elementsToRemoveList) {
        ChirdlRunnable runnable = new FormWriter(formInstance, locationTagId, fields, elementsToRemoveList); // DWE CHICA-430 Add elementsToRemoveList;
        ThreadManager manager = ThreadManager.getInstance();
        manager.execute(runnable, formInstance.getLocationId());
    }

    /**
     * DWE CHICA-430
     * Gets a list of xml elements to remove. Elements will be removed if the obs record has been voided.
     * 
     * Currently only used for the PSF
     * 
     * @param formId
     * @param formInstanceId
     * @param encounterId
     * @param locationTagId
     * @param locationId
     * @return list of element ids to remove
     */
    private List<String> createElementsToRemoveList(Form form, Integer formInstanceId, Encounter encounter,
            Integer locationTagId, Integer locationId) {
        String displayAndUpdatePreviousValues = org.openmrs.module.chirdlutilbackports.util.Util
                .getFormAttributeValue(form.getFormId(),
                        ChirdlUtilConstants.FORM_ATTR_DISPLAY_AND_UPDATE_PREVIOUS_VALUES, locationTagId,
                        locationId);

        if (displayAndUpdatePreviousValues != null
                && displayAndUpdatePreviousValues.equalsIgnoreCase(ChirdlUtilConstants.FORM_ATTR_VAL_TRUE)) // Currently only used for the PSF
        {
            try {
                List<String> elementsToRemoveList = new ArrayList<String>();
                ATDService atdService = Context.getService(ATDService.class);
                //ObsService obsService = Context.getObsService();
                List<org.openmrs.Encounter> encounters = new ArrayList<org.openmrs.Encounter>();
                encounters.add(encounter);

                FieldType mergeType = getFieldType(ChirdlUtilConstants.FORM_FIELD_TYPE_EXPORT);
                List<FieldType> fieldTypes = new ArrayList<FieldType>();
                fieldTypes.add(mergeType);

                // Get the list of export fields for this form
                List<FormField> formFields = Context.getService(ChirdlUtilBackportsService.class)
                        .getFormFields(form, fieldTypes, false);

                for (FormField formField : formFields) {
                    Concept concept = formField.getField().getConcept();
                    if (concept != null) {
                        // Find obs records using the atd_statistics table and the form field id
                        List<Obs> obsList = atdService.getObsWithStatistics(encounter.getEncounterId(),
                                concept.getConceptId(), formField.getFormFieldId(), true);

                        // Check to see if any of the obs records have been voided for the encounter
                        // If an obs has been voided, but a new one was created, it will be added back to the xml
                        // with the "fieldsToAdd" list
                        for (Obs obs : obsList) {
                            if (obs.getVoided()) {
                                elementsToRemoveList.add(formField.getField().getName());
                                break;
                            }
                        }
                    }
                }

                return elementsToRemoveList;
            } catch (Exception e) {
                log.error("Unable to create list of xml elements to remove (formId = " + form.getId()
                        + " formInstanceId = " + formInstanceId + ").", e);
                return new ArrayList<String>();
            }
        } else {
            return new ArrayList<String>();
        }
    }

    /**
     * DWE CHICA-430
     * Get a list of "Export Field" types with values that were previously entered for the form 
     * for this encounter
     * 
     * Currently only used for the PSF
     * 
     * @param formId
     * @param formInstanceId
     * @param encounterId
     * @return list of fields with values that were previously entered for the form
     */
    public List<Field> getExportElements(Integer formId, Integer formInstanceId, Integer encounterId,
            Integer locationTagId) {
        try {
            EncounterService encounterService = Context.getEncounterService();
            Encounter encounter = encounterService.getEncounter(encounterId);
            Integer locationId = encounter.getLocation().getId();
            List<Field> fields = new ArrayList<Field>();
            FormService formService = Context.getFormService();
            Form form = formService.getForm(formId);

            String displayAndUpdatePreviousValues = org.openmrs.module.chirdlutilbackports.util.Util
                    .getFormAttributeValue(formId, ChirdlUtilConstants.FORM_ATTR_DISPLAY_AND_UPDATE_PREVIOUS_VALUES,
                            locationTagId, locationId);

            if (displayAndUpdatePreviousValues != null
                    && displayAndUpdatePreviousValues.equalsIgnoreCase(ChirdlUtilConstants.FORM_ATTR_VAL_TRUE)) // Currently only used for the PSF
            {
                FormInstance formInstance = new FormInstance(locationId, formId, formInstanceId);

                String scanDirectory = IOUtil.formatDirectoryName(
                        org.openmrs.module.chirdlutilbackports.util.Util.getFormAttributeValue(formId,
                                XMLUtil.DEFAULT_EXPORT_DIRECTORY, locationTagId, locationId));

                if (scanDirectory == null) {
                    log.info("No " + XMLUtil.DEFAULT_EXPORT_DIRECTORY + " found for Form: " + formId
                            + " Location ID: " + locationId + " Location Tag ID: " + locationTagId + ".");
                    return new ArrayList<Field>();
                }

                File file = new File(scanDirectory,
                        formInstance.toString() + ChirdlUtilConstants.FILE_EXTENSION_20);
                if (file.exists() && file.length() > 0) {
                    try {
                        Records records = (Records) XMLUtil.deserializeXML(Records.class,
                                new FileInputStream(file));
                        Map<String, Field> fieldMap = new HashMap<String, Field>();
                        List<Field> currentFields = records.getRecord().getFields();

                        // Create a map with the current fields so the map can be used to lookup field values
                        for (Field field : currentFields) {
                            fieldMap.put(field.getId(), field);
                        }

                        FieldType exportType = getFieldType(ChirdlUtilConstants.FORM_FIELD_TYPE_EXPORT);
                        List<FieldType> fieldTypes = new ArrayList<FieldType>();
                        fieldTypes.add(exportType);

                        // Get the list of export fields for this form
                        List<FormField> formFields = Context.getService(ChirdlUtilBackportsService.class)
                                .getFormFields(form, fieldTypes, false);

                        // Locate values for the export fields
                        for (FormField formField : formFields) {
                            String fieldName = formField.getField().getName();
                            Field field = new Field();
                            field.setId(fieldName);
                            Field currentField = fieldMap.get(fieldName);
                            if (currentField != null) {
                                field.setValue(currentField.getValue());
                            } else {
                                field.setValue("");
                            }

                            fields.add(field);
                        }
                    } catch (IOException ioe) {
                        log.error(ioe.getMessage() + "(length = " + file.length() + ")");
                        log.error(Util.getStackTrace(ioe));
                        return new ArrayList<Field>();
                    }

                    return fields;
                } else {
                    return new ArrayList<Field>();
                }
            } else {
                return new ArrayList<Field>();
            }
        } catch (APIException apie) {
            log.error(apie.getMessage());
            log.error(Util.getStackTrace(apie));
            return new ArrayList<Field>();
        }
    }
}