org.openmrs.module.htmlformentry.element.PatientDetailSubmissionElement.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.htmlformentry.element.PatientDetailSubmissionElement.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.htmlformentry.element;

import org.openmrs.Location;
import org.openmrs.Patient;
import org.openmrs.PatientIdentifier;
import org.openmrs.PatientIdentifierType;
import org.openmrs.Person;
import org.openmrs.PersonAddress;
import org.openmrs.PersonName;
import org.openmrs.api.IdentifierNotUniqueException;
import org.openmrs.api.context.Context;
import org.openmrs.messagesource.MessageSourceService;
import org.openmrs.module.htmlformentry.FormEntryContext;
import org.openmrs.module.htmlformentry.FormEntryContext.Mode;
import org.openmrs.module.htmlformentry.FormEntrySession;
import org.openmrs.module.htmlformentry.FormSubmissionError;
import org.openmrs.module.htmlformentry.HtmlFormEntryUtil;
import org.openmrs.module.htmlformentry.ValidationException;
import org.openmrs.module.htmlformentry.action.FormSubmissionControllerAction;
import org.openmrs.module.htmlformentry.comparator.OptionComparator;
import org.openmrs.module.htmlformentry.widget.AddressWidget;
import org.openmrs.module.htmlformentry.widget.DateWidget;
import org.openmrs.module.htmlformentry.widget.DropdownWidget;
import org.openmrs.module.htmlformentry.widget.ErrorWidget;
import org.openmrs.module.htmlformentry.widget.HiddenFieldWidget;
import org.openmrs.module.htmlformentry.widget.NameWidget;
import org.openmrs.module.htmlformentry.widget.NumberFieldWidget;
import org.openmrs.module.htmlformentry.widget.Option;
import org.openmrs.module.htmlformentry.widget.TextFieldWidget;
import org.openmrs.module.htmlformentry.widget.Widget;
import org.openmrs.util.OpenmrsUtil;
import org.openmrs.validator.PatientIdentifierValidator;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;

/**
 * Holds the widgets used to represent Patient Details, and serves as both the
 * HtmlGeneratorElement and the FormSubmissionControllerAction for Patient
 * Details.
 */
public class PatientDetailSubmissionElement implements HtmlGeneratorElement, FormSubmissionControllerAction {

    public static final String FIELD_PERSON_NAME = "name";

    public static final String FIELD_BIRTH_DATE_OR_AGE = "birthDateOrAge";

    public static final String FIELD_BIRTH_DATE = "birthDate";

    public static final String FIELD_AGE = "age";

    public static final String FIELD_GENDER = "gender";

    public static final String FIELD_IDENTIFIER = "identifier";

    public static final String FIELD_IDENTIFIER_LOCATION = "identifierLocation";

    public static final String FIELD_ADDRESS = "address";

    private NameWidget nameWidget;
    private ErrorWidget nameErrorWidget;
    private DropdownWidget genderWidget;
    private ErrorWidget genderErrorWidget;
    private Widget ageWidget;
    private ErrorWidget ageErrorWidget;
    private Widget birthDateWidget;
    private ErrorWidget birthDateErrorWidget;
    private Widget identifierTypeWidget;
    private Widget identifierTypeValueWidget;
    private ErrorWidget identifierTypeValueErrorWidget;
    private Widget identifierLocationWidget;
    private ErrorWidget identifierLocationErrorWidget;
    private AddressWidget addressWidget;

    private boolean required;

    public PatientDetailSubmissionElement(FormEntryContext context, Map<String, String> attributes) {
        createElement(context, attributes);
    }

    public void createElement(FormEntryContext context, Map<String, String> attributes) {
        String field = attributes.get("field");
        Patient existingPatient = context.getExistingPatient();

        // Required attribute defaults to true if not specified
        required = !"false".equalsIgnoreCase(attributes.get("required"));

        if (FIELD_PERSON_NAME.equalsIgnoreCase(field)) {
            nameWidget = new NameWidget();
            nameErrorWidget = new ErrorWidget();
            createWidgets(context, nameWidget, nameErrorWidget,
                    existingPatient != null && existingPatient.getPersonName() != null
                            ? existingPatient.getPersonName()
                            : null);
        } else if (FIELD_GENDER.equalsIgnoreCase(field)) {
            MessageSourceService msg = Context.getMessageSourceService();
            genderWidget = new DropdownWidget();
            genderErrorWidget = new ErrorWidget();
            genderWidget.addOption(new Option(msg.getMessage("Patient.gender.male"), "M", false));
            genderWidget.addOption(new Option(msg.getMessage("Patient.gender.female"), "F", false));
            createWidgets(context, genderWidget, genderErrorWidget,
                    existingPatient != null ? existingPatient.getGender() : null);
        } else if (FIELD_AGE.equalsIgnoreCase(field)) {
            ageWidget = new NumberFieldWidget(0d, 200d, false);
            ageErrorWidget = new ErrorWidget();
            createWidgets(context, ageWidget, ageErrorWidget,
                    existingPatient != null ? existingPatient.getAge() : null);
        } else if (FIELD_BIRTH_DATE.equalsIgnoreCase(field)) {
            birthDateWidget = new DateWidget();
            birthDateErrorWidget = new ErrorWidget();
            createWidgets(context, birthDateWidget, birthDateErrorWidget,
                    existingPatient != null ? existingPatient.getBirthdate() : null);
        } else if (FIELD_BIRTH_DATE_OR_AGE.equalsIgnoreCase(field)) {
            ageWidget = new NumberFieldWidget(0d, 200d, false);
            ageErrorWidget = new ErrorWidget();
            createWidgets(context, ageWidget, ageErrorWidget,
                    existingPatient != null ? existingPatient.getAge() : null);

            birthDateWidget = new DateWidget();
            birthDateErrorWidget = new ErrorWidget();
            createWidgets(context, birthDateWidget, birthDateErrorWidget,
                    existingPatient != null ? existingPatient.getBirthdate() : null);

        } else if (FIELD_IDENTIFIER.equalsIgnoreCase(field)) {

            PatientIdentifierType idType = HtmlFormEntryUtil
                    .getPatientIdentifierType(attributes.get("identifierTypeId"));

            identifierTypeValueWidget = new TextFieldWidget();
            identifierTypeValueErrorWidget = new ErrorWidget();
            String initialValue = null;
            if (existingPatient != null) {
                if (idType == null) {
                    if (existingPatient.getPatientIdentifier() != null) {
                        initialValue = existingPatient.getPatientIdentifier().getIdentifier();
                    }
                } else {
                    if (existingPatient.getPatientIdentifier(idType) != null) {
                        initialValue = existingPatient.getPatientIdentifier(idType).getIdentifier();
                    }
                }
            }
            createWidgets(context, identifierTypeValueWidget, identifierTypeValueErrorWidget, initialValue);

            if (idType != null) {
                identifierTypeWidget = new HiddenFieldWidget();
                createWidgets(context, identifierTypeWidget, null, idType.getId().toString());
            } else {
                identifierTypeWidget = new DropdownWidget();
                List<PatientIdentifierType> patientIdentifierTypes = HtmlFormEntryUtil.getPatientIdentifierTypes();

                for (PatientIdentifierType patientIdentifierType : patientIdentifierTypes) {
                    ((DropdownWidget) identifierTypeWidget).addOption(new Option(patientIdentifierType.getName(),
                            patientIdentifierType.getPatientIdentifierTypeId().toString(), false));
                }

                createWidgets(context, identifierTypeWidget, null,
                        existingPatient != null && existingPatient.getPatientIdentifier() != null
                                ? existingPatient.getPatientIdentifier().getIdentifierType().getId()
                                : null);
            }
        } else if (FIELD_IDENTIFIER_LOCATION.equalsIgnoreCase(field)) {
            identifierLocationWidget = new DropdownWidget();
            identifierLocationErrorWidget = new ErrorWidget();

            Location defaultLocation = existingPatient != null && existingPatient.getPatientIdentifier() != null
                    ? existingPatient.getPatientIdentifier().getLocation()
                    : null;
            defaultLocation = defaultLocation == null ? context.getDefaultLocation() : defaultLocation;
            identifierLocationWidget.setInitialValue(defaultLocation);

            List<Option> locationOptions = new ArrayList<Option>();
            for (Location location : Context.getLocationService().getAllLocations()) {
                Option option = new Option(location.getName(), location.getId().toString(),
                        location.equals(defaultLocation));
                locationOptions.add(option);
            }
            Collections.sort(locationOptions, new OptionComparator());

            // if initialValueIsSet=false, no initial/default location, hence this shows the 'select input' field as first option
            boolean initialValueIsSet = !(defaultLocation == null);
            ((DropdownWidget) identifierLocationWidget).addOption(
                    new Option(Context.getMessageSourceService().getMessage("htmlformentry.chooseALocation"), "",
                            !initialValueIsSet));
            if (!locationOptions.isEmpty()) {
                for (Option option : locationOptions) {
                    ((DropdownWidget) identifierLocationWidget).addOption(option);
                }
            }
            createWidgets(context, identifierLocationWidget, identifierLocationErrorWidget, defaultLocation);
        }

        else if (FIELD_ADDRESS.equalsIgnoreCase(field)) {
            addressWidget = new AddressWidget();
            createWidgets(context, addressWidget, null,
                    existingPatient != null ? existingPatient.getPersonAddress() : null);
        }
    }

    private void createWidgets(FormEntryContext context, Widget fieldWidget, ErrorWidget errorWidget,
            Object initialValue) {
        context.registerWidget(fieldWidget);
        if (errorWidget != null) {
            context.registerErrorWidget(fieldWidget, errorWidget);
        }
        if (initialValue != null && StringUtils.hasText(initialValue.toString())) {
            fieldWidget.setInitialValue(initialValue);
        }
    }

    @Override
    public String generateHtml(FormEntryContext context) {
        StringBuilder sb = new StringBuilder();
        MessageSourceService mss = Context.getMessageSourceService();

        if (nameWidget != null) {
            sb.append(nameWidget.generateHtml(context));
            if (context.getMode() != Mode.VIEW)
                sb.append(nameErrorWidget.generateHtml(context));
        }

        if (genderWidget != null) {
            sb.append(genderWidget.generateHtml(context));
            if (context.getMode() != Mode.VIEW)
                sb.append(genderErrorWidget.generateHtml(context));
        }

        if (birthDateWidget != null) {
            if (ageWidget != null) {
                sb.append(mss.getMessage("Person.birthdate")).append(" ");
            }
            sb.append(birthDateWidget.generateHtml(context));
            if (context.getMode() != Mode.VIEW)
                sb.append(birthDateErrorWidget.generateHtml(context));
        }

        if (ageWidget != null) {
            if (birthDateWidget != null) {
                sb.append(" ").append(mss.getMessage("Person.age.or")).append(" ");
            }
            sb.append(ageWidget.generateHtml(context));
            if (context.getMode() != Mode.VIEW)
                sb.append(ageErrorWidget.generateHtml(context));
        }

        if (identifierTypeValueWidget != null) {
            sb.append(identifierTypeValueWidget.generateHtml(context));
            if (context.getMode() != Mode.VIEW) {
                // if value is required
                if (required) {
                    sb.append("<span class='required'>*</span>");
                }

                sb.append(" ");
                sb.append(identifierTypeValueErrorWidget.generateHtml(context));
            }
        }

        if (identifierTypeWidget != null) {
            if (identifierTypeValueWidget instanceof DropdownWidget) {
                sb.append(" ").append(mss.getMessage("PatientIdentifier.identifierType")).append(" ");
            }
            sb.append(identifierTypeWidget.generateHtml(context)).append(" ");
        }

        if (identifierLocationWidget != null) {
            sb.append(identifierLocationWidget.generateHtml(context));
            if (context.getMode() != Mode.VIEW)
                sb.append(identifierLocationErrorWidget.generateHtml(context));
        }

        if (addressWidget != null) {
            sb.append(addressWidget.generateHtml(context));
        }

        return sb.toString();
    }

    @Override
    public void handleSubmission(FormEntrySession session, HttpServletRequest request) {

        Patient patient = (Patient) session.getSubmissionActions().getCurrentPerson();

        if (patient == null) {
            throw new RuntimeException("Programming exception: person shouldn't be null");
        }

        FormEntryContext context = session.getContext();

        if (nameWidget != null) {
            PersonName name = (PersonName) nameWidget.getValue(context, request);

            if (patient != null) {
                if (!name.isPreferred()) {
                    PersonName currentPreferredName = context.getExistingPatient().getPersonName();
                    if (currentPreferredName != null) {
                        currentPreferredName.setPreferred(false);
                        currentPreferredName.setVoided(true);
                    }
                }
            }

            // HACK: we need to set the date created and uuid here as a hack around a hibernate flushing issue (see saving the Patient in FormEntrySession applyActions())
            if (name.getDateCreated() == null) {
                name.setDateCreated(new Date());
            }
            if (name.getUuid() == null) {
                name.setUuid(UUID.randomUUID().toString());
            }

            name.setPreferred(true);
            patient.addName(name);
        }

        if (genderWidget != null) {
            String value = (String) genderWidget.getValue(context, request);
            patient.setGender(value);
        }

        if (ageWidget != null) {
            Double value = (Double) ageWidget.getValue(context, request);
            if (value != null)
                calculateBirthDate(patient, null, value);
        }

        if (birthDateWidget != null) {
            Date value = (Date) birthDateWidget.getValue(context, request);
            if (value != null) {
                calculateBirthDate(patient, value, null);
            }
        }

        if (identifierTypeValueWidget != null && identifierTypeWidget != null) {
            String identifier = (String) identifierTypeValueWidget.getValue(context, request);
            PatientIdentifierType identifierType = getIdentifierType(
                    (String) identifierTypeWidget.getValue(context, request));

            // Look for an existing identifier of this type
            PatientIdentifier patientIdentifier = patient.getPatientIdentifier(identifierType);

            if (StringUtils.hasText(identifier)) {
                // No existing identifier of this type, so create new
                if (patientIdentifier == null) {
                    patientIdentifier = new PatientIdentifier();
                    patientIdentifier.setIdentifierType(identifierType);

                    // HACK: we need to set the date created  and uuid here as a hack around a hibernate flushing issue (see saving the Patient in FormEntrySession applyActions())
                    patientIdentifier.setDateChanged(new Date());
                    patientIdentifier.setUuid(UUID.randomUUID().toString());

                    // For 1.9+ onwards patients require a preferred identifier
                    if (patient.getPatientId() == null) {
                        patientIdentifier.setPreferred(true);
                    }

                    patient.addIdentifier(patientIdentifier);
                }

                if (!identifier.equals(patientIdentifier.getIdentifier())
                        || !identifierType.equals(patientIdentifier.getIdentifierType())) {
                    validateIdentifier(identifierType.getId(), identifier);
                }

                patientIdentifier.setIdentifier(identifier);
            } else if (patientIdentifier != null) {
                // If this field is not required, then we interpret a blank value as a request to avoid any existing identifier
                session.getSubmissionActions().getIdentifiersToVoid().add(patientIdentifier);
            }
        }

        //
        // TODO current behavior always updates location of the preferred identifier rather than
        // a specific identifier type being edited by the identifier widget. But identifier location
        // widget isn't aware of the identifier type widget
        //
        if (identifierLocationWidget != null) {
            PatientIdentifier patientIdentifier = patient.getPatientIdentifier();
            if (patientIdentifier == null) {
                patientIdentifier = new PatientIdentifier();
                patient.addIdentifier(patientIdentifier);
            }

            Object locationString = identifierLocationWidget.getValue(context, request);
            Location location = (Location) HtmlFormEntryUtil.convertToType(locationString.toString().trim(),
                    Location.class);
            patientIdentifier.setLocation(location);
            patientIdentifier.setPreferred(true);

        }

        if (addressWidget != null) {
            PersonAddress personAddress = (PersonAddress) addressWidget.getValue(context, request);
            if (context.getMode() == Mode.EDIT) {
                if (!personAddress.isPreferred()) {
                    PersonAddress currentPreferredAddress = context.getExistingPatient().getPersonAddress();
                    currentPreferredAddress.setPreferred(false);
                    currentPreferredAddress.setVoided(true);
                }
            }
            personAddress.setPreferred(true);
            patient.addAddress(personAddress);
        }

        session.getSubmissionActions().setPatientUpdateRequired(true);
    }

    private void validateIdentifier(Integer identifierType, String identifier) {
        if (identifierType != null && identifier != null) {
            try {
                PatientIdentifier pi = new PatientIdentifier();

                pi.setIdentifier(identifier);
                pi.setIdentifierType(getIdentifierType(identifierType.toString()));

                // note that this is a bit of a hack; we can't call the PatientIdentifierValidator.validateIdentifier(identifier) method
                // because it (as of 1.8) also tests to make sure that an identifier has a location associated with it; when validating an
                // individual identifier widget, there will be no location because the location is collected in another widget
                PatientIdentifierValidator.validateIdentifier(pi.getIdentifier(), pi.getIdentifierType());

                if (Context.getPatientService().isIdentifierInUseByAnotherPatient(pi)) {
                    throw new IdentifierNotUniqueException(Context.getMessageSourceService().getMessage(
                            "PatientIdentifier.error.notUniqueWithParameter", new Object[] { pi.getIdentifier() },
                            Context.getLocale()));
                }
            } catch (Exception e) {
                throw new ValidationException(e.getMessage());
            }
        }
    }

    private PatientIdentifierType getIdentifierType(String id) {
        PatientIdentifierType patientIdentifierType = Context.getPatientService()
                .getPatientIdentifierType(new Integer(id));
        if (patientIdentifierType == null) {
            throw new RuntimeException("Invalid identifierTypeId given " + id);
        }
        return patientIdentifierType;
    }

    @SuppressWarnings("unused")
    @Override
    public Collection<FormSubmissionError> validateSubmission(FormEntryContext context,
            HttpServletRequest request) {
        List<FormSubmissionError> ret = new ArrayList<FormSubmissionError>();

        List<FormSubmissionError> ageOrBirthdDateErrorMessage = new ArrayList<FormSubmissionError>();

        validateMandatoryField(context, request, genderWidget, genderErrorWidget, ret);

        if (required) {
            validateMandatoryField(context, request, identifierTypeValueWidget, identifierTypeValueErrorWidget,
                    ret);
        }

        validateMandatoryField(context, request, identifierLocationWidget, identifierLocationErrorWidget, ret);

        if (nameWidget != null) {
            PersonName personName = nameWidget.getValue(context, request);
            if (!StringUtils.hasText(personName.getGivenName())
                    || !StringUtils.hasText(personName.getFamilyName())) {
                ret.add(new FormSubmissionError(context.getFieldName(nameErrorWidget),
                        Context.getMessageSourceService().getMessage("htmlformentry.error.name.required")));
            }
        }

        if (ageWidget != null && validateMandatoryField(context, request, ageWidget, ageErrorWidget,
                ageOrBirthdDateErrorMessage)) {
            try {
                Number value = (Number) ageWidget.getValue(context, request);
            } catch (Exception e) {
                ret.add(new FormSubmissionError(context.getFieldName(ageErrorWidget), e.getMessage()));
            }
        }

        if (birthDateWidget != null && validateMandatoryField(context, request, birthDateWidget,
                birthDateErrorWidget, ageOrBirthdDateErrorMessage)) {
            try {
                if (birthDateWidget.getValue(context, request) != null
                        && OpenmrsUtil.compare((Date) birthDateWidget.getValue(context, request), new Date()) > 0) {
                    ret.add(new FormSubmissionError(context.getFieldName(birthDateErrorWidget),
                            Context.getMessageSourceService().getMessage("htmlformentry.error.cannotBeInFuture")));
                }
            } catch (Exception e) {
                ret.add(new FormSubmissionError(context.getFieldName(birthDateErrorWidget), e.getMessage()));
            }
        }

        if (ageWidget != null && birthDateWidget != null) {
            if (ageOrBirthdDateErrorMessage.size() > 1) {
                ret.add(new FormSubmissionError(context.getFieldName(ageErrorWidget),
                        Context.getMessageSourceService().getMessage("Person.birthdate.required")));
            }
        } else {
            ret.addAll(ageOrBirthdDateErrorMessage);
        }

        if (identifierTypeWidget != null && identifierTypeValueWidget != null) {
            String identifierTypeId = (String) identifierTypeWidget.getValue(context, request);
            String identifierValue = (String) identifierTypeValueWidget.getValue(context, request);
            if (StringUtils.hasText(identifierValue)) {
                try {
                    validateIdentifier(Integer.getInteger(identifierTypeId), identifierValue);
                } catch (Exception e) {
                    ret.add(new FormSubmissionError(context.getFieldName(identifierTypeValueErrorWidget),
                            e.getMessage()));
                }
            }
        }

        return ret;
    }

    private boolean validateMandatoryField(FormEntryContext context, HttpServletRequest request, Widget widget,
            ErrorWidget errorWidget, List<FormSubmissionError> ret) {

        try {
            if (widget != null) {
                Object obj = widget.getValue(context, request);
                if (obj == null || (obj instanceof String && !StringUtils.hasText((String) obj))) {
                    throw new Exception("htmlformentry.error.required");
                }
            }
        } catch (Exception ex) {
            ret.add(new FormSubmissionError(context.getFieldName(errorWidget),
                    Context.getMessageSourceService().getMessage(ex.getMessage())));
            return false;
        }

        return true;
    }

    /**
     * If there's a birthdate specified
     */
    private void calculateBirthDate(Person person, Date date, Double age) {
        Date birthdate = null;
        boolean birthdateEstimated = false;
        if (date != null) {
            birthdate = date;
            //if you have a previous date that's marked as estimated and date does not change -->  keep it that way
            //if you have a previous date that's marked as estimated but date changes --> not estimated
            //if new --> not estimated
            //if existing and not estimated --> not estimated
            birthdateEstimated = person.getBirthdate() != null && person.getBirthdateEstimated() != null
                    && person.getBirthdate().equals(date) ? person.getBirthdateEstimated() : false;
        } else if (age != null) {
            try {
                Double ageRemainder = BigDecimal.valueOf(age).subtract(BigDecimal.valueOf(Math.floor(age)))
                        .doubleValue();
                if (ageRemainder.equals(Double.valueOf(0)))
                    person.setBirthdateFromAge(age.intValue(), new Date()); //default to usual behavior from core
                else { //a decimal was entered
                    Calendar c = Calendar.getInstance();
                    c.setTime(new Date());
                    c.add(Calendar.DAY_OF_MONTH, -Double.valueOf((ageRemainder * 365)).intValue()); //if patient is 2.2 years old, patient was 2.0 years 2.2 - (.2*365) days ago
                    c.add(Calendar.YEAR, -1 * Double.valueOf(Math.floor(age)).intValue());
                    birthdate = c.getTime();
                }
                birthdateEstimated = true;
            } catch (NumberFormatException e) {
                throw new RuntimeException("Error getting date from age", e);
            }
        } else {
            throw new IllegalArgumentException("You must provide either an age or a birthdate for this patient.");
        }
        if (birthdate != null)
            person.setBirthdate(birthdate);
        person.setBirthdateEstimated(birthdateEstimated);
    }
}