c548a2b0c2f724b6e3ae88d11a842370df4c5184
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / agent / Team.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 package eu.etaxonomy.cdm.model.agent;
10
11 import java.beans.PropertyChangeEvent;
12 import java.beans.PropertyChangeListener;
13 import java.util.ArrayList;
14 import java.util.List;
15
16 import javax.persistence.Entity;
17 import javax.persistence.FetchType;
18 import javax.persistence.ManyToMany;
19 import javax.persistence.OrderColumn;
20 import javax.persistence.Transient;
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.XmlIDREF;
26 import javax.xml.bind.annotation.XmlRootElement;
27 import javax.xml.bind.annotation.XmlSchemaType;
28 import javax.xml.bind.annotation.XmlType;
29
30 import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
31 import org.hibernate.annotations.Cascade;
32 import org.hibernate.annotations.CascadeType;
33 import org.hibernate.annotations.ListIndexBase;
34 import org.hibernate.envers.Audited;
35 import org.springframework.beans.factory.annotation.Configurable;
36
37 import eu.etaxonomy.cdm.common.CdmUtils;
38 import eu.etaxonomy.cdm.model.EntityCollectionSetterAdapter;
39 import eu.etaxonomy.cdm.model.EntityCollectionSetterAdapter.SetterAdapterException;
40 import eu.etaxonomy.cdm.strategy.cache.agent.TeamDefaultCacheStrategy;
41 import eu.etaxonomy.cdm.strategy.match.Match;
42 import eu.etaxonomy.cdm.strategy.match.MatchMode;
43
44 /**
45 * This class represents teams of {@link Person persons}. A team exists either for itself
46 * or is built with the list of (distinct) persons who belong to it.
47 * In the first case the inherited attribute {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} is to be used.
48 * In the second case at least all abbreviated names
49 * (the inherited attributes {@link TeamOrPersonBase#getNomenclaturalTitle() nomenclaturalTitle})
50 * or all full names (the strings returned by Person.generateTitle)
51 * of the persons must exist. A team is a {@link java.util.List list} of persons.
52 * <P>
53 * This class corresponds to: <ul>
54 * <li> Team according to the TDWG ontology
55 * <li> AgentNames (partially) according to the TCS
56 * <li> MicroAgent (partially) according to the ABCD schema
57 * </ul>
58 *
59 * @author m.doering
60 * @since 08-Nov-2007 13:06:58
61 */
62 @XmlAccessorType(XmlAccessType.FIELD)
63 @XmlType(name = "Team", propOrder = {
64 "protectedNomenclaturalTitleCache",
65 "protectedCollectorTitleCache",
66 "teamMembers",
67 "hasMoreMembers"
68 })
69 @XmlRootElement(name = "Team")
70 @Entity
71 //@Indexed disabled to reduce clutter in indexes, since this type is not used by any search
72 //@Indexed(index = "eu.etaxonomy.cdm.model.agent.AgentBase")
73 @Audited
74 @Configurable
75 public class Team extends TeamOrPersonBase<Team> {
76
77 private static final long serialVersionUID = 97640416905934622L;
78 public static final Logger logger = LogManager.getLogger(Team.class);
79
80 @XmlElement(name = "ProtectedNomenclaturalTitleCache")
81 private boolean protectedNomenclaturalTitleCache = false;
82
83 @XmlElement(name = "ProtectedCollectorTitleCache")
84 private boolean protectedCollectorTitleCache = false;
85
86 //An abbreviated name for the team (e. g. in case of nomenclatural authorteams).
87 //A non abbreviated name for the team (e. g.
88 //in case of some bibliographical references)
89 @XmlElementWrapper(name = "TeamMembers", nillable = true)
90 @XmlElement(name = "TeamMember")
91 @XmlIDREF
92 @XmlSchemaType(name = "IDREF")
93 @OrderColumn(name="sortIndex")
94 @ListIndexBase(value=0) //not really needed as this is the default
95 @ManyToMany(fetch = FetchType.LAZY)
96 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
97 @Match(MatchMode.MATCH)
98 private List<Person> teamMembers;
99
100 @XmlElement(name = "hasMoreMembers")
101 private boolean hasMoreMembers;
102
103 // ********************************** FACTORY ***************************/
104
105 /**
106 * Creates a new team instance without any concrete {@link Person members}.
107 */
108 static public Team NewInstance(){
109 return new Team();
110 }
111
112 /**
113 * Creates a new team instance with a bibliographic and nomenclatural title
114 * but without any {@link Person members}. The caches are set to protected.
115 */
116 static public Team NewTitledInstance(String title, String nomTitle){
117 Team result = new Team();
118 if (isNotBlank(title)){
119 result.setTitleCache(title, true);
120 }
121 if (isNotBlank(nomTitle)){
122 result.setNomenclaturalTitleCache(nomTitle, true);
123 }
124 return result;
125 }
126
127 /**
128 * Creates a new team instance with a bibliographic, nomenclatural and collector title
129 * but without any {@link Person members}. The caches are set to protected if not blank.
130 */
131 static public Team NewTitledInstance(String title, String nomTitle, String collectorTitle){
132 Team result = new Team();
133 if (isNotBlank(title)){
134 result.setTitleCache(title, true);
135 }
136 if (isNotBlank(nomTitle)){
137 result.setNomenclaturalTitleCache(nomTitle, true);
138 }
139 if (isNotBlank(collectorTitle)){
140 result.setCollectorTitleCache(collectorTitle, true);
141 }
142 return result;
143 }
144
145 public static Team NewInstance(Person... members) {
146 Team team = new Team();
147 for (Person member : members){
148 team.addTeamMember(member);
149 }
150 return team;
151 }
152
153 //************************ CONSTRUCTOR *******************************/
154
155 /**
156 * Class constructor (including the cache strategy defined in
157 * {@link eu.etaxonomy.cdm.strategy.cache.agent.TeamDefaultCacheStrategy TeamDefaultCacheStrategy}).
158 */
159 public Team() {
160 addListenersToMembers();
161 }
162
163 // ******************************************************************/
164
165 @Override
166 protected void initDefaultCacheStrategy() {
167 this.cacheStrategy = TeamDefaultCacheStrategy.NewInstance();
168 }
169
170 @Override
171 public void initListener(){
172 PropertyChangeListener listener = new PropertyChangeListener() {
173 @Override
174 public void propertyChange(PropertyChangeEvent ev) {
175 if (!ev.getPropertyName().equals("titleCache") &&
176 !ev.getPropertyName().equals("collectorTitleCache") &&
177 !ev.getPropertyName().equals("nomenclaturalTitleCache") &&
178 !ev.getPropertyName().equals("cacheStrategy")){
179 resetCaches();
180 }
181 }
182 };
183 addPropertyChangeListener(listener);
184 }
185
186 /**
187 * Adds a property change listener to all team members.
188 */
189 private void addListenersToMembers() {
190 List<Person> members = getTeamMembers();
191 for (Person member : members){
192 addListenerForTeamMember(member);
193 }
194 }
195
196 private void addListenerForTeamMember(Person member) {
197 PropertyChangeListener listener = new PropertyChangeListener() {
198 @Override
199 public void propertyChange(PropertyChangeEvent e) {
200 resetCaches();
201 }
202 };
203 member.addPropertyChangeListener(listener);
204 }
205
206 public void resetCaches() {
207 if(!protectedTitleCache){
208 titleCache = null;
209 }
210 if (! protectedNomenclaturalTitleCache){
211 nomenclaturalTitleCache = null;
212 }
213 if (!protectedCollectorTitleCache){
214 collectorTitleCache = null;
215 }
216 }
217
218 /**
219 * Returns the list of {@link Person members} belonging to <i>this</i> team.
220 * A person may be a member of several distinct teams.
221 */
222 public List<Person> getTeamMembers(){
223 if(teamMembers == null) {
224 this.teamMembers = new ArrayList<>();
225 }
226 return this.teamMembers;
227 }
228
229 public void setTeamMembers(List<Person> teamMembers) throws SetterAdapterException {
230 new EntityCollectionSetterAdapter<Team, Person>(Team.class, Person.class, "teamMembers").setCollection(this, teamMembers);
231 }
232
233 /**
234 * Adds a new {@link Person person} to <i>this</i> team at the end of the members' list.
235 *
236 * @param person the person who should be added to the other team members
237 * @see #getTeamMembers()
238 * @see Person
239 */
240 public void addTeamMember(Person person){
241 if (person != null){
242 getTeamMembers().add(person);
243 firePropertyChange("teamMember", null, person);
244 addListenerForTeamMember(person);
245 }
246 }
247
248 /**
249 * Adds a new {@link Person person} to <i>this</i> team
250 * at the given index place of the members' list. If the person is already
251 * a member of the list he will be moved to the given index place.
252 * The index must be an integer (>=0). If the index is larger than
253 * the present number of members the person will be added at the end of the list.
254 *
255 * @param person the person who should be added to the other team members
256 * @param index the position at which the person should be placed within the members' list (starting with 0)
257 * @see #getTeamMembers()
258 * @see Person
259 */
260 public void addTeamMember(Person person, int index){
261 if (person != null){
262 int oldIndex = getTeamMembers().indexOf(person);
263 if (oldIndex != -1 ){
264 getTeamMembers().remove(person);
265 }
266 if (index >= getTeamMembers().size()){
267 index = getTeamMembers().size();
268 }
269 getTeamMembers().add(index, person);
270 addListenerForTeamMember(person);
271 firePropertyChange("teamMember", null, person);
272 }
273 }
274
275 /**
276 * Removes one person from the list of members of <i>this</i> team.
277 *
278 * @param person the person who should be deleted from <i>this</i> team
279 * @see #getTeamMembers()
280 */
281 public void removeTeamMember(Person person){
282 boolean wasMember = getTeamMembers().remove(person);
283 if (wasMember){
284 firePropertyChange("teamMember", person, null);
285 }
286 }
287
288 public boolean replaceTeamMember(Person newObject, Person oldObject){
289 return replaceInList(this.teamMembers, newObject, oldObject);
290 }
291
292 /**
293 * Generates or returns the {@link TeamOrPersonBase#getnomenclaturalTitle() nomenclatural identification} string for <i>this</i> team.
294 * This method overrides {@link TeamOrPersonBase#getNomenclaturalTitle() getNomenclaturalTitle}.
295 * This string is built with the {@link TeamOrPersonBase#getNomenclaturalTitle() abbreviated names}
296 * of all persons belonging to its (ordered) members' list if the flag
297 * {@link #protectedNomenclaturalTitleCache protectedNomenclaturalTitleCache} is not set.
298 * Otherwise this method returns the present nomenclatural abbreviation.
299 * In case the string is generated the cache strategy used is defined in
300 * {@link eu.etaxonomy.cdm.strategy.cache.agent.TeamDefaultCacheStrategy TeamDefaultCacheStrategy}.
301 * The result might be kept as nomenclatural abbreviation
302 * by using the {@link #setNomenclaturalTitle(String) setNomenclaturalTitle} method.
303 *
304 * @return a string which identifies <i>this</i> team for nomenclature
305 */
306 @Override
307 @Transient
308 public String getNomenclaturalTitleCache() {
309 if (protectedNomenclaturalTitleCache == PROTECTED){
310 return this.nomenclaturalTitleCache;
311 }
312 if (nomenclaturalTitleCache == null){
313 this.nomenclaturalTitleCache = cacheStrategy().getNomenclaturalTitleCache(this);
314 }else{
315 //as long as team members do not inform the team about changes the cache must be created new each time
316 nomenclaturalTitleCache = cacheStrategy().getNomenclaturalTitleCache(this);
317 }
318 return nomenclaturalTitleCache;
319 }
320
321 /**
322 * Assigns a {@link TeamOrPersonBase#nomenclaturalTitle nomenclatural identification} string to <i>this</i> team
323 * and a protection flag status to this string.
324 *
325 * @see #getNomenclaturalTitleCache()
326 */
327 @Override
328 public void setNomenclaturalTitleCache(String nomenclaturalTitleCache, boolean protectedNomenclaturalTitleCache) {
329 firePropertyChange("nomenclaturalTitleCache", this.nomenclaturalTitleCache, nomenclaturalTitleCache);
330 this.nomenclaturalTitleCache = CdmUtils.Nb(nomenclaturalTitleCache);
331 this.protectedNomenclaturalTitleCache = protectedNomenclaturalTitleCache;
332 }
333
334 @Override
335 //@Transient //TODO a.kohlbecker remove??
336 public String getTitleCache() {
337 String result = "";
338 if (isProtectedTitleCache()){
339 result = this.titleCache;
340 }else{
341 result = generateTitle();
342 result = replaceEmptyTitleByNomTitle(result);
343 result = getTruncatedCache(result);
344 this.titleCache = result;
345 }
346 return result;
347 }
348
349 //#4311
350 @Override
351 public String getCollectorTitleCache() {
352 if (protectedCollectorTitleCache == PROTECTED){
353 return this.collectorTitleCache;
354 }
355 if (collectorTitleCache == null){
356 this.collectorTitleCache = cacheStrategy().getCollectorTitleCache(this);
357 }else{
358 //as long as team members do not inform the team about changes the cache must be created new each time
359 collectorTitleCache = cacheStrategy().getCollectorTitleCache(this);
360 }
361 return collectorTitleCache;
362 }
363
364 /**
365 * Protected nomenclatural title cache flag should be set to true, if
366 * the title cache is to be preferred against the atomized data.
367 * This may be the case if no atomized data exists or if atomization
368 * was incomplete for whatever reason.
369 * @return
370 */
371 public boolean isProtectedNomenclaturalTitleCache() {
372 return protectedNomenclaturalTitleCache;
373 }
374 public void setProtectedNomenclaturalTitleCache(boolean protectedNomenclaturalTitleCache) {
375 this.protectedNomenclaturalTitleCache = protectedNomenclaturalTitleCache;
376 }
377
378
379 public boolean isProtectedCollectorTitleCache() {
380 return protectedCollectorTitleCache;
381 }
382 public void setProtectedCollectorTitleCache(boolean protectedCollectorTitleCache) {
383 this.protectedCollectorTitleCache = protectedCollectorTitleCache;
384 }
385
386 public void setCollectorTitleCache(String collectorTitleCache, boolean protectedCache) {
387 this.collectorTitleCache = CdmUtils.Nb(collectorTitleCache);
388 this.protectedCollectorTitleCache = protectedCache;
389 }
390
391 /**
392 * The hasMoreMembers flag is true if this team has more members than
393 * mentioned in the {@link #teamMembers} list. This is usually the case
394 * when "et al." is used in the team representation. Formatters should add
395 * "et al." or an according post fix to the team string representation
396 * if this flag is set.
397 * @return
398 */
399 public boolean isHasMoreMembers() {
400 return hasMoreMembers;
401 }
402 public void setHasMoreMembers(boolean hasMoreMembers) {
403 this.hasMoreMembers = hasMoreMembers;
404 }
405
406 @Override
407 public boolean updateCaches(){
408 boolean result = super.updateCaches();
409 if (this.protectedNomenclaturalTitleCache == false){
410 String oldNomTitleCache = this.nomenclaturalTitleCache;
411
412 String newNomTitleCache = cacheStrategy().getNomenclaturalTitleCache(this);
413
414 if ( oldNomTitleCache == null || ! oldNomTitleCache.equals(newNomTitleCache) ){
415 this.setNomenclaturalTitleCache(null, false);
416 String newCache = this.getNomenclaturalTitleCache();
417
418 if (newCache == null){
419 logger.warn("New nomTitleCache should never be null");
420 }
421 if (oldNomTitleCache == null){
422 logger.info("Old nomTitleCache should never be null");
423 }
424 result = true;
425 }
426 }
427 if (this.protectedCollectorTitleCache == false){
428 String oldCollTitleCache = this.collectorTitleCache;
429 String newCollTitleCache = cacheStrategy().getCollectorTitleCache(this);
430
431 if ( oldCollTitleCache == null || ! oldCollTitleCache.equals(newCollTitleCache) ){
432 this.setCollectorTitleCache(null, false);
433 String newCache = this.getCollectorTitleCache();
434
435 if (newCache == null){
436 logger.warn("New collectorTitleCache should never be null");
437 }
438 if (oldCollTitleCache == null){
439 logger.info("Old collectorTitleCache should never be null");
440 }
441 result = true;
442 }
443 }
444 return result;
445 }
446
447 //*********************** CLONE ********************************************************/
448
449 /**
450 * Clones <i>this</i> Team. This is a shortcut that enables to create
451 * a new instance that differs only slightly from <i>this</i> Team.
452 * The corresponding person is cloned.
453 *
454 * @see java.lang.Object#clone()
455 */
456 @Override
457 public Team clone() {
458 try{
459 Team result = (Team)super.clone();
460 result.teamMembers = new ArrayList<>();
461 for (Person teamMember: this.teamMembers){
462 result.addTeamMember(teamMember);
463 }
464 //no changes to protectedNomenclaturalTitleCache
465 return result;
466 } catch (CloneNotSupportedException e){
467 logger.warn("Object does not implement cloneable");
468 e.printStackTrace();
469 return null;
470 }
471 }
472 }