a00bea7bdcf5223bd3ab4cbf6477d21921be4afa
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / agent / Person.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9
10 package eu.etaxonomy.cdm.model.agent;
11
12 import java.beans.PropertyChangeEvent;
13 import java.beans.PropertyChangeListener;
14 import java.util.HashSet;
15 import java.util.Set;
16
17 import javax.persistence.Column;
18 import javax.persistence.Entity;
19 import javax.persistence.FetchType;
20 import javax.persistence.OneToMany;
21 import javax.xml.bind.annotation.XmlAccessType;
22 import javax.xml.bind.annotation.XmlAccessorType;
23 import javax.xml.bind.annotation.XmlElement;
24 import javax.xml.bind.annotation.XmlElementWrapper;
25 import javax.xml.bind.annotation.XmlRootElement;
26 import javax.xml.bind.annotation.XmlType;
27
28 import org.apache.log4j.Logger;
29 import org.hibernate.annotations.Cascade;
30 import org.hibernate.annotations.CascadeType;
31 import org.hibernate.annotations.Type;
32 import org.hibernate.envers.Audited;
33 import org.hibernate.search.annotations.Field;
34 import org.hibernate.search.annotations.FieldBridge;
35 import org.hibernate.search.annotations.Index;
36 import org.hibernate.search.annotations.IndexedEmbedded;
37 import org.springframework.beans.factory.annotation.Configurable;
38
39 import eu.etaxonomy.cdm.common.CdmUtils;
40 import eu.etaxonomy.cdm.hibernate.search.OrcidBridge;
41 import eu.etaxonomy.cdm.model.common.TimePeriod;
42 import eu.etaxonomy.cdm.strategy.cache.agent.PersonDefaultCacheStrategy;
43 import eu.etaxonomy.cdm.strategy.match.Match;
44 import eu.etaxonomy.cdm.strategy.match.MatchMode;
45 import eu.etaxonomy.cdm.validation.annotation.NullOrNotEmpty;
46 import javassist.compiler.ast.Keyword;
47
48 /**
49 * This class represents human beings, living or dead.<BR>
50 * It includes name parts, {@link Contact contact} details, {@link InstitutionalMembership institutional membership},
51 * and other possible information such as life {@link TimePeriod time period},
52 * taxonomic and/or geographical {@link Keyword specialization}.
53 * For a short abbreviated name the inherited attribute {@link TeamOrPersonBase#getNomenclaturalTitle() nomenclaturalTitle}
54 * is to be used.<BR>
55 * For other alternative (string-)names {@link eu.etaxonomy.cdm.model.reference.OriginalSourceBase OriginalSource} instances must be created
56 * and the attribute {@link eu.etaxonomy.cdm.model.common.OriginalSourceBase#getOriginalNameString() originalNameString} must be used.
57 * <P>
58 * This class corresponds to: <ul>
59 * <li> Person according to the TDWG ontology
60 * <li> AgentName (partially) according to the TCS
61 * <li> Person (PersonName partially) according to the ABCD schema
62 * </ul>
63 *
64 * @author m.doering
65 * @since 08-Nov-2007 13:06:42
66 */
67 @XmlAccessorType(XmlAccessType.FIELD)
68 @XmlType(name = "Person", propOrder = {
69 "prefix",
70 "familyName",
71 "givenName",
72 "initials",
73 "suffix",
74 "nomenclaturalTitle",
75 "collectorTitle",
76 "lifespan",
77 "orcid",
78 "institutionalMemberships"
79 })
80 @XmlRootElement(name = "Person")
81 @Entity
82 //@Indexed disabled to reduce clutter in indexes, since this type is not used by any search
83 //@Indexed(index = "eu.etaxonomy.cdm.model.agent.AgentBase")
84 @Audited
85 @Configurable
86 public class Person extends TeamOrPersonBase<Person>{
87
88 private static final long serialVersionUID = 4153566493065539763L;
89 public static final Logger logger = Logger.getLogger(Person.class);
90
91 @XmlElement(name="NomenclaturalTitle")
92 @Field(index=Index.YES)
93 @NullOrNotEmpty
94 @Column(length=255)
95 protected String nomenclaturalTitle;
96
97 @XmlElement(name="CollectorTitle")
98 @Field(index=Index.YES)
99 @NullOrNotEmpty
100 @Column(length=255)
101 private String collectorTitle;
102
103 @XmlElement(name = "Prefix")
104 @Field
105 //TODO Val #3379
106 // @NullOrNotEmpty
107 @Column(length=255)
108 private String prefix;
109
110 @XmlElement(name = "GivenName")
111 @Field
112 //TODO Val #3379
113 // @NullOrNotEmpty
114 @Column(length=255)
115 private String givenName;
116
117 @XmlElement(name = "Initials")
118 @Field
119 @NullOrNotEmpty
120 @Column(length=80)
121 private String initials;
122
123 @XmlElement(name = "FamilyName")
124 @Field
125 //TODO Val #3379
126 // @NullOrNotEmpty
127 @Column(length=255)
128 private String familyName;
129
130 @XmlElement(name = "Suffix")
131 @Field
132 //TODO Val #3379
133 // @NullOrNotEmpty
134 @Column(length=255)
135 private String suffix;
136
137 @XmlElement(name = "Lifespan")
138 @IndexedEmbedded
139 @Match(value=MatchMode.EQUAL_OR_ONE_NULL)
140 //TODO Val #3379 check carefully what the condition is that lifespan is really null in legacy data
141 // @NotNull
142 private TimePeriod lifespan = TimePeriod.NewInstance();
143
144 @XmlElement(name = "Orcid")
145 @Field
146 @FieldBridge(impl = OrcidBridge.class)
147 @Type(type="orcidUserType")
148 @Column(length=16)
149 private ORCID orcid;
150
151 @XmlElementWrapper(name = "InstitutionalMemberships", nillable = true)
152 @XmlElement(name = "InstitutionalMembership")
153 @OneToMany(fetch=FetchType.LAZY, mappedBy = "person")
154 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
155 protected Set<InstitutionalMembership> institutionalMemberships;
156
157 // *********************** FACTORY **********************************/
158
159 /**
160 * Creates a new empty instance for a person whose existence is all what is known.
161 * This can be a provisional solution until more information about <i>this</i> person
162 * can be gathered, for instance in case a member of a nomenclatural author team
163 * is not explicitly mentioned. It also includes the cache strategy defined in
164 * {@link eu.etaxonomy.cdm.strategy.cache.agent.PersonDefaultCacheStrategy PersonDefaultCacheStrategy}.
165 */
166 public static Person NewInstance(){
167 return new Person();
168 }
169
170 /**
171 * Creates a new instance for a person for whom an "identification" string
172 * is all what is known. This string is generally a short or a complete name.
173 * As this string is kept in the {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache}
174 * attribute and should not be overwritten by the {@link #generateTitle() generateTitle} method
175 * the {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#isProtectedTitleCache() protectedTitleCache} flag will be turned on.
176 */
177 public static Person NewTitledInstance(String titleCache){
178 Person result = new Person();
179 result.setTitleCache(titleCache, true);
180 return result;
181 }
182
183 public static Person NewInstance(String nomRefTitle, String familyName, String initials, String givenName){
184 Person result = new Person();
185 result.setNomenclaturalTitle(nomRefTitle);
186 result.setFamilyName(familyName);
187 result.setInitials(initials);
188 result.setGivenName(givenName);
189 return result;
190 }
191
192 // *********************** CONSTRUCTOR **********************************/
193
194 /**
195 * Class constructor.
196 *
197 * @see #Person(String, String, String)
198 */
199 protected Person() {
200 super();
201 }
202
203 /**
204 * Class constructor using a "forenames" string (including initials),
205 * a surname (family name) and an abbreviated name as used in nomenclature.
206 * For the abbreviated name the inherited attribute {@link TeamOrPersonBase#getNomenclaturalTitle() nomenclaturalTitle}
207 * is used.
208 *
209 * @param givenname the given name
210 * @param familyname the hereditary name
211 * @param nomenclaturalTitel the abbreviated name
212 * @see #Person()
213 * @see #NewInstance()
214 */
215 public Person(String givenname, String familyname, String nomenclaturalTitel) {
216 this.setGivenName(givenname);
217 this.setFamilyName(familyname);
218 logger.debug("before - Set nomenclatural Title");
219 this.setNomenclaturalTitle(nomenclaturalTitel);
220 logger.debug("after - Set nomenclatural Title");
221 }
222
223 @Override
224 protected void initDefaultCacheStrategy() {
225 this.cacheStrategy = PersonDefaultCacheStrategy.NewInstance();
226 }
227
228 @Override
229 public void initListener(){
230 PropertyChangeListener listener = new PropertyChangeListener() {
231 @Override
232 public void propertyChange(PropertyChangeEvent ev) {
233 if (!ev.getPropertyName().equals("nomenclaturalTitleCache") //not sure if called at all
234 && !ev.getPropertyName().equals("collectorTitleCache") //not sure if called at all
235 && !ev.getPropertyName().equals("cacheStrategy")){
236 if (!ev.getPropertyName().equals("titleCache") && ! isProtectedTitleCache()){
237 titleCache = null;
238 }
239 nomenclaturalTitleCache = null;
240 collectorTitleCache = null;
241 }
242 }
243 };
244 addPropertyChangeListener(listener);
245 }
246
247 // *********************** GETTER SETTER ADDER **********************************/
248
249 /**
250 * Returns the set of {@link InstitutionalMembership institution memberships} corresponding to <i>this</i> person.
251 *
252 * @see InstitutionalMembership
253 */
254 public Set<InstitutionalMembership> getInstitutionalMemberships(){
255 if(institutionalMemberships == null) {
256 this.institutionalMemberships = new HashSet<>();
257 }
258 return this.institutionalMemberships;
259 }
260
261 protected void addInstitutionalMembership(InstitutionalMembership ims){
262 getInstitutionalMemberships().add(ims);
263 if (ims.getPerson() != this){
264 logger.warn("Institutional membership's person has to be changed for adding it to person: " + this);
265 ims.getPerson().removeInstitutionalMembership(ims);
266 ims.setPerson(this);
267 }
268 }
269
270 /**
271 * Adds a new {@link InstitutionalMembership membership} of <i>this</i> person in an {@link Institution institution}
272 * to the set of his institution memberships.
273 * This method also creates a new institutional membership instance.
274 *
275 * @param institution the institution <i>this</i> person belongs to
276 * @param period the time period for which <i>this</i> person has been a member of the institution
277 * @param department the string label for the department <i>this</i> person belongs to,
278 * within the institution
279 * @param role the string label for the persons's role within the department or institution
280 * @see #getInstitutionalMemberships()
281 * @see InstitutionalMembership#InstitutionalMembership(Institution, Person, TimePeriod, String, String)
282 */
283 public InstitutionalMembership addInstitutionalMembership(Institution institution, TimePeriod period, String department, String role){
284 return new InstitutionalMembership(institution, this, period, department, role);
285 }
286
287 /**
288 * Removes one element from the set of institutional memberships of <i>this</i> person.
289 * Institute and person attributes of the institutional membership object
290 * will be nullified.
291 *
292 * @param ims the institutional membership of <i>this</i> person which should be deleted
293 * @see #getInstitutionalMemberships()
294 */
295 public void removeInstitutionalMembership(InstitutionalMembership ims){
296 ims.setInstitute(null);
297 ims.setPerson(null);
298 getInstitutionalMemberships().remove(ims);
299 }
300
301 /**
302 * Returns the string representing the prefix (for instance "Prof.&nbsp;Dr.<!-- -->")
303 * to <i>this</i> person's name.
304 */
305 public String getPrefix(){
306 return this.prefix;
307 }
308 /**
309 * @see #getPrefix()
310 */
311 public void setPrefix(String prefix){
312 this.prefix = isBlank(prefix) ? null : prefix;
313 }
314
315 /**
316 * Returns the string representing the given name or forename
317 * (for instance "John") of <i>this</i> person.
318 * This is the part of his name which is not shared with other
319 * family members. <BR>
320 * Pure initials should be stored in {@link #getInitials() initials}
321 * A combination of expanded names and initials maybe stored here.
322 * <BR> In user interfaces (UI) this field should better be called
323 * "Other/given names" according to {@link https://www.w3.org/International/questions/qa-personal-names.en#fielddesign }.
324 *
325 * @see #getInitials()
326 * @see #getFamilyName()
327 * @see https://www.w3.org/International/questions/qa-personal-names.en#fielddesign
328 */
329 public String getGivenName(){
330 return this.givenName;
331 }
332 /**
333 * @see #getGivenName()
334 */
335 public void setGivenName(String givenName){
336 this.givenName = isBlank(givenName) ? null : givenName;
337 }
338
339 //#4311
340 public String getCollectorTitle() {
341 return collectorTitle;
342 }
343 public void setCollectorTitle(String collectorTitle) {
344 this.collectorTitle = collectorTitle;
345 }
346
347 public String getNomenclaturalTitle() {
348 return nomenclaturalTitle;
349 }
350 @Override
351 public void setNomenclaturalTitle(String nomenclaturalTitle) {
352 this.nomenclaturalTitle = isBlank(nomenclaturalTitle) ? null : nomenclaturalTitle;
353 }
354 @Override
355 public void setNomenclaturalTitleCache(String nomenclaturalTitle) {
356 this.nomenclaturalTitle = isBlank(nomenclaturalTitle) ? null : nomenclaturalTitle;
357 }
358
359 /**
360 * Returns the initials of this person as used in bibliographic
361 * references. Usually these are the first letters of each givenname
362 * followed by "." per givenname. For East Asian names it may
363 * be the first 2 letters. Also dashes are kept.
364 * @return the initials
365 */
366 public String getInitials(){
367 return this.initials;
368 }
369 /**
370 * @see #getInitals()
371 */
372 public void setInitials(String initials){
373 this.initials = isBlank(initials) ? null : initials;
374 }
375
376 /**
377 * Returns the string representing the hereditary name (surname or family name)
378 * (for instance "Smith") of <i>this</i> person.
379 * This is the part of his name which is common to (all) other
380 * members of his family, as distinct from the given name or forename.
381 *
382 * <BR> In user interfaces (UI) this field should better be called
383 * "Family name" according to {@link https://www.w3.org/International/questions/qa-personal-names.en#fielddesign }.
384 *
385 * @see #getInitials()
386 * @see #getGivenName()
387 * @see https://www.w3.org/International/questions/qa-personal-names.en#fielddesign
388 */
389 public String getFamilyName(){
390 return this.familyName;
391 }
392 /**
393 * @see #getfamilyName()
394 */
395 public void setFamilyName(String familyName){
396 this.familyName = isBlank(familyName) ? null : familyName;
397 }
398
399 /**
400 * Returns the string representing the suffix (for instance "Junior")
401 * of <i>this</i> person's name.
402 */
403 public String getSuffix(){
404 return this.suffix;
405 }
406 /**
407 * @see #getSuffix()
408 */
409 public void setSuffix(String suffix){
410 this.suffix = isBlank(suffix) ? null: suffix;
411 }
412
413
414 /**
415 * Returns the {@link eu.etaxonomy.cdm.model.common.TimePeriod period of time}
416 * in which <i>this</i> person was alive (life span).
417 * The general form is birth date - death date
418 * (XXXX - YYYY; XXXX - or - YYYY as appropriate),
419 * but a simple flourished date (fl. XXXX) is also possible
420 * if that is all what is known.
421 *
422 * @see eu.etaxonomy.cdm.model.common.TimePeriod
423 */
424 public TimePeriod getLifespan(){
425 if(lifespan == null) {
426 this.lifespan = TimePeriod.NewInstance();
427 }
428 return this.lifespan;
429 }
430 /**
431 * @see #getLifespan()
432 */
433 public void setLifespan(TimePeriod lifespan){
434 this.lifespan = lifespan != null? lifespan : TimePeriod.NewInstance();
435 }
436
437 /**
438 * The {@link ORCID ORCiD} of this person.<BR>
439 * See https://orcid.org/ for information on ORCiD.
440 * @return the ORCiD
441 */
442 public ORCID getOrcid() {
443 return orcid;
444 }
445 /**
446 * @see #getOrcid()
447 */
448 public void setOrcid(ORCID orcid) {
449 this.orcid = orcid;
450 }
451
452 @Override
453 public boolean updateCaches(){
454 boolean result = false;
455 result |= super.updateCaches();
456 result |= updateNomenclaturalCache();
457 result |= updateCollectorCache();
458
459 return result;
460 }
461
462 private boolean updateNomenclaturalCache() {
463 //updates the nomenclaturalTitleCache if necessary
464 String oldCache = this.nomenclaturalTitleCache;
465 String newCache = getCacheStrategy().getNomenclaturalTitleCache(this);
466 if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
467 // this.setNomenclaturalTitleCache(null, false);
468 this.getNomenclaturalTitleCache();
469 return true;
470 }
471 return false;
472 }
473
474 private boolean updateCollectorCache() {
475 //updates the collectorTitleCache if necessary
476 String oldCache = this.collectorTitleCache;
477 String newCache = getCacheStrategy().getCollectorTitleCache(this);
478 if (!CdmUtils.nullSafeEqual(oldCache, newCache)){
479 // this.setNomenclaturalTitleCache(null, false);
480 this.getCollectorTitleCache();
481 return true;
482 }
483 return false;
484 }
485
486 //*********************** CLONE ********************************************************/
487
488 /**
489 * Clones <i>this</i> Person. This is a shortcut that enables to create
490 * a new instance that differs only slightly from <i>this</i> Person.
491 *
492 * @see java.lang.Object#clone()
493 */
494 @Override
495 public Person clone() {
496 try{
497 Person result = (Person)super.clone();
498 //no changes to givenname, familyname, lifespan, prefix, suffix
499 return result;
500 } catch (CloneNotSupportedException e){
501 logger.warn("Object does not implement cloneable");
502 e.printStackTrace();
503 return null;
504 }
505 }
506 }