/**
* 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;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.api.UserService;
import org.openmrs.util.OpenmrsUtil;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Root;
import org.simpleframework.xml.load.Replace;
import org.springframework.util.StringUtils;
/**
* A Person in the system. This can be either a small person stub, or indicative of an actual
* Patient in the system. This class holds the generic person things that both the stubs and
* patients share. Things like birthdate, names, addresses, and attributes are all generified into
* the person table (and hence this super class)
*
* @see org.openmrs.Patient
*/
@Root(strict = false)
public class Person extends BaseOpenmrsData implements java.io.Serializable {
public static final long serialVersionUID = 2L;
private static final Log log = LogFactory.getLog(Person.class);
protected Integer personId;
private Set<PersonAddress> addresses = null;
private Set<PersonName> names = null;
private Set<PersonAttribute> attributes = null;
private String gender;
private Date birthdate;
private Boolean birthdateEstimated = false;
private Boolean dead = false;
private Date deathDate;
private Concept causeOfDeath;
private User personCreator;
private Date personDateCreated;
private User personChangedBy;
private Date personDateChanged;
private Boolean personVoided = false;
private User personVoidedBy;
private Date personDateVoided;
private String personVoidReason;
private boolean isPatient;
/**
* Convenience map from PersonAttributeType.name to PersonAttribute.<br/>
* <br/>
* This is "cached" for each user upon first load. When an attribute is changed, the cache is
* cleared and rebuilt on next access.
*/
Map<String, PersonAttribute> attributeMap = null;
/**
* default empty constructor
*/
public Person() {
}
/**
* This constructor is used to build a new Person object copy from another person object
* (usually a patient or a user subobject). All attributes are copied over to the new object.
* NOTE! All child collection objects are copied as pointers, each individual element is not
* copied. <br/>
*
* @param person Person to create this person object from
*/
public Person(Person person) {
if (person == null)
return;
personId = person.getPersonId();
addresses = person.getAddresses();
names = person.getNames();
attributes = person.getAttributes();
gender = person.getGender();
birthdate = person.getBirthdate();
birthdateEstimated = person.getBirthdateEstimated();
dead = person.isDead();
deathDate = person.getDeathDate();
causeOfDeath = person.getCauseOfDeath();
// base creator/voidedBy/changedBy info is not copied here
// because that is specific to and will be recreated
// by the subobject upon save
setPersonCreator(person.getPersonCreator());
setPersonDateCreated(person.getPersonDateCreated());
setPersonChangedBy(person.getPersonChangedBy());
setPersonDateChanged(person.getPersonDateChanged());
setPersonVoided(person.isPersonVoided());
setPersonVoidedBy(person.getPersonVoidedBy());
setPersonDateVoided(person.getPersonDateVoided());
setPersonVoidReason(person.getPersonVoidReason());
}
/**
* Default constructor taking in the primary key personId value
*
* @param personId Integer internal id for this person
* @should set person id
*/
public Person(Integer personId) {
this.personId = personId;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
* @should equal person with same person id
* @should not equal person with different person id
* @should not equal on null
* @should equal person objects with no person id
* @should not equal person objects when one has null person id
*/
public boolean equals(Object obj) {
if (obj instanceof Person) {
Person person = (Person) obj;
if (getPersonId() != null && person.getPersonId() != null)
return personId.equals(person.getPersonId());
}
// if personId is null for either object, for equality the
// two objects must be the same
return this == obj;
}
/**
* @see java.lang.Object#hashCode()
* @should have same hashcode when equal
* @should have different hash code when not equal
* @should get hash code with null attributes
*/
public int hashCode() {
if (this.getPersonId() == null)
return super.hashCode();
return this.getPersonId().hashCode();
}
// Property accessors
/**
* @return Returns the personId.
*/
@Attribute(required = true)
public Integer getPersonId() {
return personId;
}
/**
* @param personId The personId to set.
*/
@Attribute(required = true)
public void setPersonId(Integer personId) {
this.personId = personId;
}
/**
* @return person's gender
*/
@Attribute(required = false)
public String getGender() {
return this.gender;
}
/**
* @param gender person's gender
*/
@Attribute(required = false)
public void setGender(String gender) {
this.gender = gender;
}
/**
* @return person's date of birth
*/
@Element(required = false)
public Date getBirthdate() {
return this.birthdate;
}
/**
* @param birthdate person's date of birth
*/
@Element(required = false)
public void setBirthdate(Date birthdate) {
this.birthdate = birthdate;
}
/**
* @return true if person's birthdate is estimated
*/
public Boolean isBirthdateEstimated() {
// if (this.birthdateEstimated == null) {
// return new Boolean(false);
// }
return this.birthdateEstimated;
}
@Attribute(required = true)
public Boolean getBirthdateEstimated() {
return isBirthdateEstimated();
}
/**
* @param birthdateEstimated true if person's birthdate is estimated
*/
@Attribute(required = true)
public void setBirthdateEstimated(Boolean birthdateEstimated) {
this.birthdateEstimated = birthdateEstimated;
}
/**
* @return Returns the death status.
*/
public Boolean isDead() {
return dead;
}
/**
* @return Returns the death status.
*/
@Attribute(required = true)
public Boolean getDead() {
return isDead();
}
/**
* @param dead The dead to set.
*/
@Attribute(required = true)
public void setDead(Boolean dead) {
this.dead = dead;
}
/**
* @return date of person's death
*/
@Element(required = false)
public Date getDeathDate() {
return this.deathDate;
}
/**
* @param deathDate date of person's death
*/
@Element(required = false)
public void setDeathDate(Date deathDate) {
this.deathDate = deathDate;
}
/**
* @return cause of person's death
*/
@Element(required = false)
public Concept getCauseOfDeath() {
return this.causeOfDeath;
}
/**
* @param causeOfDeath cause of person's death
*/
@Element(required = false)
public void setCauseOfDeath(Concept causeOfDeath) {
this.causeOfDeath = causeOfDeath;
}
/**
* @return list of known addresses for person
* @see org.openmrs.PersonAddress
* @should not get voided addresses
* @should not fail with null addresses
*/
@ElementList(required = false)
public Set<PersonAddress> getAddresses() {
if (addresses == null)
addresses = new TreeSet<PersonAddress>();
return this.addresses;
}
/**
* @param addresses Set<PersonAddress> list of known addresses for person
* @see org.openmrs.PersonAddress
*/
@ElementList(required = false)
public void setAddresses(Set<PersonAddress> addresses) {
this.addresses = addresses;
}
/**
* @return all known names for person
* @see org.openmrs.PersonName
* @should not get voided names
* @should not fail with null names
*/
@ElementList
public Set<PersonName> getNames() {
if (names == null)
names = new TreeSet<PersonName>();
return this.names;
}
/**
* @param names update all known names for person
* @see org.openmrs.PersonName
*/
@ElementList
public void setNames(Set<PersonName> names) {
this.names = names;
}
/**
* @return all known attributes for person
* @see org.openmrs.PersonAttribute
* @should not get voided attributes
* @should not fail with null attributes
*/
@ElementList
public Set<PersonAttribute> getAttributes() {
if (attributes == null)
attributes = new TreeSet<PersonAttribute>();
return this.attributes;
}
/**
* Returns only the non-voided attributes for this person
*
* @return list attributes
* @should not get voided attributes
* @should not fail with null attributes
*/
public List<PersonAttribute> getActiveAttributes() {
List<PersonAttribute> attrs = new Vector<PersonAttribute>();
for (PersonAttribute attr : getAttributes()) {
if (!attr.isVoided())
attrs.add(attr);
}
return attrs;
}
/**
* @param attributes update all known attributes for person
* @see org.openmrs.PersonAttribute
*/
@ElementList
public void setAttributes(Set<PersonAttribute> attributes) {
this.attributes = attributes;
attributeMap = null;
}
// Convenience methods
/**
* Convenience method to add the <code>attribute</code> to this person's attribute list if the
* attribute doesn't exist already.<br/>
* <br/>
* Voids any current attribute with type = <code>newAttribute.getAttributeType()</code><br/>
* <br/>
* NOTE: This effectively limits persons to only one attribute of any given type **
*
* @param newAttribute PersonAttribute to add to the Person
* @should fail when new attribute exist
* @should fail when new atribute are the same type with same value
* @should void old attribute when new attribute are the same type with different value
* @should remove attribute when old attribute are temporary
* @should not save an attribute with a null value
* @should not save an attribute with a blank string value
* @should void old attribute when a null or blank string value is added
*/
public void addAttribute(PersonAttribute newAttribute) {
newAttribute.setPerson(this);
boolean newIsNull = !StringUtils.hasText(newAttribute.getValue());
for (PersonAttribute currentAttribute : getActiveAttributes()) {
if (currentAttribute.equals(newAttribute))
return; // if we have the same PersonAttributeId, don't add the new attribute
else if (currentAttribute.getAttributeType().equals(newAttribute.getAttributeType())) {
if (currentAttribute.getValue() != null && currentAttribute.getValue().equals(newAttribute.getValue()))
// this person already has this attribute
return;
// if the to-be-added attribute isn't already voided itself
// and if we have the same type, different value
if (newAttribute.isVoided() == false || newIsNull) {
if (currentAttribute.getCreator() != null)
currentAttribute.voidAttribute("New value: " + newAttribute.getValue());
else
// remove the attribute if it was just temporary (didn't have a creator
// attached to it yet)
removeAttribute(currentAttribute);
}
}
}
attributeMap = null;
if (!OpenmrsUtil.collectionContains(attributes, newAttribute) && !newIsNull)
attributes.add(newAttribute);
}
/**
* Convenience method to get the <code>attribute</code> from this person's attribute list if the
* attribute exists already.
*
* @param attribute
* @should not fail when person attribute is null
* @should not fail when person attribute is not exist
* @should remove attribute when exist
*/
public void removeAttribute(PersonAttribute attribute) {
if (attributes != null)
if (attributes.remove(attribute))
attributeMap = null;
}
/**
* Convenience Method to return the first non-voided person attribute matching a person
* attribute type. <br/>
* <br/>
* Returns null if this person has no non-voided {@link PersonAttribute} with the given
* {@link PersonAttributeType}, the given {@link PersonAttributeType} is null, or this person
* has no attributes.
*
* @param pat the PersonAttributeType to look for (can be a stub, see
* {@link PersonAttributeType#equals(Object)} for how its compared)
* @return PersonAttribute that matches the given type
* @should not fail when attribute type is null
* @should not return voided attribute
* @should return null when given attribute type is not exist
*/
public PersonAttribute getAttribute(PersonAttributeType pat) {
if (pat != null)
for (PersonAttribute attribute : getAttributes()) {
if (pat.equals(attribute.getAttributeType()) && !attribute.isVoided()) {
return attribute;
}
}
return null;
}
/**
* Convenience method to get this person's first attribute that has a PersonAttributeType.name
* equal to <code>attributeName</code>.<br/>
* <br/>
* Returns null if this person has no non-voided {@link PersonAttribute} with the given type
* name, the given name is null, or this person has no attributes.
*
* @param attributeName the name string to match on
* @return PersonAttribute whose {@link PersonAttributeType#getName()} matchs the given name
* string
*/
public PersonAttribute getAttribute(String attributeName) {
if (attributeName != null)
for (PersonAttribute attribute : getAttributes()) {
PersonAttributeType type = attribute.getAttributeType();
if (type != null && attributeName.equals(type.getName()) && !attribute.isVoided()) {
return attribute;
}
}
return null;
}
/**
* Convenience method to get this person's first attribute that has a PersonAttributeTypeId
* equal to <code>attributeTypeId</code>.<br/>
* <br/>
* Returns null if this person has no non-voided {@link PersonAttribute} with the given type id
* or this person has no attributes.<br/>
* <br/>
* The given id cannot be null.
*
* @param attributeTypeId the id of the {@link PersonAttributeType} to look for
* @return PersonAttribute whose {@link PersonAttributeType#getId()} equals the given Integer id
*/
public PersonAttribute getAttribute(Integer attributeTypeId) {
for (PersonAttribute attribute : getActiveAttributes()) {
if (attributeTypeId.equals(attribute.getAttributeType().getPersonAttributeTypeId())) {
return attribute;
}
}
return null;
}
/**
* Convenience method< to get all of this person's attributes that have a
* PersonAttributeType.name equal to <code>attributeName</code>.
*
* @param attributeName
*/
public List<PersonAttribute> getAttributes(String attributeName) {
List<PersonAttribute> ret = new Vector<PersonAttribute>();
for (PersonAttribute attribute : getActiveAttributes()) {
PersonAttributeType type = attribute.getAttributeType();
if (type != null && attributeName.equals(type.getName())) {
ret.add(attribute);
}
}
return ret;
}
/**
* Convenience method to get all of this person's attributes that have a PersonAttributeType.id
* equal to <code>attributeTypeId</code>.
*
* @param attributeTypeId
*/
public List<PersonAttribute> getAttributes(Integer attributeTypeId) {
List<PersonAttribute> ret = new Vector<PersonAttribute>();
for (PersonAttribute attribute : getActiveAttributes()) {
if (attributeTypeId.equals(attribute.getAttributeType().getPersonAttributeTypeId())) {
ret.add(attribute);
}
}
return ret;
}
/**
* Convenience method to get all of this person's attributes that have a PersonAttributeType
* equal to <code>personAttributeType</code>.
*
* @param personAttributeType
*/
public List<PersonAttribute> getAttributes(PersonAttributeType personAttributeType) {
List<PersonAttribute> ret = new Vector<PersonAttribute>();
for (PersonAttribute attribute : getAttributes()) {
if (personAttributeType.equals(attribute.getAttributeType()) && !attribute.isVoided()) {
ret.add(attribute);
}
}
return ret;
}
/**
* Convenience method to get all of this person's attributes in map form: <String,
* PersonAttribute>.
*/
public Map<String, PersonAttribute> getAttributeMap() {
if (attributeMap != null)
return attributeMap;
if (log.isDebugEnabled())
log.debug("Current Person Attributes: \n" + printAttributes());
attributeMap = new HashMap<String, PersonAttribute>();
for (PersonAttribute attribute : getActiveAttributes()) {
attributeMap.put(attribute.getAttributeType().getName(), attribute);
}
return attributeMap;
}
/**
* Convenience method for viewing all of the person's current attributes
*
* @return Returns a string with all the attributes
*/
public String printAttributes() {
String s = "";
for (PersonAttribute attribute : getAttributes()) {
s += attribute.getAttributeType() + " : " + attribute.getValue() + " : voided? " + attribute.isVoided() + "\n";
}
return s;
}
/**
* Convenience method to add the <code>name</code> to this person's name list if the name
* doesn't exist already.
*
* @param name
*/
public void addName(PersonName name) {
if (name != null) {
name.setPerson(this);
if (names == null)
names = new TreeSet<PersonName>();
if (!OpenmrsUtil.collectionContains(names, name))
names.add(name);
}
}
/**
* Convenience method remove the <code>name</code> from this person's name list if the name
* exists already.
*
* @param name
*/
public void removeName(PersonName name) {
if (names != null)
names.remove(name);
}
/**
* Convenience method to add the <code>address</code> to this person's address list if the
* address doesn't exist already.
*
* @param address
*/
public void addAddress(PersonAddress address) {
if (address != null) {
address.setPerson(this);
if (addresses == null)
addresses = new TreeSet<PersonAddress>();
if (!OpenmrsUtil.collectionContains(addresses, address))
addresses.add(address);
}
}
/**
* Convenience method to remove the <code>address</code> from this person's address list if the
* address exists already.
*
* @param address
*/
public void removeAddress(PersonAddress address) {
if (addresses != null)
addresses.remove(address);
}
/**
* Convenience method to get the {@link PersonName} object that is marked as "preferred". <br/>
* <br/>
* If two names are marked as preferred (or no names), the database ordering comes into effect
* and the one that was created most recently will be returned. <br/>
* <br/>
* This method will never return a voided name, even if it is marked as preferred. <br/>
* <br/>
* Null is returned if this person has no names or all voided names.
*
* @return the "preferred" person name.
* @see #getNames()
* @see PersonName#isPreferred()
*/
public PersonName getPersonName() {
// normally the DAO layer returns these in the correct order, i.e. preferred and non-voided first, but it's possible that someone
// has fetched a Person, changed their names around, and then calls this method, so we have to be careful.
if (getNames() != null && getNames().size() > 0) {
for (PersonName name : getNames()) {
if (name.isPreferred() && !name.isVoided())
return name;
}
for (PersonName name : getNames()) {
if (!name.isVoided())
return name;
}
return null;
}
return null;
}
/**
* Convenience method to get the given name attribute on this person's preferred PersonName
*
* @return String given name of the person
*/
public String getGivenName() {
PersonName personName = getPersonName();
if (personName == null)
return "";
else
return personName.getGivenName();
}
/**
* Convenience method to get the middle name attribute on this person's preferred PersonName
*
* @return String middle name of the person
*/
public String getMiddleName() {
PersonName personName = getPersonName();
if (personName == null)
return "";
else
return personName.getMiddleName();
}
/**
* Convenience method to get the family name attribute on this person's preferred PersonName
*
* @return String family name of the person
*/
public String getFamilyName() {
PersonName personName = getPersonName();
if (personName == null)
return "";
else
return personName.getFamilyName();
}
/**
* Convenience method to get the {@link PersonAddress} object that is marked as "preferred". <br/>
* <br/>
* If two addresses are marked as preferred (or no addresses), the database ordering comes into
* effect and the one that was created most recently will be returned. <br/>
* <br/>
* This method will never return a voided address, even if it is marked as preferred. <br/>
* <br/>
* Null is returned if this person has no addresses or all voided addresses.
*
* @return the "preferred" person address.
* @see #getAddresses()
* @see PersonAddress#isPreferred()
*/
public PersonAddress getPersonAddress() {
// normally the DAO layer returns these in the correct order, i.e. preferred and non-voided first, but it's possible that someone
// has fetched a Person, changed their addresses around, and then calls this method, so we have to be careful.
if (getAddresses() != null && getAddresses().size() > 0) {
for (PersonAddress addr : getAddresses()) {
if (addr.isPreferred() && !addr.isVoided())
return addr;
}
for (PersonAddress addr : getAddresses()) {
if (!addr.isVoided())
return addr;
}
return null;
}
return null;
}
/**
* Convenience method to calculate this person's age based on the birthdate For a person who
* lived 1990 to 2000, age would be -5 in 1985, 5 in 1995, 10 in 2000, and 10 2010.
*
* @return Returns age as an Integer.
* @should get correct age after death
*/
public Integer getAge() {
return getAge(null);
}
/**
* Convenience method: calculates the person's age on a given date based on the birthdate
*
* @param onDate (null defaults to today)
* @return int value of the person's age
* @should get age before birthday
* @should get age on birthday with no minutes defined
* @should get age on birthday with minutes defined
* @should get age after birthday
* @should get age after death
* @should get age with given date after death
* @should get age with given date before death
* @should get age with given date before birth
*/
public Integer getAge(Date onDate) {
if (birthdate == null)
return null;
// Use default end date as today.
Calendar today = Calendar.getInstance();
// But if given, use the given date.
if (onDate != null)
today.setTime(onDate);
// If date given is after date of death then use date of death as end date
if (getDeathDate() != null && today.getTime().after(getDeathDate())) {
today.setTime(getDeathDate());
}
Calendar bday = Calendar.getInstance();
bday.setTime(birthdate);
int age = today.get(Calendar.YEAR) - bday.get(Calendar.YEAR);
// Adjust age when today's date is before the person's birthday
int todaysMonth = today.get(Calendar.MONTH);
int bdayMonth = bday.get(Calendar.MONTH);
int todaysDay = today.get(Calendar.DAY_OF_MONTH);
int bdayDay = bday.get(Calendar.DAY_OF_MONTH);
if (todaysMonth < bdayMonth) {
age--;
} else if (todaysMonth == bdayMonth && todaysDay < bdayDay) {
// we're only comparing on month and day, not minutes, etc
age--;
}
return age;
}
/**
* Convenience method: sets a person's birth date from an age as of the given date Also sets
* flag indicating that the birth date is inexact. This sets the person's birth date to January
* 1 of the year that matches this age and date
*
* @param age (the age to set)
* @param ageOnDate (null defaults to today)
*/
public void setBirthdateFromAge(int age, Date ageOnDate) {
Calendar c = Calendar.getInstance();
c.setTime(ageOnDate == null ? new Date() : ageOnDate);
c.set(Calendar.DATE, 1);
c.set(Calendar.MONTH, Calendar.JANUARY);
c.add(Calendar.YEAR, -1 * age);
setBirthdate(c.getTime());
setBirthdateEstimated(true);
}
public User getPersonChangedBy() {
return personChangedBy;
}
public void setPersonChangedBy(User changedBy) {
this.personChangedBy = changedBy;
}
public Date getPersonDateChanged() {
return personDateChanged;
}
public void setPersonDateChanged(Date dateChanged) {
this.personDateChanged = dateChanged;
}
public User getPersonCreator() {
return personCreator;
}
public void setPersonCreator(User creator) {
this.personCreator = creator;
}
public Date getPersonDateCreated() {
return personDateCreated;
}
public void setPersonDateCreated(Date dateCreated) {
this.personDateCreated = dateCreated;
}
public Date getPersonDateVoided() {
return personDateVoided;
}
public void setPersonDateVoided(Date dateVoided) {
this.personDateVoided = dateVoided;
}
public void setPersonVoided(Boolean voided) {
this.personVoided = voided;
}
public Boolean getPersonVoided() {
return isPersonVoided();
}
public Boolean isPersonVoided() {
return personVoided;
}
public User getPersonVoidedBy() {
return personVoidedBy;
}
public void setPersonVoidedBy(User voidedBy) {
this.personVoidedBy = voidedBy;
}
public String getPersonVoidReason() {
return personVoidReason;
}
public void setPersonVoidReason(String voidReason) {
this.personVoidReason = voidReason;
}
/**
* @return true/false whether this person is a patient or not
*/
public boolean isPatient() {
return isPatient;
}
/**
* This should only be set by the database layer by looking at whether a row exists in the
* patient table
*
* @param isPatient whether this person is a patient or not
*/
@SuppressWarnings("unused")
private void setPatient(boolean isPatient) {
this.isPatient = isPatient;
}
/**
* @return true/false whether this person is a user or not
* @deprecated use {@link UserService#getUsersByPerson(Person, boolean)}
*/
public boolean isUser() {
return false;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "Person(personId=" + personId + ")";
}
/**
* If the serializer wishes, don't serialize this entire object, just the important parts
*
* @param sessionMap serialization session information
* @return Person object to serialize
* @see OpenmrsUtil#isShortSerialization(Map)
*/
@Replace
public Person replaceSerialization(Map<?, ?> sessionMap) {
if (OpenmrsUtil.isShortSerialization(sessionMap)) {
// only serialize the person id
return new Person(getPersonId());
}
// don't do short serialization
return this;
}
/**
* @since 1.5
* @see org.openmrs.OpenmrsObject#getId()
*/
public Integer getId() {
return getPersonId();
}
/**
* @since 1.5
* @see org.openmrs.OpenmrsObject#setId(java.lang.Integer)
*/
public void setId(Integer id) {
setPersonId(id);
}
}