/** * 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.web.controller.encounter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.Concept; import org.openmrs.Encounter; import org.openmrs.Field; import org.openmrs.FormField; import org.openmrs.Obs; import org.openmrs.api.context.Context; import org.openmrs.util.OpenmrsUtil; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; /** * This is the java controller for the /openmrs/admin/encounters/encounterDisplay.list page. The jsp * for this display popup is located at /web/WEB-INF/view/encounters/encounterDisplay.jsp */ public class EncounterDisplayController implements Controller { protected final Log log = LogFactory.getLog(getClass()); /** * The page that obs are put on if they are not given a page number in their associated * FormField object */ public static final Integer DEFAULT_PAGE_NUMBER = 999; /** * This is the method called to produce the data and objects for the jsp page * * @see org.springframework.web.servlet.mvc.Controller#handleRequest(javax.servlet.http.HttpServletRequest, * javax.servlet.http.HttpServletResponse) */ public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { Map<String, Object> model = new HashMap<String, Object>(); if (Context.isAuthenticated()) { String encounterId = request.getParameter("encounterId"); if (encounterId == null || encounterId.length() == 0) throw new IllegalArgumentException("encounterId is a required parameter"); model.put("encounterId", Integer.valueOf(encounterId)); Encounter encounter = Context.getEncounterService().getEncounter(Integer.valueOf(encounterId)); model.put("encounter", encounter); List<FormField> formFields = new ArrayList<FormField>(); if (encounter.getForm() != null && encounter.getForm().getFormFields() != null) formFields.addAll(encounter.getForm().getFormFields()); // mapping from concept to FieldHolder. there should be only one // fieldholder (aka one row) per unique concept in the obs for an encounter // these are the rows that will be returned and displayed // the values of this map will be returned and displayed as the rows // in the jsp view Map<Concept, FieldHolder> rowMapping = new HashMap<Concept, FieldHolder>(); // loop over all obs in this encounter for (Obs obs : encounter.getObsAtTopLevel(false)) { // the question for this obs Concept conceptForThisObs = obs.getConcept(); // try to get a unique formfield for this concept. If one exists, // remove that formfield from the list of formfields FormField formField = popFormFieldForConcept(formFields, conceptForThisObs); // try to get a previously added row FieldHolder fieldHolder = rowMapping.get(conceptForThisObs); if (fieldHolder != null) { // there is already a row that uses the same concept as its // question. lets put this obs in that same row fieldHolder.addObservation(obs); } else { // if we don't have a row for this concept yet, create one // if this observation was added to the encounter magically // (meaning there isn't a formfield for it) create a generic // formfield to use with this obs if (formField == null) { formField = new FormField(); formField.setPageNumber(DEFAULT_PAGE_NUMBER); formField.setFieldNumber(null); } fieldHolder = new FieldHolder(formField, obs); // add this row to the list of all possible rows rowMapping.put(conceptForThisObs, fieldHolder); } } // now that we're done processing all of the obs, get all of the // rows that we will return // this is not the object we will give to the jsp view, the jsp // view only sees the rows on a per page basis List<FieldHolder> rows = new ArrayList<FieldHolder>(); rows.addAll(rowMapping.values()); Collections.sort(rows); String usePages = Context.getAdministrationService().getGlobalProperty("dashboard.encounters.usePages", "true") .toLowerCase(); if (usePages.equals("smart")) { // if more than 50% of fields have page numbers, then use pages int with = 0; int without = 0; for (FieldHolder holder : rows) { if (holder.getPageNumber() == DEFAULT_PAGE_NUMBER) ++without; else ++with; } usePages = "" + (with > without); } // this is a mapping from page number to list of rows on that page // this is the object returned to the view that should be looped over // and displayed. Each value part of the key-value pair is sorted // according to FieldHolder.compareTo Map<Integer, List<FieldHolder>> pages = new HashMap<Integer, List<FieldHolder>>(); if (Boolean.valueOf(usePages).booleanValue()) { // if we're doing pages // loop over all of the rows and put them in pages for (FieldHolder row : rows) { Integer pageNumber = row.getPageNumber(); List<FieldHolder> thisPage = pages.get(pageNumber); // if a page (set of rows) for this pagNumber doesn't exist yet, create it if (thisPage == null) { thisPage = new ArrayList<FieldHolder>(); pages.put(pageNumber, thisPage); } // add this row to its page thisPage.add(row); } } else { // if we're not doing pages, put all rows on the first page List<FieldHolder> pageOneRows = new ArrayList<FieldHolder>(); pageOneRows.addAll(rows); pages.put(0, pageOneRows); } model.put("showBlankFields", "true".equals(request.getParameter("showBlankFields"))); model.put("usePages", Boolean.valueOf(usePages).booleanValue()); model.put("pageNumbers", pages.keySet()); model.put("pages", pages); model.put("orders", encounter.getOrders()); model.put("locale", Context.getLocale()); } return new ModelAndView("/encounters/encounterDisplay", "model", model); } /** * This will look through all form fields and find the one that has a concept that matches the * given concept If one is found, that formfield is removed from the given list * <code>formFields</code> If there are none found, null is returned. * * @param formFields list of FormFields to rifle through * @param conceptToSearchFor concept to look for in <code>formFields</code> * @return FormField object from <code>formFields</code> or null */ private FormField popFormFieldForConcept(List<FormField> formFields, Concept conceptToSearchFor) { //drop out if a null concept was passed in if (conceptToSearchFor == null) return null; Integer conceptId = conceptToSearchFor.getConceptId(); // look through all formFields for this concept for (FormField formField : formFields) { Field field = formField.getField(); if (field != null) { Concept otherConcept = field.getConcept(); if (otherConcept != null && conceptId.equals(otherConcept.getConceptId())) { // found a FormField. Remove it from the list and // return it formFields.remove(formField); return formField; } } } // no formField with concept = conceptToSearchFor was found return null; } /** * This class represents one row to display on the jsp page */ public class FieldHolder implements Comparable<FieldHolder> { /** * The formfield that represents the labeling of this row This is also used in the sorting. * See * {@link #compareTo(org.openmrs.web.controller.encounter.EncounterDisplayController.FieldHolder)} * See {@link #getLabel()} for the labeling */ private FormField formField = null; /** * these are the rows in the obsGroup table If this is not an obs grouper, this will contain * only one obs */ private List<Obs> obs; /** * these are the column names in the obsGroup table */ private LinkedHashSet<Concept> groupMemberConcepts; /** * A row must be created with both a FormField to act as its label and an obs that is the * first of possibly several rows to display * * @throws Exception if the obsToAdd is an invalid type (meaning its contained in another * obs group) */ public FieldHolder(FormField formField, Obs obsToAdd) throws Exception { obs = new LinkedList<Obs>(); groupMemberConcepts = new LinkedHashSet<Concept>(); this.formField = formField; addObservation(obsToAdd); if (obsToAdd.getObsGroup() != null) { throw new Exception( "FieldHolders only contain top-level obs. This obs is contained in some other group, it is added automagically there. " + obsToAdd); } } /** * public getter method for the formfield that is the label for this row * * @return FormField for this row */ public FormField getFormField() { return formField; } /** * public getter for the columns (that are unique concepts across all obs in this * FieldHolder) * * @return unique concepts across these obs */ public Set<Concept> getGroupMemberConcepts() { return groupMemberConcepts; } /** * public getter for the obs that are the different rows for this FieldHolder. If this isn't * a grouping type of row, the set could still have multiple obs in it because there are * multiple questions (obs) in this encounter that are asking the same thing (same concept) * * @return List of obs for this row */ public List<Obs> getObs() { return obs; } /** * Convenience method to know whether this row is an obs grouping and should be displayed * with a table or if its a single one-and-done obs and should just be shown as one value * * @return true/false whether this holder is for an obs grouping */ public boolean isObsGrouping() { if (obs == null || groupMemberConcepts == null) return false; return groupMemberConcepts.size() > 1 || obs.get(0).isObsGrouping(); } /** * List of columns for each obs grouper in this fieldholder. Not every grouper will have * every column (concept), so some cells will be null. The columns are determined by * getObsGroupConcepts() * * @return a matrix of columns */ public Map<Obs, List<List<Obs>>> getObsGroupMatrix() { Map<Obs, List<List<Obs>>> matrix = new HashMap<Obs, List<List<Obs>>>(); for (Obs obsGrouper : obs) { List<List<Obs>> obsRow = new LinkedList<List<Obs>>(); // create a hashmap of concept-->obs for these groupedObs Map<Concept, List<Obs>> conceptToObsMap = new HashMap<Concept, List<Obs>>(); for (Obs groupedObs : obsGrouper.getGroupMembers()) { List<Obs> obsMatchingThisConcept = conceptToObsMap.get(groupedObs.getConcept()); if (obsMatchingThisConcept == null) obsMatchingThisConcept = new LinkedList<Obs>(); obsMatchingThisConcept.add(groupedObs); conceptToObsMap.put(groupedObs.getConcept(), obsMatchingThisConcept); } // loop over each possible concept and put the obs in the // row in the order of the columns. if no obs is found, // the cell is blank (null) for (Concept concept : groupMemberConcepts) { // loop over each obs in this group obsRow.add(conceptToObsMap.get(concept)); } matrix.put(obsGrouper, obsRow); } return matrix; } /** * Add another obs grouper to this row This method shouldn't be called with obs that are * within another grouped obs. This should only be called with the parent obs grouper. * * @param obsToAdd Obs that should be an obs grouper */ public void addObservation(Obs obsToAdd) { // if we are in an obs grouping, make sure that we know // about each concept in each of the underling grouped obs if (obsToAdd.isObsGrouping()) { for (Obs groupedObs : obsToAdd.getGroupMembers()) { groupMemberConcepts.add(groupedObs.getConcept()); } } else { // this is not a grouping, but its concept is being used // in another row, so just add it to the list of concepts // in this row. The values won't be spit out in a table // but they will be displayed together groupMemberConcepts.add(obsToAdd.getConcept()); } // add the given obs to the list of obs for this row obs.add(obsToAdd); } /** * Use this row's FormField to make comparisons about where to put it in relation to the * FieldHolder's * * @see org.openmrs.FormField#compareTo(org.openmrs.FormField) */ public int compareTo(FieldHolder other) { int temp = OpenmrsUtil .compareWithNullAsGreatest(formField.getPageNumber(), other.getFormField().getPageNumber()); if (temp == 0) temp = OpenmrsUtil.compareWithNullAsGreatest(formField.getFieldNumber(), other.getFormField() .getFieldNumber()); if (temp == 0) temp = OpenmrsUtil.compareWithNullAsGreatest(formField.getFieldPart(), other.getFormField().getFieldPart()); if (temp == 0 && formField.getPageNumber() == null && formField.getFieldNumber() == null && formField.getFieldPart() == null) temp = OpenmrsUtil .compareWithNullAsGreatest(formField.getSortWeight(), other.getFormField().getSortWeight()); return temp; } /** * Convenience method to get the label that this field should have. This is produced from * the formfield associated with this row * * @return String representing the label to be displayed for this row */ public String getLabel() { String label = ""; if (formField.getFieldNumber() != null) label = formField.getFieldNumber() + "."; if (formField.getFieldPart() != null) label += formField.getFieldPart(); if (label.equals("")) return "--"; else return label; } /** * Convenience method to get the page number for this row. This just checks the associated * form field for its assigned page number. If the formfield doesn't have a page, this row * is thrown on the DEFAULT_PAGE_NUMBERth page. * * @return page number for this row */ public Integer getPageNumber() { if (formField == null || formField.getPageNumber() == null) return DEFAULT_PAGE_NUMBER; return formField.getPageNumber(); } } }