org.openmrs.reporting.export.DataExportFunctions.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.reporting.export.DataExportFunctions.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.reporting.export;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Cohort;
import org.openmrs.Concept;
import org.openmrs.Drug;
import org.openmrs.DrugOrder;
import org.openmrs.Encounter;
import org.openmrs.EncounterType;
import org.openmrs.Location;
import org.openmrs.Obs;
import org.openmrs.Patient;
import org.openmrs.PatientIdentifier;
import org.openmrs.PatientIdentifierType;
import org.openmrs.PatientProgram;
import org.openmrs.Program;
import org.openmrs.Relationship;
import org.openmrs.RelationshipType;
import org.openmrs.User;
import org.openmrs.api.APIException;
import org.openmrs.api.ConceptService;
import org.openmrs.api.EncounterService;
import org.openmrs.api.PatientService;
import org.openmrs.api.PatientSetService;
import org.openmrs.api.context.Context;
import org.openmrs.report.EvaluationContext;
import org.openmrs.reporting.PatientFilter;
import org.openmrs.reporting.PatientSearchReportObject;
import org.openmrs.util.OpenmrsUtil;

/**
 * @deprecated see reportingcompatibility module
 */
@Deprecated
public class DataExportFunctions {

    public final Log log = LogFactory.getLog(this.getClass());

    protected Integer patientId;

    protected Patient patient;

    //protected PatientSet patientSet;
    protected Cohort patientSet;

    protected boolean isAllPatients = false;

    private Integer patientCounter = 0; // used for garbage collection (Clean up every x patients)

    protected String separator = "   ";

    protected DateFormat dateFormatLong = null;

    protected DateFormat dateFormatShort = null;

    protected DateFormat dateFormatYmd = null;

    protected Map<String, DateFormat> formats = new HashMap<String, DateFormat>();

    public Date currentDate = new Date();

    protected Calendar calendar = null;

    // Map<EncounterType, Map<patientId, Encounter>>
    protected Map<String, Map<Integer, ?>> patientEncounterMap = new HashMap<String, Map<Integer, ?>>();

    // Map<PatientIdentifierType, Map<patientId, PatientIdentifier>>
    protected Map<String, Map<Integer, PatientIdentifier>> patientIdentifierMap = new HashMap<String, Map<Integer, PatientIdentifier>>();

    // Map<EncounterType, Map<patientId, Encounter>>
    protected Map<String, Map<Integer, ?>> patientFirstEncounterMap = new HashMap<String, Map<Integer, ?>>();

    protected Map<String, Concept> conceptNameMap = new HashMap<String, Concept>();

    // Map<Concept, Map<obsId, List<Obs values>>>
    //protected Map<Concept, Map<Integer, List<Object>>> conceptObsMap = new HashMap<Concept, Map<Integer, List<Object>>>();

    // Map<conceptId + attr, Map<patientId, List<List<Obs values>>>>
    protected Map<String, Map<Integer, List<List<Object>>>> conceptAttrObsMap = new HashMap<String, Map<Integer, List<List<Object>>>>();

    // Map<RelationshipType, Map<patientId, List<Relationship>>>
    protected Map<String, Map<Integer, List<Relationship>>> relationshipMap = new HashMap<String, Map<Integer, List<Relationship>>>();

    // Map<Program.name, Map<patientId, PatientProgram>>
    protected Map<String, Map<Integer, PatientProgram>> programMap = new HashMap<String, Map<Integer, PatientProgram>>();

    // Map<name of drug set, Map<patientId, List<DrugOrder>>>
    protected Map<String, Map<Integer, List<DrugOrder>>> drugOrderMap = new HashMap<String, Map<Integer, List<DrugOrder>>>();

    // Map<name of drug set, Map<patientId, List<DrugOrder>>>
    protected Map<String, Map<Integer, List<DrugOrder>>> currentDrugOrderMap = new HashMap<String, Map<Integer, List<DrugOrder>>>();

    // Map<tablename+columnname, Map<patientId, columnvalue>>
    protected Map<String, Map<Integer, Object>> patientAttributeMap = new HashMap<String, Map<Integer, Object>>();

    // Map<tablename+columnname, Map<personId, columnvalue>>
    protected Map<String, Map<Integer, Object>> personAttributeMap = new HashMap<String, Map<Integer, Object>>();

    // Map<key, Collection<personId>>, where key is like "Cohort.1" or "Filter.3"
    protected Map<String, Collection<Integer>> cohortMap = new HashMap<String, Collection<Integer>>();

    protected PatientSetService patientSetService;

    protected PatientService patientService;

    protected ConceptService conceptService;

    protected EncounterService encounterService;

    protected Locale locale = null;

    // Constructors

    public DataExportFunctions(Patient p) {
        this(p.getPatientId());
    }

    public DataExportFunctions(Integer patientId) {
        this();
        setPatientId(patientId);
    }

    public DataExportFunctions() {
        this.patientSetService = Context.getPatientSetService();
        this.patientService = Context.getPatientService();
        this.conceptService = Context.getConceptService();
        this.encounterService = Context.getEncounterService();

        locale = Context.getLocale();
        dateFormatLong = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
        dateFormatShort = Context.getDateFormat();
        dateFormatYmd = new SimpleDateFormat("yyyy-MM-dd", locale);
    }

    @SuppressWarnings("unchecked")
    public void clear() {
        for (Map map : patientEncounterMap.values())
            map.clear();
        patientEncounterMap.clear();
        patientEncounterMap = null;
        for (Map map : patientIdentifierMap.values())
            map.clear();
        patientIdentifierMap.clear();
        patientIdentifierMap = null;
        for (Map map : patientFirstEncounterMap.values())
            map.clear();
        patientFirstEncounterMap.clear();
        patientFirstEncounterMap = null;
        conceptNameMap.clear();
        for (Map map : conceptAttrObsMap.values())
            map.clear();
        conceptAttrObsMap.clear();
        for (Map map : relationshipMap.values())
            map.clear();
        relationshipMap.clear();
        for (Map map : programMap.values())
            map.clear();
        programMap.clear();
        for (Map map : drugOrderMap.values())
            map.clear();
        drugOrderMap.clear();
        for (Map map : currentDrugOrderMap.values())
            map.clear();
        currentDrugOrderMap.clear();
        currentDrugOrderMap = null;
        for (Map map : patientAttributeMap.values())
            map.clear();
        patientAttributeMap.clear();
        patientAttributeMap = null;

        patientSetService = null;
        patientService = null;
        conceptService = null;
        encounterService = null;
    }

    /**
     * Called when garbage collecting this class
     * 
     * @see java.lang.Object#finalize()
     */
    @Override
    protected void finalize() throws Throwable {
        log.debug("GC is collecting the data export functions..." + this);
        super.finalize();
    }

    /**
     * @return Returns the patient.
     */
    public Patient getPatient() {
        if (patient == null)
            patient = patientService.getPatient(patientId);

        return patient;
    }

    /**
     * @param patient The patient to set.
     */
    public void setPatient(Patient patient) {
        this.patient = patient;
    }

    public Integer getPatientId() {
        return patientId;
    }

    public void setPatientId(Integer patientId) {
        // remove last patient from maps to allow for garbage collection
        if (this.patientId != null) {
            for (Map<Integer, ?> map : patientEncounterMap.values())
                map.remove(this.patientId);
            for (Map<Integer, ?> map : patientFirstEncounterMap.values())
                map.remove(this.patientId);
            for (Map<Integer, Object> map : patientAttributeMap.values())
                map.remove(this.patientId);
            for (Map<Integer, ?> map : patientIdentifierMap.values())
                map.remove(this.patientId);
        }

        // reclaim some memory
        garbageCollect();

        setPatient(null);
        this.patientId = patientId;
    }

    /**
     * Call the system garbage collecter. This method only calls every 500 patients
     */
    protected void garbageCollect() {
        if (patientCounter++ % 500 == 0) {
            System.gc();
            System.gc();
        }
    }

    /**
     * @return Returns the patientSet.
     */
    public Cohort getPatientSet() {
        return patientSet;
    }

    /**
     * @param patientSet The patientSet to set.
     */
    public void setPatientSet(Cohort patientSet) {
        this.patientSet = patientSet;
    }

    /**
     * @return the isAllPatients
     */
    public boolean isAllPatients() {
        return isAllPatients;
    }

    /**
     * @param isAllPatients the isAllPatients to set
     */
    public void setAllPatients(boolean isAllPatients) {
        this.isAllPatients = isAllPatients;
    }

    /**
     * @return Returns the separator.
     */
    public String getSeparator() {
        return separator;
    }

    /**
     * @param separator The separator to set.
     */
    public void setSeparator(String separator) {
        this.separator = separator;
    }

    public String getCohortMembership(Integer cohortId, String valueIfTrue, String valueIfFalse) {
        return getCohortHelper("C." + cohortId) ? valueIfTrue : valueIfFalse;
    }

    public String getCohortDefinitionMembership(Integer filterId, String valueIfTrue, String valueIfFalse) {
        return getCohortHelper("F." + filterId) ? valueIfTrue : valueIfFalse;
    }

    public String getPatientSearchMembership(Integer searchId, String valueIfTrue, String valueIfFalse) {
        return getCohortHelper("S." + searchId) ? valueIfTrue : valueIfFalse;
    }

    protected Boolean getCohortHelper(String key) {
        if (cohortMap.containsKey(key))
            return cohortMap.get(key).contains(getPatientId());

        //TODO try to cache the evaluation context
        EvaluationContext context = new EvaluationContext();

        log.debug("getting cohort/definition for key: " + key);
        //PatientSet ps = null;
        Cohort ps = null;
        if (key.startsWith("C.")) {
            ps = Context.getCohortService().getCohort(Integer.valueOf(key.substring(2)));
        } else if (key.startsWith("F.")) {
            PatientFilter pf = Context.getReportObjectService()
                    .getPatientFilterById(Integer.valueOf(key.substring(2)));
            ps = pf.filter(getPatientSet(), context);
        } else if (key.startsWith("S.")) {
            PatientSearchReportObject ro = (PatientSearchReportObject) Context.getReportObjectService()
                    .getReportObject(Integer.valueOf(key.substring(2)));
            PatientFilter pf = OpenmrsUtil.toPatientFilter(ro.getPatientSearch(), null);
            ps = pf.filter(getPatientSet(), context);
        } else {
            log.error("key = " + key);
        }
        Set<Integer> set = new HashSet<Integer>(ps.getMemberIds());
        cohortMap.put(key, set);

        return set.contains(getPatientId());
    }

    /**
     * @return Encounter last encounter of type <code>encounterType</code>
     * @param encounterType
     */
    public Encounter getLastEncounter(String encounterType) {
        if (patientEncounterMap.containsKey(encounterType))
            return (Encounter) patientEncounterMap.get(encounterType).get(getPatientId());

        log.debug("getting first encounters for type: " + encounterType);

        EncounterType type = null;
        if (!encounterType.equals(""))
            type = encounterService.getEncounterType(encounterType);

        Map<Integer, ?> encounterMap = patientSetService.getEncountersByType(getPatientSetIfNotAllPatients(), type);

        patientEncounterMap.put(encounterType, encounterMap);

        return (Encounter) encounterMap.get(getPatientId());
    }

    /**
     * Get the attribute (encounterDatetime, provider, encounterType, etc) from the most recent
     * encounter.
     * 
     * @param typeArray
     * @param attr
     * @return the encounter attribute
     */
    @SuppressWarnings("unchecked")
    public Object getLastEncounterAttr(Object typeArray, String attr) {

        List<String> types = (List<String>) typeArray;
        String key = OpenmrsUtil.join(types, ",") + "|" + attr;

        if (patientEncounterMap.containsKey(key))
            return patientEncounterMap.get(key).get(getPatientId());

        log.debug("getting first encounters for type: " + key);

        List<EncounterType> encounterTypes = new Vector<EncounterType>();

        // find the EncounterType objects for each type passed in
        for (String typeName : types) {
            EncounterType type = null;
            try {
                type = encounterService.getEncounterType(Integer.valueOf(typeName));
            } catch (Exception e) {
                /* pass */}
            ;

            if (type == null)
                type = encounterService.getEncounterType(typeName);

            if (type != null)
                encounterTypes.add(type);
        }

        Map<Integer, Object> encounterMap = patientSetService
                .getEncounterAttrsByType(getPatientSetIfNotAllPatients(), encounterTypes, attr);

        patientEncounterMap.put(key, encounterMap);

        return encounterMap.get(getPatientId());

    }

    /**
     * @return Encounter first encounter of type <code>encounterType</code>
     * @param encounterType
     */
    public Encounter getFirstEncounter(String encounterType) {
        if (patientFirstEncounterMap.containsKey(encounterType))
            return (Encounter) patientFirstEncounterMap.get(encounterType).get(getPatientId());

        log.debug("getting first encounters for type: " + encounterType);

        EncounterType type = null;
        if (!encounterType.equals(""))
            type = encounterService.getEncounterType(encounterType);

        Map<Integer, Encounter> encounterMap = patientSetService
                .getFirstEncountersByType(getPatientSetIfNotAllPatients(), type);

        patientFirstEncounterMap.put(encounterType, encounterMap);

        return encounterMap.get(getPatientId());
    }

    /**
     * Get an attribute (encounterDatetime, provider, etc) from the oldest encounter.
     * 
     * @param typeArray
     * @param attr
     * @return Object from the oldest encounter
     */
    @SuppressWarnings("unchecked")
    public Object getFirstEncounterAttr(Object typeArray, String attr) {

        List<String> types = (List<String>) typeArray;
        String key = OpenmrsUtil.join(types, ",") + "|" + attr;

        if (patientFirstEncounterMap.containsKey(key))
            return patientFirstEncounterMap.get(key).get(getPatientId());

        log.debug("getting first encounters for type: " + key);

        List<EncounterType> encounterTypes = new Vector<EncounterType>();

        // find the EncounterType objects for each type passed in
        for (String typeName : types) {
            EncounterType type = null;
            try {
                type = encounterService.getEncounterType(Integer.valueOf(typeName));
            } catch (Exception e) {
                /* pass */}
            ;

            if (type == null)
                type = encounterService.getEncounterType(typeName);

            if (type != null)
                encounterTypes.add(type);
        }

        Map<Integer, Object> encounterMap = patientSetService
                .getFirstEncounterAttrsByType(getPatientSetIfNotAllPatients(), encounterTypes, attr);

        patientFirstEncounterMap.put(key, encounterMap);

        return encounterMap.get(getPatientId());

    }

    // methods

    public Concept getConcept(String conceptName) throws Exception {
        if (conceptName == null)
            throw new Exception("conceptName cannot be null");

        if (conceptNameMap.containsKey(conceptName))
            return conceptNameMap.get(conceptName);

        //log.debug("getting concept object for name: " + conceptName);

        Concept c;
        try {
            Integer conceptId = Integer.valueOf(conceptName);
            c = conceptService.getConcept(conceptId);
        } catch (NumberFormatException e) {
            c = conceptService.getConceptByName(conceptName);
        }

        if (c == null)
            throw new APIException("A Concept with name or id '" + conceptName + "' was not found");

        conceptNameMap.put(conceptName, c);
        return c;
    }

    /*
    public List<Object> getObs(Concept c) {
       Map<Integer, List<Object>> patientIdObsMap;
       if (conceptObsMap.containsKey(c)) {
     patientIdObsMap = conceptObsMap.get(c);
       }
       else {
     log.debug("getting obs list for concept: " + c);
     patientIdObsMap = Context.getPatientSetService().getObservationsValues(getPatientSet(), c);
     conceptObsMap.put(c, patientIdObsMap);
       }
       return patientIdObsMap.get(patientId);
    }
    */

    public List<List<Object>> getObsWithValues(Concept c, List<String> attrs) {
        if (attrs == null)
            attrs = new Vector<String>();

        String key = c.getConceptId() + "";
        Map<Integer, List<List<Object>>> patientIdObsMap = conceptAttrObsMap.get(key);
        if (patientIdObsMap == null) {
            //log.debug("getting obs list for concept: " + c + " and attr: " + attr);
            patientIdObsMap = patientSetService.getObservationsValues(getPatientSetIfNotAllPatients(), c, attrs);
            conceptAttrObsMap.put(key, patientIdObsMap);
        }
        return patientIdObsMap.get(patientId);
    }

    /**
     * Gets a patient program given a program ID or program name.
     * 
     * @param programIdOrName the identifier or name of the program
     * @return PatientProgram by the program ID or name
     */
    public PatientProgram getProgram(String programIdOrName) {

        Map<Integer, PatientProgram> patientIdProgramMap;
        if (programMap.containsKey(programIdOrName)) {
            patientIdProgramMap = programMap.get(programIdOrName);
        } else {

            Program program = null;

            //
            // Ticket #912 - Fixed by adding some code to lookup the program by ID
            //
            try {
                Integer programId = Integer.parseInt(programIdOrName);
                program = Context.getProgramWorkflowService().getProgram(programId);
            } catch (NumberFormatException e) {
                /* ignore error because we're going to look the program up by name */}

            if (program == null) {
                program = Context.getProgramWorkflowService().getProgramByName(programIdOrName);
            }
            patientIdProgramMap = patientSetService.getPatientPrograms(getPatientSetIfNotAllPatients(), program);
            programMap.put(programIdOrName, patientIdProgramMap);
        }
        return patientIdProgramMap.get(patientId);
    }

    public List<DrugOrder> getCurrentDrugOrders(String drugSetName) {
        Map<Integer, List<DrugOrder>> patientIdDrugOrderMap;
        if (currentDrugOrderMap.containsKey(drugSetName)) {
            patientIdDrugOrderMap = currentDrugOrderMap.get(drugSetName);
        } else {
            Concept drugSet = conceptService.getConceptByName(drugSetName);
            patientIdDrugOrderMap = patientSetService.getCurrentDrugOrders(getPatientSetIfNotAllPatients(),
                    drugSet);
            currentDrugOrderMap.put(drugSetName, patientIdDrugOrderMap);
        }
        return patientIdDrugOrderMap.get(patientId);
    }

    public String getCurrentDrugNames(String drugSetName) {
        List<DrugOrder> patientOrders = getCurrentDrugOrders(drugSetName);
        if (patientOrders == null)
            return "";
        StringBuilder ret = new StringBuilder();
        for (Iterator<DrugOrder> i = patientOrders.iterator(); i.hasNext();) {
            DrugOrder o = i.next();
            if (o.getDrug() != null)
                ret.append(o.getDrug().getName());
            else
                ret.append(o.getConcept().getBestName(Context.getLocale()).getName());
            if (i.hasNext())
                ret.append(" ");
        }
        return ret.toString();
    }

    public List<DrugOrder> getDrugOrders(String drugSetName) {
        Map<Integer, List<DrugOrder>> patientIdDrugOrderMap;
        if (drugOrderMap.containsKey(drugSetName)) {
            patientIdDrugOrderMap = drugOrderMap.get(drugSetName);
        } else {
            Concept drugSet = conceptService.getConceptByName(drugSetName);
            patientIdDrugOrderMap = patientSetService.getDrugOrders(getPatientSetIfNotAllPatients(), drugSet);
            drugOrderMap.put(drugSetName, patientIdDrugOrderMap);
        }
        return patientIdDrugOrderMap.get(patientId);
    }

    public Date getEarliestDrugStart(String drugSetName) {
        List<DrugOrder> patientOrders = getDrugOrders(drugSetName);
        if (patientOrders == null)
            return null;
        Date earliest = null;
        for (DrugOrder o : patientOrders) {
            if (earliest == null || OpenmrsUtil.compareWithNullAsLatest(o.getStartDate(), earliest) < 0)
                earliest = o.getStartDate();
        }
        return earliest;
    }

    public List<Relationship> getRelationships(String relationshipTypeName) {
        Map<Integer, List<Relationship>> patientIdRelationshipMap;
        if (relationshipMap.containsKey(relationshipTypeName)) {
            patientIdRelationshipMap = relationshipMap.get(relationshipTypeName);
        } else {
            //log.debug("getting relationship list for type: " + relationshipTypeName);
            RelationshipType relType = Context.getPersonService().getRelationshipTypeByName(relationshipTypeName);
            patientIdRelationshipMap = patientSetService.getRelationships(getPatientSetIfNotAllPatients(), relType);
            relationshipMap.put(relationshipTypeName, patientIdRelationshipMap);
        }
        return patientIdRelationshipMap.get(patientId);
    }

    // TODO: revisit this if we change our terminology for relationships
    public String getRelationshipNames(String relationshipTypeName) {
        List<Relationship> rels = getRelationships(relationshipTypeName);
        if (rels == null || rels.size() == 0) {
            return "";
        } else {
            StringBuilder sb = new StringBuilder();
            for (Iterator<Relationship> i = rels.iterator(); i.hasNext();) {
                Relationship r = i.next();
                sb.append(r.getPersonA().getPersonName().getFullName());
                if (i.hasNext())
                    sb.append(" ");
            }
            return sb.toString();
        }
    }

    // TODO: revisit this if we change our terminology for relationships
    public String getRelationshipIds(String relationshipTypeName) {
        List<Relationship> rels = getRelationships(relationshipTypeName);
        if (rels == null || rels.size() == 0) {
            return "";
        } else {
            StringBuilder sb = new StringBuilder();
            for (Iterator<Relationship> i = rels.iterator(); i.hasNext();) {
                Relationship r = i.next();
                r.getPersonA().toString();
                if (i.hasNext())
                    sb.append(" ");
            }
            return sb.toString();
        }
    }

    public String getRelationshipIdentifiers(String relationshipTypeName) {
        List<Relationship> rels = getRelationships(relationshipTypeName);
        if (rels == null || rels.size() == 0) {
            return "";
        } else {
            StringBuilder sb = new StringBuilder();
            for (Iterator<Relationship> i = rels.iterator(); i.hasNext();) {
                Relationship r = i.next();
                Patient p = patientService.getPatient(r.getPersonA().getPersonId());
                if (p != null)
                    sb.append("Patient " + p.getPatientIdentifier());
                if (i.hasNext())
                    sb.append(" ");
            }
            return sb.toString();
        }
    }

    /**
     * Retrieves properties on the patient like patient.patientName.familyName
     * 
     * @param className
     * @param property
     * @return Object the specified property of the patient
     */
    public Object getPatientAttr(String className, String property) {
        return getPatientAttr(className, property, false);
    }

    /**
     * Retrieves properties on the patient like patient.patientName.familyName
     * <p>
     * If returnAll is set, returns an array of every matching property for the patient instead of
     * just the preferred one
     * 
     * @param className
     * @param property
     * @param returnAll
     * @return Object the specified property of the patient, or an array of all properties if
     *         returnAll is set to true
     */
    public Object getPatientAttr(String className, String property, boolean returnAll) {
        String key = className + "." + property;

        if (returnAll)
            key += "--all";

        Map<Integer, Object> patientIdAttrMap;
        if (patientAttributeMap.containsKey(key)) {
            patientIdAttrMap = patientAttributeMap.get(key);
        } else {
            //log.debug("getting patient attrs: " + key);
            patientIdAttrMap = patientSetService.getPatientAttributes(getPatientSetIfNotAllPatients(), className,
                    property, returnAll);
            patientAttributeMap.put(key, patientIdAttrMap);
        }

        return patientIdAttrMap.get(patientId);
    }

    public Object getPersonAttribute(String attributeName, String joinClass, String joinProperty,
            String outputColumn, boolean returnAll) {
        String key = attributeName + "." + joinClass + "." + joinProperty;

        if (returnAll)
            key += "--all";

        Map<Integer, Object> personIdAttrMap;
        if (personAttributeMap.containsKey(key)) {
            personIdAttrMap = personAttributeMap.get(key);
        } else {
            //log.debug("getting patient attrs: " + key);
            personIdAttrMap = patientSetService.getPersonAttributes(getPatientSetIfNotAllPatients(), attributeName,
                    joinClass, joinProperty, outputColumn, returnAll);
            personAttributeMap.put(key, personIdAttrMap);
        }
        return personIdAttrMap.get(patientId);
    }

    public Object getPersonAttribute(String attributeName) {
        return getPersonAttribute(attributeName, null, null, null, false);
    }

    /**
     * Gets the last <code>n</code> obs for <code>conceptName</code>
     * 
     * @param n max number of obs to return
     * @param conceptName
     * @return List<Object> of observations
     */
    public List<Object> getLastNObs(Integer n, String conceptName) throws Exception {
        return getLastNObs(n, getConcept(conceptName));
    }

    /**
     * Gets Observations from the last encounter
     * 
     * @param n max number of obs to return
     * @param concept
     * @return List<Object> of observations
     * @throws Exception
     */
    public List<Object> getLastNObs(Integer n, Concept concept) throws Exception {
        List<Object> returnList = new Vector<Object>();
        for (List<Object> row : getLastNObsWithValues(n, concept, null)) {
            returnList.add(row.get(0));
        }
        return returnList;
    }

    @SuppressWarnings("unchecked")
    public List<List<Object>> getLastNObsWithValues(Integer n, String conceptId, Object attrs) throws Exception {
        return getLastNObsWithValues(n, getConcept(conceptId), (List<String>) attrs);
    }

    /**
     * Gets the most recent observation value
     * 
     * @param n max number of obs to return
     * @param concept
     * @param attrs
     * @return List<List<Object>> of the n most recent observations (with their values)
     * @throws Exception
     */
    @SuppressWarnings("unused")
    public List<List<Object>> getLastNObsWithValues(Integer n, Concept concept, List<String> attrs)
            throws Exception {
        //log.debug("Looking for n concepts: " + n + " " + concept);

        if (attrs == null)
            attrs = new Vector<String>();

        attrs.add(0, null);

        List<List<Object>> returnList = getObsWithValues(concept, attrs);

        if (returnList == null)
            returnList = new Vector<List<Object>>();

        //log.debug("Got obs list for: " + concept.getConceptId() + ". size: " + returnList.size());

        if (n.equals(-1))
            return returnList;

        // bring the list size up to 'n'
        List<Object> blankRow = new Vector<Object>();
        for (String attr : attrs)
            blankRow.add("");
        while (returnList.size() < n)
            returnList.add(blankRow);

        List<List<Object>> rList = returnList.subList(0, n);

        //for (Object o : rList)
        //   log.debug("rList object: " + o);

        return rList;
    }

    /**
     * Gets the most recent Observation matching this concept
     * 
     * @param conceptName
     * @return Object the most recent observation that matches the concept name specified
     */
    public Object getLastObs(String conceptName) throws Exception {
        return getLastObs(getConcept(conceptName));
    }

    /**
     * Gets the most recent Observation value matching this concept
     * 
     * @param conceptName
     * @param attrs string array
     * @return List<Object> of the most recent observation values
     */
    @SuppressWarnings("unchecked")
    public List<Object> getLastObsWithValues(String conceptName, Object attrs) throws Exception {
        //List<String> attrs = new Vector<String>();
        //Collections.addAll(attrs, attrArray);
        return getLastObsWithValues(getConcept(conceptName), (List<String>) attrs);
    }

    /**
     * Get the most recent obs matching <code>concept</code> out of the patient's encounters
     * 
     * @param concept
     * @return the most recent Obs matching concept from the last encounter
     * @throws Exception
     */
    public Object getLastObs(Concept concept) throws Exception {
        List<Object> obs = getLastNObs(1, concept);

        return obs.get(0);
    }

    /**
     * Get the most recent obs matching <code>concept</code> out of the patient's encounters
     * 
     * @param concept
     * @param attrs list of Strings like provider, encounterDatetime, etc
     * @return List<Object> of the most recent observation values that matches the concept of the
     *         patient encounters
     * @throws Exception
     */
    public List<Object> getLastObsWithValues(Concept concept, List<String> attrs) throws Exception {
        List<List<Object>> obs = getLastNObsWithValues(1, concept, attrs);

        return obs.get(0);
    }

    /**
     * Get the first occurrence of matching <code>obs.concept</code> out of the patient's encounters
     * 
     * @param conceptName
     * @return Object the first occurrence of the observation concept name of the patient's
     *         encounters
     */
    public Object getFirstObs(String conceptName) throws Exception {
        return getFirstObs(getConcept(conceptName));
    }

    /**
     * Get the first occurrence of matching <code>obs.concept</code> out of the patient's encounters
     * 
     * @param concept
     * @return Object
     * @throws Exception
     */
    public Object getFirstObs(Concept concept) throws Exception {
        List<List<Object>> obs = getObsWithValues(concept, null);

        if (obs != null && obs.size() > 0) {
            List<Object> o = obs.get(obs.size() - 1);
            return o.get(0);
        }

        log.info("Could not find an Obs with concept " + concept + " for patient " + patientId);

        return null;
    }

    /**
     * Get the first occurrence of matching <code>obs.concept</code> out of the patient's encounters
     * 
     * @param conceptName
     * @return List<Object>
     */
    @SuppressWarnings("unchecked")
    public List<Object> getFirstObsWithValues(String conceptName, Object attrs) throws Exception {
        //List<String> attrs = new Vector<String>();
        //Collections.addAll(attrs, attrArray);
        return getFirstObsWithValues(getConcept(conceptName), (List<String>) attrs);
    }

    /**
     * Get the first occurence of matching <code>obs.concept</code> out of the patient's encounters
     * 
     * @param concept
     * @param attrs the List of attributes to fetch
     * @return List<Object>
     * @throws Exception
     */
    @SuppressWarnings("unused")
    public List<Object> getFirstObsWithValues(Concept concept, List<String> attrs) throws Exception {
        // add a null first column for the actual obs value
        attrs.add(0, null);

        List<List<Object>> obs = getObsWithValues(concept, attrs);

        if (obs == null) {
            List<Object> blankRow = new Vector<Object>();
            for (String attr : attrs)
                blankRow.add("");
            return blankRow;
        }

        if (obs.size() > 0) {
            return obs.get(obs.size() - 1);
        }

        log.info("Could not find an Obs with concept " + concept + " for patient " + patientId);

        return null;
    }

    /**
     * Get the first occurrences of matching <code>obs.concept</code> out of the patient's
     * encounters
     * 
     * @param concept the Concept of the obs to fetch
     * @param n number of obs to get
     * @param attrs the Extra obs attributes to get along with this obs value
     * @return List<List<Object>>
     * @throws Exception
     */
    @SuppressWarnings("unused")
    public List<List<Object>> getFirstNObsWithValues(Integer n, Concept concept, List<String> attrs)
            throws Exception {
        // add a null first column for the actual obs value
        attrs.add(0, null);

        List<List<Object>> obs = getObsWithValues(concept, attrs);

        if (obs == null)
            obs = new Vector<List<Object>>();

        if (n.equals(-1))
            return obs;

        List<Object> blankRow = new Vector<Object>();
        for (String attr : attrs)
            blankRow.add("");
        while (obs.size() < n)
            obs.add(0, blankRow);

        int size = obs.size();
        List<List<Object>> rList = obs.subList(size - n, size);

        Collections.reverse(rList);

        return rList;
    }

    /**
     * Convenience method for other getFirstNObsWithValues method
     * 
     * @see #getFirstNObsWithValues(Integer, Concept, List)
     */
    @SuppressWarnings("unchecked")
    public List<List<Object>> getFirstNObsWithValues(Integer n, String conceptId, Object attrs) throws Exception {
        return getFirstNObsWithValues(n, getConcept(conceptId), (List<String>) attrs);
    }

    /**
     * Retrieves a patient identifier based on the given identifier type.
     * 
     * @param typeName
     * @return the PatientIdentifier for the given typename
     */
    public Object getPatientIdentifier(String typeName) {

        log.debug("Identifier Type: " + typeName);
        Map<Integer, PatientIdentifier> patientIdentifiers;
        if (patientIdentifierMap.containsKey(typeName)) {
            patientIdentifiers = patientIdentifierMap.get(typeName);
        } else {
            PatientIdentifierType type = null;
            // First try by Integer id
            try {
                Integer id = Integer.valueOf(typeName);
                type = patientService.getPatientIdentifierType(id);
            } catch (NumberFormatException ex) {
            }
            // otherwise get identifier type by the given name
            if (type == null) {
                type = patientService.getPatientIdentifierTypeByName(typeName);
            }
            // Get identifiers by type 
            patientIdentifiers = patientSetService.getPatientIdentifiersByType(getPatientSetIfNotAllPatients(),
                    type);

            log.debug("Found identifiers for patient identifier " + type + " = " + patientIdentifiers);

            patientIdentifierMap.put(typeName, patientIdentifiers);
        }

        return patientIdentifiers.get(patientId);

    }

    // /**
    // * Get all obs for the current patient that match this
    // * <code>obs.concept</code>=<code>concept</code> and
    // * <code>obs.valueCoded</code>=<code>valueCoded</code>
    // * 
    // * @param conceptName
    // * @param valueCoded
    // * @return 
    // * @throws Exception
    // */
    //   public Collection<Obs> getMatchingObs(String conceptName, String valueCoded) throws Exception {
    //      return getMatchingObs(getConcept(conceptName), getConcept(valueCoded));
    //   }
    // /**
    // * Get all obs for the current patient that match this
    // * <code>obs.concept</code>=<code>concept</code> and
    // * <code>obs.valueCoded</code>=<code>valueCoded</code>
    // * 
    // * @param concept
    // * @param valueCoded
    // * @return
    // * @throws Exception
    // */
    //   public Collection<Obs> getMatchingObs(Concept concept, Concept valueCoded) throws Exception {
    //      Collection<Obs> returnList = new Vector<Obs>();
    //      for (Obs o : getObs(concept)) {
    //         if (concept.getConceptId().equals(o.getConcept().getConceptId()) && valueCoded.equals(o.getValueCoded()))
    //            returnList.add(o);
    //      }
    //      
    //      return returnList;
    //   }
    /**
     * Calculate the years between two dates (age).
     * 
     * @param fromDate
     * @param toDate
     */
    public int calculateYearsBetween(Date fromDate, Date toDate) {

        if (fromDate == null || toDate == null) {
            return 0;
        }

        Calendar from = Calendar.getInstance();
        from.setTime(fromDate);

        Calendar to = Calendar.getInstance();
        to.setTime(toDate);

        int yearsBetween = to.get(Calendar.YEAR) - from.get(Calendar.YEAR);
        if (from.get(Calendar.DAY_OF_YEAR) > to.get(Calendar.DAY_OF_YEAR)) {
            yearsBetween -= 1;
        }
        return yearsBetween;
    }

    /**
     * Get the person's birthdate as of today
     * 
     * @param birthdate the person's date of birth
     * @return The calculated age
     */
    public int calculateAge(Date birthdate) {
        log.info("Calculating age " + birthdate);
        return calculateYearsBetween(birthdate, new Date());
    }

    /**
     * Format the given date according to the type ('short', 'long', 'ymd')
     * 
     * @param type format to use
     * @param d Date to format
     * @return a String with the formatted date
     */
    public String formatDate(String type, Date d) {
        if (d == null)
            return "";

        if ("long".equals(type)) {
            return dateFormatLong.format(d);
        } else if ("ymd".equals(type))
            return dateFormatYmd.format(d);
        else if ("short".equals(type) || type == null)
            return dateFormatShort.format(d);
        else {
            if (formats.containsKey(type))
                return formats.get(type).format(d);
            else {
                DateFormat df = new SimpleDateFormat(type, locale);
                formats.put(type, df);
                return df.format(d);
            }
        }
    }

    /**
     * Get a calendar instance for use in velocity scripts.
     * 
     * @return calendar
     */
    public Calendar getCalendarInstance() {
        if (calendar == null)
            calendar = Calendar.getInstance(locale);
        return calendar;
    }

    /**
     * Check the given string against the check digit algorithm
     * 
     * @param id
     * @return true/false whether the string has a valid check digit
     */
    public boolean isValidCheckDigit(String id) {
        try {
            return Context.getPatientService().getDefaultIdentifierValidator().isValid(id);
        } catch (Exception e) {
            log.error("Error evaluating identifier during report", e);
            return false;
        }
    }

    /**
     * @return results of o.hashCode()
     */
    public Integer hashCode(Object o) {
        if (o == null)
            return null;
        return o.hashCode();
    }

    public String getValueAsString(Object o) {
        if (o == null)
            return "";

        else if (o instanceof Concept)
            return ((Concept) o).getName().toString();
        else if (o instanceof Drug)
            return ((Drug) o).getName();
        else if (o instanceof Location)
            return ((Location) o).getName();
        else if (o instanceof User)
            return ((User) o).toString();
        else if (o instanceof EncounterType)
            return ((EncounterType) o).getName();
        else if (o instanceof Date)
            return formatDate(null, (Date) o);
        else if (o instanceof Obs)
            return ((Obs) o).getValueAsString(Context.getLocale());
        else
            return o.toString();
    }

    /**
     * Returns the patient set only if it is a subset of all patients. Returns null other wise
     * 
     * @return PatientSet object with patients or null if it isn't needed
     */
    public Cohort getPatientSetIfNotAllPatients() {
        if (isAllPatients)
            return null;
        return getPatientSet();
    }

}