/** * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. * * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS * graphic logo is a trademark of OpenMRS Inc. */ package org.openmrs; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Comparator; import java.util.Date; import org.apache.commons.lang.StringUtils; import org.hibernate.search.annotations.Analyzer; import org.hibernate.search.annotations.Boost; import org.hibernate.search.annotations.DocumentId; import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Fields; import org.hibernate.search.annotations.Indexed; import org.hibernate.search.annotations.IndexedEmbedded; import org.openmrs.api.context.Context; import org.openmrs.api.db.hibernate.search.LuceneAnalyzers; import org.openmrs.util.OpenmrsClassLoader; import org.openmrs.util.OpenmrsUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 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 */ @Indexed public class PersonAttribute extends BaseOpenmrsData implements java.io.Serializable, Comparable<PersonAttribute> { public static final long serialVersionUID = 11231211232111L; private static final Logger log = LoggerFactory.getLogger(PersonAttribute.class); // Fields @DocumentId private Integer personAttributeId; @IndexedEmbedded(includeEmbeddedObjectId = true) private Person person; @IndexedEmbedded private PersonAttributeType attributeType; @Fields({ @Field(name = "valuePhrase", analyzer = @Analyzer(definition = LuceneAnalyzers.PHRASE_ANALYZER), boost = @Boost(8f)), @Field(name = "valueExact", analyzer = @Analyzer(definition = LuceneAnalyzers.EXACT_ANALYZER), boost = @Boost(4f)), @Field(name = "valueStart", analyzer = @Analyzer(definition = LuceneAnalyzers.START_ANALYZER), boost = @Boost(2f)), @Field(name = "valueAnywhere", analyzer = @Analyzer(definition = LuceneAnalyzers.ANYWHERE_ANALYZER)) }) 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(getVoided()); target.setDateVoided(getDateVoided()); target.setVoidReason(getVoidReason()); return target; } /** * 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. */ public Person getPerson() { return person; } /** * @param person The person to set. */ public void setPerson(Person person) { this.person = person; } /** * @return the attributeType */ public PersonAttributeType getAttributeType() { return attributeType; } /** * @param attributeType the attributeType to set */ public void setAttributeType(PersonAttributeType attributeType) { this.attributeType = attributeType; } /** * @return the value */ public String getValue() { return value; } /** * @param value the value to set */ public void setValue(String value) { this.value = value; } /** * @see java.lang.Object#toString() * @should return toString of hydrated value */ @Override 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 */ public Integer getPersonAttributeId() { return personAttributeId; } /** * @param personAttributeId the personAttributeId to set */ 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() { if (getValue() == null) { return null; } 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 (Exception e) { // No need to warn if the input was blank if (StringUtils.isBlank(getValue())) { return null; } log.warn("Unable to hydrate value: " + getValue() + " for type: " + getAttributeType(), e); } 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 * @should not throw exception if attribute type is null * Note: this comparator imposes orderings that are inconsistent with equals */ @Override public int compareTo(PersonAttribute other) { DefaultComparator paDComparator = new DefaultComparator(); return paDComparator.compare(this, other); } /** * @since 1.5 * @see org.openmrs.OpenmrsObject#getId() */ @Override public Integer getId() { return getPersonAttributeId(); } /** * @since 1.5 * @see org.openmrs.OpenmrsObject#setId(java.lang.Integer) */ @Override public void setId(Integer id) { setPersonAttributeId(id); } /** Provides a default comparator. @since 1.12 **/ public static class DefaultComparator implements Comparator<PersonAttribute> { @Override public int compare(PersonAttribute pa1, PersonAttribute pa2) { int retValue; if ((retValue = OpenmrsUtil.compareWithNullAsGreatest(pa1.getAttributeType(), pa2.getAttributeType())) != 0) { return retValue; } if ((retValue = pa1.getVoided().compareTo(pa2.getVoided())) != 0) { return retValue; } if ((retValue = OpenmrsUtil.compareWithNullAsLatest(pa1.getDateCreated(), pa2.getDateCreated())) != 0) { return retValue; } if ((retValue = OpenmrsUtil.compareWithNullAsGreatest(pa1.getValue(), pa2.getValue())) != 0) { return retValue; } return OpenmrsUtil.compareWithNullAsGreatest(pa1.getPersonAttributeId(), pa2.getPersonAttributeId()); } } }