/** * 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.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Date; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.api.context.Context; import org.openmrs.util.OpenmrsClassLoader; import org.openmrs.util.OpenmrsUtil; import org.simpleframework.xml.Attribute; import org.simpleframework.xml.Element; import org.simpleframework.xml.Root; /** * A PersonAttribute is meant as way for implementations to add arbitrary information about a * user/patient to their database. PersonAttributes are essentially just key-value pairs. However, * the PersonAttributeType can be defined in such a way that the value portion of this * PersonAttribute is a foreign key to another database table (like to the location table, or * concept table). This gives a PersonAttribute the ability to link to any other part of the * database A Person can have zero to n PersonAttribute(s). * * @see org.openmrs.PersonAttributeType * @see org.openmrs.Attributable */ @Root(strict = false) public class PersonAttribute extends BaseOpenmrsData implements java.io.Serializable, Comparable<PersonAttribute> { public static final long serialVersionUID = 11231211232111L; private static final Log log = LogFactory.getLog(PersonAttribute.class); // Fields private Integer personAttributeId; private Person person; private PersonAttributeType attributeType; private String value; /** default constructor */ public PersonAttribute() { } public PersonAttribute(Integer personAttributeId) { this.personAttributeId = personAttributeId; } /** * Constructor for creating a basic attribute * * @param type PersonAttributeType * @param value String */ public PersonAttribute(PersonAttributeType type, String value) { this.attributeType = type; this.value = value; } /** * Shallow copy of this PersonAttribute. Does NOT copy personAttributeId * * @return a shallows copy of <code>this</code> */ public PersonAttribute copy() { return copyHelper(new PersonAttribute()); } /** * The purpose of this method is to allow subclasses of PersonAttribute to delegate a portion of * their copy() method back to the superclass, in case the base class implementation changes. * * @param target a PersonAttribute that will have the state of <code>this</code> copied into it * @return Returns the PersonAttribute that was passed in, with state copied into it */ protected PersonAttribute copyHelper(PersonAttribute target) { target.setPerson(getPerson()); target.setAttributeType(getAttributeType()); target.setValue(getValue()); target.setCreator(getCreator()); target.setDateCreated(getDateCreated()); target.setChangedBy(getChangedBy()); target.setDateChanged(getDateChanged()); target.setVoidedBy(getVoidedBy()); target.setVoided(isVoided()); target.setDateVoided(getDateVoided()); target.setVoidReason(getVoidReason()); return target; } /** * Compares two objects for similarity * * @param obj * @return boolean true/false whether or not they are the same objects * @should return true if personAttributeIds match * @should return false if personAttributeIds do not match * @should match on object equality if a personAttributeId is null */ public boolean equals(Object obj) { if (obj instanceof PersonAttribute) { PersonAttribute attr = (PersonAttribute) obj; if (attr.getPersonAttributeId() != null && getPersonAttributeId() != null) return attr.getPersonAttributeId().equals(getPersonAttributeId()); } return this == obj; } /** * @see java.lang.Object#hashCode() */ public int hashCode() { if (this.getPersonAttributeId() == null) return super.hashCode(); int hash = 5; hash += 29 * hash + this.getPersonAttributeId().hashCode(); return hash; } /** * Compares this PersonAttribute object to the given otherAttribute. This method differs from * {@link #equals(Object)} in that this method compares the inner fields of each attribute for * equality. Note: Null/empty fields on <code>otherAttribute</code> /will not/ cause a false * value to be returned * * @param otherAttribute PersonAttribute with which to compare * @return boolean true/false whether or not they are the same attributes * @should return true if attributeType value and void status are the same */ @SuppressWarnings("unchecked") public boolean equalsContent(PersonAttribute otherAttribute) { boolean returnValue = true; // these are the methods to compare. String[] methods = { "getAttributeType", "getValue", "getVoided" }; Class attributeClass = this.getClass(); // loop over all of the selected methods and compare this and other for (String methodAttribute : methods) { try { Method method = attributeClass.getMethod(methodAttribute, new Class[] {}); Object thisValue = method.invoke(this); Object otherValue = method.invoke(otherAttribute); if (otherValue != null) returnValue &= otherValue.equals(thisValue); } catch (NoSuchMethodException e) { log.warn("No such method for comparison " + methodAttribute, e); } catch (IllegalAccessException e) { log.error("Error while comparing attributes", e); } catch (InvocationTargetException e) { log.error("Error while comparing attributes", e); } } return returnValue; } // property accessors /** * @return Returns the person. */ @Element(required = true) public Person getPerson() { return person; } /** * @param person The person to set. */ @Element(required = true) public void setPerson(Person person) { this.person = person; } /** * @return the attributeType */ @Element(required = true) public PersonAttributeType getAttributeType() { return attributeType; } /** * @param attributeType the attributeType to set */ @Element(required = true) public void setAttributeType(PersonAttributeType attributeType) { this.attributeType = attributeType; } /** * @return the value */ @Element(data = true, required = false) public String getValue() { return value; } /** * @param value the value to set */ @Element(data = true, required = false) public void setValue(String value) { this.value = value; } /** * @see java.lang.Object#toString() * @should return toString of hydrated value */ @SuppressWarnings("unchecked") public String toString() { Object o = getHydratedObject(); if (o instanceof Attributable) return ((Attributable) o).getDisplayString(); else if (o != null) return o.toString(); return this.value; } /** * @return the personAttributeId */ @Attribute(required = true) public Integer getPersonAttributeId() { return personAttributeId; } /** * @param personAttributeId the personAttributeId to set */ @Attribute(required = true) public void setPersonAttributeId(Integer personAttributeId) { this.personAttributeId = personAttributeId; } /** * Will try to create an object of class 'PersonAttributeType.format'. If that implements * <code>Attributable</code>, hydrate(value) is called. Defaults to just returning getValue() * * @return hydrated object or getValue() * @should load class in format property * @should still load class in format property if not Attributable */ @SuppressWarnings("unchecked") public Object getHydratedObject() { try { Class c = OpenmrsClassLoader.getInstance().loadClass(getAttributeType().getFormat()); try { Object o = c.newInstance(); if (o instanceof Attributable) { Attributable attr = (Attributable) o; return attr.hydrate(getValue()); } } catch (InstantiationException e) { // try to hydrate the object with the String constructor log.trace("Unable to call no-arg constructor for class: " + c.getName()); Object o = c.getConstructor(String.class).newInstance(getValue()); return o; } } catch (Throwable t) { log.warn("Unable to hydrate value: " + getValue() + " for type: " + getAttributeType(), t); } log.debug("Returning value: '" + getValue() + "'"); return getValue(); } /** * Convenience method for voiding this attribute * * @param reason * @should set voided bit to true */ public void voidAttribute(String reason) { setVoided(true); setVoidedBy(Context.getAuthenticatedUser()); setVoidReason(reason); setDateVoided(new Date()); } /** * @see java.lang.Comparable#compareTo(java.lang.Object) * @should return negative if other attribute is voided * @should return negative if other attribute has earlier date created * @should return negative if this attribute has lower attribute type than argument * @should return negative if other attribute has lower value * @should return negative if this attribute has lower attribute id than argument */ public int compareTo(PersonAttribute other) { int retValue = 0; retValue = isVoided().compareTo(other.isVoided()); if (retValue == 0) retValue = OpenmrsUtil.compareWithNullAsLatest(getDateCreated(), other.getDateCreated()); if (retValue == 0) retValue = getAttributeType().getPersonAttributeTypeId().compareTo( other.getAttributeType().getPersonAttributeTypeId()); if (retValue == 0) retValue = OpenmrsUtil.compareWithNullAsGreatest(getValue(), other.getValue()); if (retValue == 0) retValue = OpenmrsUtil.compareWithNullAsGreatest(getPersonAttributeId(), other.getPersonAttributeId()); return retValue; } /** * @since 1.5 * @see org.openmrs.OpenmrsObject#getId() */ public Integer getId() { return getPersonAttributeId(); } /** * @since 1.5 * @see org.openmrs.OpenmrsObject#setId(java.lang.Integer) */ public void setId(Integer id) { setPersonAttributeId(id); } }