• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmrs / openmrs-core / 15205088843

23 May 2025 07:43AM UTC coverage: 65.083% (+0.01%) from 65.069%
15205088843

push

github

rkorytkowski
TRUNK-6300: Adding Windows test, cleaning up logs, adjusting variable name

2 of 2 new or added lines in 2 files covered. (100.0%)

336 existing lines in 18 files now uncovered.

23379 of 35922 relevant lines covered (65.08%)

0.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.04
/api/src/main/java/org/openmrs/PersonAttribute.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs;
11

12
import javax.persistence.Cacheable;
13
import java.io.Serializable;
14
import java.lang.reflect.InvocationTargetException;
15
import java.lang.reflect.Method;
16
import java.util.Comparator;
17
import java.util.Date;
18

19
import org.apache.commons.lang3.StringUtils;
20
import org.hibernate.annotations.Cache;
21
import org.hibernate.annotations.CacheConcurrencyStrategy;
22
import org.hibernate.envers.Audited;
23
import org.hibernate.search.mapper.pojo.automaticindexing.ReindexOnUpdate;
24
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.AssociationInverseSide;
25
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.DocumentId;
26
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
27
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
28
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexedEmbedded;
29
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexingDependency;
30
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ObjectPath;
31
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyValue;
32
import org.openmrs.api.context.Context;
33
import org.openmrs.api.db.hibernate.search.SearchAnalysis;
34
import org.openmrs.util.OpenmrsClassLoader;
35
import org.openmrs.util.OpenmrsUtil;
36
import org.slf4j.Logger;
37
import org.slf4j.LoggerFactory;
38

39
/**
40
 * A PersonAttribute is meant as way for implementations to add arbitrary information about a
41
 * user/patient to their database. PersonAttributes are essentially just key-value pairs. However,
42
 * the PersonAttributeType can be defined in such a way that the value portion of this
43
 * PersonAttribute is a foreign key to another database table (like to the location table, or
44
 * concept table). This gives a PersonAttribute the ability to link to any other part of the
45
 * database A Person can have zero to n PersonAttribute(s).
46
 * 
47
 * @see org.openmrs.PersonAttributeType
48
 * @see org.openmrs.Attributable
49
 */
50
@Indexed
51
@Audited
52
@Cacheable
53
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
54
public class PersonAttribute extends BaseChangeableOpenmrsData implements java.io.Serializable, Comparable<PersonAttribute> {
55
        
56
        public static final long serialVersionUID = 11231211232111L;
57
        
58
        private static final Logger log = LoggerFactory.getLogger(PersonAttribute.class);
1✔
59
        
60
        // Fields
61
        @DocumentId
62
        private Integer personAttributeId;
63

64
        @IndexedEmbedded(includeEmbeddedObjectId = true)
65
        @AssociationInverseSide(inversePath = @ObjectPath({
66
                @PropertyValue(propertyName = "attributes")
67
        }))
68
        private Person person;
69

70
        @IndexedEmbedded
71
        @IndexingDependency(reindexOnUpdate = ReindexOnUpdate.SHALLOW)
72
        private PersonAttributeType attributeType;
73
        
74
        @FullTextField(name="valuePhrase", analyzer = SearchAnalysis.PHRASE_ANALYZER)
75
        @FullTextField(name = "valueExact", analyzer = SearchAnalysis.EXACT_ANALYZER)
76
        @FullTextField(name = "valueStart", analyzer = SearchAnalysis.START_ANALYZER, searchAnalyzer = SearchAnalysis.EXACT_ANALYZER)
77
        @FullTextField(name = "valueAnywhere", analyzer = SearchAnalysis.ANYWHERE_ANALYZER, searchAnalyzer = SearchAnalysis.EXACT_ANALYZER)
78
        private String value;
79
        
80
        /** default constructor */
81
        public PersonAttribute() {
1✔
82
        }
1✔
83
        
84
        public PersonAttribute(Integer personAttributeId) {
1✔
85
                this.personAttributeId = personAttributeId;
1✔
86
        }
1✔
87
        
88
        /**
89
         * Constructor for creating a basic attribute
90
         * 
91
         * @param type PersonAttributeType
92
         * @param value String
93
         */
94
        public PersonAttribute(PersonAttributeType type, String value) {
1✔
95
                this.attributeType = type;
1✔
96
                this.value = value;
1✔
97
        }
1✔
98
        
99
        /**
100
         * Shallow copy of this PersonAttribute. Does NOT copy personAttributeId
101
         * 
102
         * @return a shallows copy of <code>this</code>
103
         */
104
        public PersonAttribute copy() {
105
                return copyHelper(new PersonAttribute());
1✔
106
        }
107
        
108
        /**
109
         * The purpose of this method is to allow subclasses of PersonAttribute to delegate a portion of
110
         * their copy() method back to the superclass, in case the base class implementation changes.
111
         * 
112
         * @param target a PersonAttribute that will have the state of <code>this</code> copied into it
113
         * @return Returns the PersonAttribute that was passed in, with state copied into it
114
         */
115
        protected PersonAttribute copyHelper(PersonAttribute target) {
116
                target.setPerson(getPerson());
1✔
117
                target.setAttributeType(getAttributeType());
1✔
118
                target.setValue(getValue());
1✔
119
                target.setCreator(getCreator());
1✔
120
                target.setDateCreated(getDateCreated());
1✔
121
                target.setChangedBy(getChangedBy());
1✔
122
                target.setDateChanged(getDateChanged());
1✔
123
                target.setVoidedBy(getVoidedBy());
1✔
124
                target.setVoided(getVoided());
1✔
125
                target.setDateVoided(getDateVoided());
1✔
126
                target.setVoidReason(getVoidReason());
1✔
127
                return target;
1✔
128
        }
129
        
130
        /**
131
         * Compares this PersonAttribute object to the given otherAttribute. This method differs from
132
         * {@link #equals(Object)} in that this method compares the inner fields of each attribute for
133
         * equality. Note: Null/empty fields on <code>otherAttribute</code> /will not/ cause a false
134
         * value to be returned
135
         * 
136
         * @param otherAttribute PersonAttribute with which to compare
137
         * @return boolean true/false whether or not they are the same attributes
138
         * <strong>Should</strong> return true if attributeType value and void status are the same
139
         */
140
        @SuppressWarnings("unchecked")
141
        public boolean equalsContent(PersonAttribute otherAttribute) {
142
                boolean returnValue = true;
1✔
143
                
144
                // these are the methods to compare.
145
                String[] methods = { "getAttributeType", "getValue", "getVoided" };
1✔
146
                
147
                Class attributeClass = this.getClass();
1✔
148
                
149
                // loop over all of the selected methods and compare this and other
150
                for (String methodAttribute : methods) {
1✔
151
                        try {
152
                                Method method = attributeClass.getMethod(methodAttribute);
1✔
153
                                
154
                                Object thisValue = method.invoke(this);
1✔
155
                                Object otherValue = method.invoke(otherAttribute);
1✔
156
                                
157
                                if (otherValue != null) {
1✔
158
                                        returnValue &= otherValue.equals(thisValue);
1✔
159
                                }
160
                                
161
                        }
UNCOV
162
                        catch (NoSuchMethodException e) {
×
UNCOV
163
                                log.warn("No such method for comparison " + methodAttribute, e);
×
164
                        }
UNCOV
165
                        catch (IllegalAccessException | InvocationTargetException e) {
×
UNCOV
166
                                log.error("Error while comparing attributes", e);
×
167
                        }
1✔
168

169
                }
170
                
171
                return returnValue;
1✔
172
        }
173
        
174
        // property accessors
175
        
176
        /**
177
         * @return Returns the person.
178
         */
179
        public Person getPerson() {
180
                return person;
1✔
181
        }
182
        
183
        /**
184
         * @param person The person to set.
185
         */
186
        public void setPerson(Person person) {
187
                this.person = person;
1✔
188
        }
1✔
189
        
190
        /**
191
         * @return the attributeType
192
         */
193
        public PersonAttributeType getAttributeType() {
194
                return attributeType;
1✔
195
        }
196
        
197
        /**
198
         * @param attributeType the attributeType to set
199
         */
200
        public void setAttributeType(PersonAttributeType attributeType) {
201
                this.attributeType = attributeType;
1✔
202
        }
1✔
203
        
204
        /**
205
         * @return the value
206
         */
207
        public String getValue() {
208
                return value;
1✔
209
        }
210
        
211
        /**
212
         * @param value the value to set
213
         */
214
        public void setValue(String value) {
215
                this.value = value;
1✔
216
        }
1✔
217
        
218
        /**
219
         * @see java.lang.Object#toString()
220
         * <strong>Should</strong> return toString of hydrated value
221
         */
222
        @Override
223
        public String toString() {
224
                Object o = getHydratedObject();
1✔
225
                if (o instanceof Attributable) {
1✔
226
                        return ((Attributable) o).getDisplayString();
1✔
227
                } else if (o != null) {
1✔
228
                        return o.toString();
1✔
229
                }
230
                
231
                return this.value;
1✔
232
        }
233
        
234
        /**
235
         * @return the personAttributeId
236
         */
237
        public Integer getPersonAttributeId() {
238
                return personAttributeId;
1✔
239
        }
240
        
241
        /**
242
         * @param personAttributeId the personAttributeId to set
243
         */
244
        public void setPersonAttributeId(Integer personAttributeId) {
245
                this.personAttributeId = personAttributeId;
1✔
246
        }
1✔
247
        
248
        /**
249
         * Will try to create an object of class 'PersonAttributeType.format'. If that implements
250
         * <code>Attributable</code>, hydrate(value) is called. Defaults to just returning getValue()
251
         * 
252
         * @return hydrated object or getValue()
253
         * <strong>Should</strong> load class in format property
254
         * <strong>Should</strong> still load class in format property if not Attributable
255
         */
256
        @SuppressWarnings("unchecked")
257
        public Object getHydratedObject() {
258
                
259
                if (getValue() == null) {
1✔
UNCOV
260
                        return null;
×
261
                }
262
                
263
                try {
264
                        Class c = OpenmrsClassLoader.getInstance().loadClass(getAttributeType().getFormat());
1✔
265
                        try {
266
                                Object o = c.newInstance();
1✔
267
                                if (o instanceof Attributable) {
1✔
268
                                        Attributable attr = (Attributable) o;
1✔
269
                                        return attr.hydrate(getValue());
1✔
270
                                }
271
                        }
UNCOV
272
                        catch (InstantiationException e) {
×
273
                                // try to hydrate the object with the String constructor
UNCOV
274
                                log.trace("Unable to call no-arg constructor for class: " + c.getName());
×
UNCOV
275
                                return c.getConstructor(String.class).newInstance(getValue());
×
276
                        }
1✔
277
                }
UNCOV
278
                catch (Exception e) {
×
279
                        
280
                        // No need to warn if the input was blank
UNCOV
281
                        if (StringUtils.isBlank(getValue())) {
×
UNCOV
282
                                return null;
×
283
                        }
284
                        
UNCOV
285
                        log.warn("Unable to hydrate value: " + getValue() + " for type: " + getAttributeType(), e);
×
286
                }
1✔
287
                
288
                log.debug("Returning value: '" + getValue() + "'");
1✔
289
                return getValue();
1✔
290
        }
291
        
292
        /**
293
         * Convenience method for voiding this attribute
294
         * 
295
         * @param reason
296
         * <strong>Should</strong> set voided bit to true
297
         */
298
        public void voidAttribute(String reason) {
299
                setVoided(true);
1✔
300
                setVoidedBy(Context.getAuthenticatedUser());
1✔
301
                setVoidReason(reason);
1✔
302
                setDateVoided(new Date());
1✔
303
        }
1✔
304
        
305
        /**
306
         * @see java.lang.Comparable#compareTo(java.lang.Object)
307
         * <strong>Should</strong> return negative if other attribute is voided
308
         * <strong>Should</strong> return negative if other attribute has earlier date created
309
         * <strong>Should</strong> return negative if this attribute has lower attribute type than argument
310
         * <strong>Should</strong> return negative if other attribute has lower value
311
         * <strong>Should</strong> return negative if this attribute has lower attribute id than argument
312
         * <strong>Should</strong> not throw exception if attribute type is null
313
         * Note: this comparator imposes orderings that are inconsistent with equals
314
         */
315
        @Override
316
        public int compareTo(PersonAttribute other) {
317
                DefaultComparator paDComparator = new DefaultComparator();
1✔
318
                return paDComparator.compare(this, other);
1✔
319
        }
320
        
321
        /**
322
         * @since 1.5
323
         * @see org.openmrs.OpenmrsObject#getId()
324
         */
325
        @Override
326
        public Integer getId() {
327
                
328
                return getPersonAttributeId();
1✔
329
        }
330
        
331
        /**
332
         * @since 1.5
333
         * @see org.openmrs.OpenmrsObject#setId(java.lang.Integer)
334
         */
335
        @Override
336
        public void setId(Integer id) {
UNCOV
337
                setPersonAttributeId(id);
×
338
                
UNCOV
339
        }
×
340
        
341
        /**
342
         Provides a default comparator.
343
         @since 1.12
344
         **/
345
        public static class DefaultComparator implements Comparator<PersonAttribute>, Serializable {
1✔
346

347
                private static final long serialVersionUID = 1L;
348
                
349
                @Override
350
                public int compare(PersonAttribute pa1, PersonAttribute pa2) {
351
                        int retValue;
352
                        if ((retValue = OpenmrsUtil.compareWithNullAsGreatest(pa1.getAttributeType(), pa2.getAttributeType())) != 0) {
1✔
353
                                return retValue;
1✔
354
                        }
355
                        
356
                        if ((retValue = pa1.getVoided().compareTo(pa2.getVoided())) != 0) {
1✔
357
                                return retValue;
1✔
358
                        }
359
                        
360
                        if ((retValue = OpenmrsUtil.compareWithNullAsLatest(pa1.getDateCreated(), pa2.getDateCreated())) != 0) {
1✔
UNCOV
361
                                return retValue;
×
362
                        }
363
                        
364
                        if ((retValue = OpenmrsUtil.compareWithNullAsGreatest(pa1.getValue(), pa2.getValue())) != 0) {
1✔
365
                                return retValue;
1✔
366
                        }
367
                        
368
                        return OpenmrsUtil.compareWithNullAsGreatest(pa1.getPersonAttributeId(), pa2.getPersonAttributeId());
1✔
369
                }
370
        }
371
        
372
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc