org.openmrs.module.coreapps.htmlformentry.EncounterDiagnosesByObsElement.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.coreapps.htmlformentry.EncounterDiagnosesByObsElement.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public License,
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
 *
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
 * graphic logo is a trademark of OpenMRS Inc.
 */

package org.openmrs.module.coreapps.htmlformentry;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.openmrs.ConceptSource;
import org.openmrs.Encounter;
import org.openmrs.Obs;
import org.openmrs.Visit;
import org.openmrs.api.ConceptService;
import org.openmrs.api.context.Context;
import org.openmrs.module.emrapi.EmrApiProperties;
import org.openmrs.module.emrapi.adt.AdtService;
import org.openmrs.module.emrapi.diagnosis.CodedOrFreeTextAnswer;
import org.openmrs.module.emrapi.diagnosis.Diagnosis;
import org.openmrs.module.emrapi.diagnosis.DiagnosisMetadata;
import org.openmrs.module.emrapi.disposition.DispositionType;
import org.openmrs.module.emrapi.visit.VisitDomainWrapper;
import org.openmrs.module.htmlformentry.FormEntryContext;
import org.openmrs.module.htmlformentry.FormEntrySession;
import org.openmrs.module.htmlformentry.FormSubmissionActions;
import org.openmrs.module.htmlformentry.FormSubmissionError;
import org.openmrs.module.htmlformentry.InvalidActionException;
import org.openmrs.module.htmlformentry.action.FormSubmissionControllerAction;
import org.openmrs.module.htmlformentry.element.HtmlGeneratorElement;
import org.openmrs.module.htmlformentry.widget.ErrorWidget;
import org.openmrs.module.htmlformentry.widget.HiddenFieldWidget;
import org.openmrs.ui.framework.UiUtils;
import org.openmrs.ui.framework.page.PageAction;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
 * TODO: this is identical to EncounterDiagnosesElement in pre-2.2 package, but I couldn't figure out how to get around it without cyclic dependencies
 * Once we no longer support versions of OpenMRS prior to 2.2 (a long way away!) can probably just delete the pre2.2 package
 */
public class EncounterDiagnosesByObsElement implements HtmlGeneratorElement, FormSubmissionControllerAction {

    private boolean required = false;
    private UiUtils uiUtils;
    private String selectedDiagnosesTarget;

    private EmrApiProperties emrApiProperties;

    private ConceptService conceptService;

    private AdtService adtService;

    private DispositionType dispositionTypeForPriorDiagnoses = null;

    // we do not actually use the hiddenDiagnoses widget (the form field name is hardcoded) but we need it to register errorWidget
    private HiddenFieldWidget hiddenDiagnoses;
    private ErrorWidget errorWidget;

    public EncounterDiagnosesByObsElement() {
    }

    @Override
    public String generateHtml(FormEntryContext context) {
        List<Diagnosis> existingDiagnoses = getExistingDiagnoses(context, emrApiProperties.getDiagnosisMetadata());

        if (FormEntryContext.Mode.VIEW == context.getMode()) {
            StringBuilder sb = new StringBuilder();
            if (existingDiagnoses != null) {
                List<ConceptSource> conceptSourcesForDiagnosisSearch = emrApiProperties
                        .getConceptSourcesForDiagnosisSearch();
                for (Diagnosis diagnosis : existingDiagnoses) {
                    sb.append("<p><small>");
                    // question (e.g. "Primary diagnosis")
                    sb.append(message("coreapps.patientDashBoard.diagnosisQuestion." + diagnosis.getOrder()));
                    sb.append("</small><span>");
                    // answer (e.g. "(Confirmed) Malaria [code]")
                    sb.append("(" + message("coreapps.Diagnosis.Certainty." + diagnosis.getCertainty()) + ") ");
                    sb.append(
                            diagnosis.getDiagnosis().formatWithCode(getLocale(), conceptSourcesForDiagnosisSearch));
                    sb.append("</span></p>");
                }
            }
            return sb.toString();

        } else {

            hiddenDiagnoses = new HiddenFieldWidget();
            errorWidget = new ErrorWidget();
            context.registerWidget(hiddenDiagnoses);
            context.registerErrorWidget(hiddenDiagnoses, errorWidget);

            try {
                Map<String, Object> fragmentConfig = new HashMap<String, Object>();
                fragmentConfig.put("formFieldName", "encounterDiagnoses");
                fragmentConfig.put("existingDiagnoses", existingDiagnoses);

                // add the prior diagnoses if requested
                if (FormEntryContext.Mode.ENTER == context.getMode() && dispositionTypeForPriorDiagnoses != null) {
                    fragmentConfig.put("priorDiagnoses",
                            getPriorDiagnoses(context, dispositionTypeForPriorDiagnoses));
                }

                try {
                    StringBuilder output = new StringBuilder();
                    output.append(errorWidget.generateHtml(context));
                    output.append(
                            uiUtils.includeFragment("coreapps", "diagnosis/encounterDiagnoses", fragmentConfig));
                    if (selectedDiagnosesTarget != null) {
                        output.append(
                                "\n <script type=\"text/javascript\"> \n $(function() { $('#display-encounter-diagnoses-container').appendTo('"
                                        + selectedDiagnosesTarget + "'); }); \n </script>");
                    }
                    return output.toString();
                } catch (NullPointerException ex) {
                    // if we are validating/submitting the form, then this method is being called from a fragment action method
                    // and the UiUtils we have access to doesn't have a FragmentIncluder. That's okay, because we don't actually
                    // need to generate the HTML, so we can pass on this exception.
                    // (This is hacky, but I don't see a better way to do it.)
                    return "Submitting the form, so we don't generate HTML";
                }
            } catch (PageAction pageAction) {
                throw new IllegalStateException("Included fragment threw a PageAction", pageAction);
            }
        }
    }

    @Override
    public Collection<FormSubmissionError> validateSubmission(FormEntryContext context,
            HttpServletRequest request) {
        String submitted = request.getParameter("encounterDiagnoses");
        if (StringUtils.isEmpty(submitted) && required) {
            return Collections.singleton(new FormSubmissionError(hiddenDiagnoses, "Required"));
        }

        try {
            List<Diagnosis> diagnoses = parseDiagnoses(submitted, null);
            if (diagnoses.size() == 0 && required) {
                return Collections.singleton(new FormSubmissionError(hiddenDiagnoses, "Required"));
            }
            if (diagnoses.size() > 0) {
                // at least one diagnosis must be primary
                boolean foundPrimary = false;
                for (Diagnosis diagnosis : diagnoses) {
                    if (diagnosis.getOrder().equals(Diagnosis.Order.PRIMARY)) {
                        foundPrimary = true;
                        break;
                    }
                }
                if (!foundPrimary) {
                    return Collections.singleton(new FormSubmissionError(hiddenDiagnoses,
                            message("coreapps.encounterDiagnoses.error.primaryRequired")));
                }
            }
        } catch (IOException e) {
            return Collections.singleton(
                    new FormSubmissionError(hiddenDiagnoses, "Programming Error: invalid json list submitted"));
        }
        return null;
    }

    private List<Diagnosis> parseDiagnoses(String jsonList, Map<Integer, Obs> existingDiagnosisObs)
            throws IOException {
        // low-priority: refactor this so that a Diagnosis can parse itself via jackson.
        // requires changing org.openmrs.module.emrapi.diagnosis.ConceptCodeDeserializer to also handle parse by id.
        List<Diagnosis> parsed = new ArrayList<Diagnosis>();
        JsonNode list = new ObjectMapper().readTree(jsonList);
        for (JsonNode node : list) {
            CodedOrFreeTextAnswer answer = new CodedOrFreeTextAnswer(node.get("diagnosis").getTextValue(),
                    conceptService);
            Diagnosis.Order diagnosisOrder = Diagnosis.Order.valueOf(node.get("order").getTextValue());
            Diagnosis.Certainty certainty = Diagnosis.Certainty.valueOf(node.get("certainty").getTextValue());
            Obs existingObs = null;
            if (existingDiagnosisObs != null && node.path("existingObs").getNumberValue() != null) {
                existingObs = existingDiagnosisObs.get(node.get("existingObs").getNumberValue());
            }

            Diagnosis diagnosis = new Diagnosis(answer, diagnosisOrder);
            diagnosis.setCertainty(certainty);
            diagnosis.setExistingObs(existingObs);
            parsed.add(diagnosis);
        }
        return parsed;
    }

    @Override
    public void handleSubmission(FormEntrySession formEntrySession, HttpServletRequest request) {
        DiagnosisMetadata diagnosisMetadata = emrApiProperties.getDiagnosisMetadata();
        String submitted = request.getParameter("encounterDiagnoses");

        // if we are in edit mode, we need to map the submitted diagnoses to their existing obs
        Map<Integer, Obs> existingDiagnosisObs = getExistingDiagnosisObs(formEntrySession.getContext(),
                diagnosisMetadata);

        FormSubmissionActions submissionActions = formEntrySession.getSubmissionActions();
        try {
            Set<Integer> resubmittedObs = new HashSet<Integer>(); // we need to void any existing that isn't resubmitted

            List<Diagnosis> diagnoses = parseDiagnoses(submitted, existingDiagnosisObs);
            for (Diagnosis diagnosis : diagnoses) {
                if (diagnosis.getExistingObs() != null) {
                    resubmittedObs.add(diagnosis.getExistingObs().getId());
                }
                Obs obsGroup = diagnosisMetadata.buildDiagnosisObsGroup(diagnosis);
                createObsGroup(submissionActions, obsGroup);
            }

            if (formEntrySession.getContext().getMode().equals(FormEntryContext.Mode.EDIT)) {
                // void any diagnosis that wasn't resubmitted
                Collection<Integer> obsToVoid = CollectionUtils.subtract(existingDiagnosisObs.keySet(),
                        resubmittedObs);
                for (Integer obsId : obsToVoid) {
                    submissionActions.modifyObs(existingDiagnosisObs.get(obsId), null, null, null, null, null);
                }
            }
        } catch (IOException e) {
            throw new IllegalStateException(e);
        } catch (InvalidActionException e) {
            throw new IllegalStateException(e);
        }
    }

    private Map<Integer, Obs> getExistingDiagnosisObs(FormEntryContext context,
            DiagnosisMetadata diagnosisMetadata) {
        Map<Integer, Obs> existingDiagnosisObs = null;
        FormEntryContext.Mode mode = context.getMode();
        if (mode == FormEntryContext.Mode.EDIT || mode == FormEntryContext.Mode.VIEW) {
            existingDiagnosisObs = new HashMap<Integer, Obs>();
            Encounter encounter = context.getExistingEncounter();
            if (encounter == null) {
                // this situation happens during unit tests when viewing the form with a Person. (I don't know if this a
                // real use case though.
                return null;
            }
            for (Obs candidate : encounter.getObsAtTopLevel(false)) {
                if (diagnosisMetadata.isDiagnosis(candidate)) {
                    existingDiagnosisObs.put(candidate.getObsId(), candidate);
                }
            }

            // remove any diagnoses found from existingObsInGroups
            // TODO do we need to remove from existingObs as well?
            for (Obs existingDiagnosis : existingDiagnosisObs.values()) {
                context.getExistingObsInGroups().remove(existingDiagnosis);
            }

        }
        return existingDiagnosisObs;
    }

    /**
     * only visible for testing
     * @param context
     * @param diagnosisMetadata
     * @return
     */
    List<Diagnosis> getExistingDiagnoses(FormEntryContext context, DiagnosisMetadata diagnosisMetadata) {
        List<Diagnosis> diagnoses = new ArrayList<Diagnosis>();
        Map<Integer, Obs> existing = getExistingDiagnosisObs(context, diagnosisMetadata);
        if (existing != null) {
            for (Obs group : existing.values()) {
                diagnoses.add(diagnosisMetadata.toDiagnosis(group));
            }
        }
        Collections.sort(diagnoses, new Comparator<Diagnosis>() {
            @Override
            public int compare(Diagnosis left, Diagnosis right) {
                return left.getOrder().compareTo(right.getOrder());
            }
        });
        return diagnoses;
    }

    private List<Diagnosis> getPriorDiagnoses(FormEntryContext context, DispositionType dispositionType) {
        List<Diagnosis> diagnoses = new ArrayList<Diagnosis>();
        if (context.getVisit() != null) {

            VisitDomainWrapper visitDomainWrapper;

            if (context.getVisit() instanceof Visit) {
                visitDomainWrapper = adtService.wrap((Visit) context.getVisit());
            } else {
                visitDomainWrapper = (VisitDomainWrapper) context.getVisit();
            }

            diagnoses = visitDomainWrapper.getDiagnosesFromMostRecentDispositionByType(dispositionType);
        }

        return diagnoses;
    }

    private void createObsGroup(FormSubmissionActions actions, Obs obsGroup) throws InvalidActionException {
        actions.beginObsGroup(obsGroup);
        actions.endObsGroup();
    }

    public void setRequired(boolean required) {
        this.required = required;
    }

    public boolean getRequired() {
        return required;
    }

    public void setUiUtils(UiUtils uiUtils) {
        this.uiUtils = uiUtils;
    }

    public void setEmrApiProperties(EmrApiProperties emrApiProperties) {
        this.emrApiProperties = emrApiProperties;
    }

    public void setConceptService(ConceptService conceptService) {
        this.conceptService = conceptService;
    }

    public void setAdtService(AdtService adtService) {
        this.adtService = adtService;
    }

    public void setDispositionTypeForPriorDiagnoses(DispositionType dispositionTypeForPriorDiagnoses) {
        this.dispositionTypeForPriorDiagnoses = dispositionTypeForPriorDiagnoses;
    }

    public DispositionType getDispositionTypeForPriorDiagnoses() {
        return dispositionTypeForPriorDiagnoses;
    }

    public void setSelectedDiagnosesTarget(String selectedDiagnosesTarget) {
        this.selectedDiagnosesTarget = selectedDiagnosesTarget;
    }

    public String getSelectedDiagnosesTarget() {
        return selectedDiagnosesTarget;
    }

    /**
     * In case you are viewing a form with this element on it from the legacy UI, don't use UiUtils to format
     * @param code
     * @return
     */
    String message(String code) {
        if (uiUtils != null) {
            return uiUtils.message(code);
        } else {
            return Context.getMessageSourceService().getMessage(code);
        }
    }

    private Locale getLocale() {
        if (uiUtils != null) {
            return uiUtils.getLocale();
        } else {
            return Context.getLocale();
        }
    }
}