2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
9 package eu
.etaxonomy
.cdm
.model
.agent
;
11 import java
.beans
.PropertyChangeEvent
;
12 import java
.beans
.PropertyChangeListener
;
13 import java
.util
.ArrayList
;
14 import java
.util
.List
;
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
;
30 import org
.apache
.logging
.log4j
.LogManager
;
31 import org
.apache
.logging
.log4j
.Logger
;
32 import org
.hibernate
.annotations
.Cascade
;
33 import org
.hibernate
.annotations
.CascadeType
;
34 import org
.hibernate
.annotations
.ListIndexBase
;
35 import org
.hibernate
.envers
.Audited
;
36 import org
.springframework
.beans
.factory
.annotation
.Configurable
;
38 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
39 import eu
.etaxonomy
.cdm
.model
.EntityCollectionSetterAdapter
;
40 import eu
.etaxonomy
.cdm
.model
.EntityCollectionSetterAdapter
.SetterAdapterException
;
41 import eu
.etaxonomy
.cdm
.strategy
.cache
.agent
.TeamDefaultCacheStrategy
;
42 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
;
43 import eu
.etaxonomy
.cdm
.strategy
.match
.MatchMode
;
46 * This class represents teams of {@link Person persons}. A team exists either for itself
47 * or is built with the list of (distinct) persons who belong to it.
48 * In the first case the inherited attribute {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} is to be used.
49 * In the second case at least all abbreviated names
50 * (the inherited attributes {@link TeamOrPersonBase#getNomenclaturalTitle() nomenclaturalTitle})
51 * or all full names (the strings returned by Person.generateTitle)
52 * of the persons must exist. A team is a {@link java.util.List list} of persons.
54 * This class corresponds to: <ul>
55 * <li> Team according to the TDWG ontology
56 * <li> AgentNames (partially) according to the TCS
57 * <li> MicroAgent (partially) according to the ABCD schema
61 * @since 08-Nov-2007 13:06:58
63 @XmlAccessorType(XmlAccessType
.FIELD
)
64 @XmlType(name
= "Team", propOrder
= {
65 "protectedNomenclaturalTitleCache",
66 "protectedCollectorTitleCache",
70 @XmlRootElement(name
= "Team")
74 public class Team
extends TeamOrPersonBase
<Team
> {
76 private static final long serialVersionUID
= 97640416905934622L;
77 private static final Logger logger
= LogManager
.getLogger();
79 @XmlElement(name
= "ProtectedNomenclaturalTitleCache")
80 private boolean protectedNomenclaturalTitleCache
= false;
82 @XmlElement(name
= "ProtectedCollectorTitleCache")
83 private boolean protectedCollectorTitleCache
= false;
85 //An abbreviated name for the team (e. g. in case of nomenclatural authorteams).
86 //A non abbreviated name for the team (e. g.
87 //in case of some bibliographical references)
88 @XmlElementWrapper(name
= "TeamMembers", nillable
= true)
89 @XmlElement(name
= "TeamMember")
91 @XmlSchemaType(name
= "IDREF")
92 @OrderColumn(name
="sortIndex")
93 @ListIndexBase(value
=0) //not really needed as this is the default
94 @ManyToMany(fetch
= FetchType
.LAZY
)
95 @Cascade({CascadeType
.SAVE_UPDATE
,CascadeType
.MERGE
})
96 @Match(MatchMode
.MATCH
)
97 private List
<Person
> teamMembers
;
99 @XmlElement(name
= "hasMoreMembers")
100 private boolean hasMoreMembers
;
102 // ********************************** FACTORY ***************************/
105 * Creates a new team instance without any concrete {@link Person members}.
107 static public Team
NewInstance(){
112 * Creates a new team instance with a bibliographic and nomenclatural title
113 * but without any {@link Person members}. The caches are set to protected.
115 static public Team
NewTitledInstance(String title
, String nomTitle
){
116 Team result
= new Team();
117 if (isNotBlank(title
)){
118 result
.setTitleCache(title
, true);
120 if (isNotBlank(nomTitle
)){
121 result
.setNomenclaturalTitleCache(nomTitle
, true);
127 * Creates a new team instance with a bibliographic, nomenclatural and collector title
128 * but without any {@link Person members}. The caches are set to protected if not blank.
130 static public Team
NewTitledInstance(String title
, String nomTitle
, String collectorTitle
){
131 Team result
= new Team();
132 if (isNotBlank(title
)){
133 result
.setTitleCache(title
, true);
135 if (isNotBlank(nomTitle
)){
136 result
.setNomenclaturalTitleCache(nomTitle
, true);
138 if (isNotBlank(collectorTitle
)){
139 result
.setCollectorTitleCache(collectorTitle
, true);
144 public static Team
NewInstance(Person
... members
) {
145 Team team
= new Team();
146 for (Person member
: members
){
147 team
.addTeamMember(member
);
152 //************************ CONSTRUCTOR *******************************/
155 * Class constructor (including the cache strategy defined in
156 * {@link eu.etaxonomy.cdm.strategy.cache.agent.TeamDefaultCacheStrategy TeamDefaultCacheStrategy}).
159 addListenersToMembers();
162 // ******************************************************************/
165 protected void initDefaultCacheStrategy() {
166 this.cacheStrategy
= TeamDefaultCacheStrategy
.NewInstance();
170 public void initListener(){
171 PropertyChangeListener listener
= new PropertyChangeListener() {
173 public void propertyChange(PropertyChangeEvent ev
) {
174 if (!ev
.getPropertyName().equals("titleCache") &&
175 !ev
.getPropertyName().equals("collectorTitleCache") &&
176 !ev
.getPropertyName().equals("nomenclaturalTitleCache") &&
177 !ev
.getPropertyName().equals("cacheStrategy")){
182 addPropertyChangeListener(listener
);
186 * Adds a property change listener to all team members.
188 private void addListenersToMembers() {
189 List
<Person
> members
= getTeamMembers();
190 for (Person member
: members
){
191 addListenerForTeamMember(member
);
195 private void addListenerForTeamMember(Person member
) {
196 PropertyChangeListener listener
= new PropertyChangeListener() {
198 public void propertyChange(PropertyChangeEvent e
) {
202 member
.addPropertyChangeListener(listener
);
205 public void resetCaches() {
206 if(!protectedTitleCache
){
209 if (! protectedNomenclaturalTitleCache
){
210 nomenclaturalTitleCache
= null;
212 if (!protectedCollectorTitleCache
){
213 collectorTitleCache
= null;
218 * Returns the list of {@link Person members} belonging to <i>this</i> team.
219 * A person may be a member of several distinct teams.
221 public List
<Person
> getTeamMembers(){
222 if(teamMembers
== null) {
223 this.teamMembers
= new ArrayList
<>();
225 return this.teamMembers
;
228 public void setTeamMembers(List
<Person
> teamMembers
) throws SetterAdapterException
{
229 new EntityCollectionSetterAdapter
<Team
, Person
>(Team
.class, Person
.class, "teamMembers").setCollection(this, teamMembers
);
233 * Adds a new {@link Person person} to <i>this</i> team at the end of the members' list.
235 * @param person the person who should be added to the other team members
236 * @see #getTeamMembers()
239 public void addTeamMember(Person person
){
241 getTeamMembers().add(person
);
242 firePropertyChange("teamMember", null, person
);
243 addListenerForTeamMember(person
);
248 * Adds a new {@link Person person} to <i>this</i> team
249 * at the given index place of the members' list. If the person is already
250 * a member of the list he will be moved to the given index place.
251 * The index must be an integer (>=0). If the index is larger than
252 * the present number of members the person will be added at the end of the list.
254 * @param person the person who should be added to the other team members
255 * @param index the position at which the person should be placed within the members' list (starting with 0)
256 * @see #getTeamMembers()
259 public void addTeamMember(Person person
, int index
){
261 int oldIndex
= getTeamMembers().indexOf(person
);
262 if (oldIndex
!= -1 ){
263 getTeamMembers().remove(person
);
265 if (index
>= getTeamMembers().size()){
266 index
= getTeamMembers().size();
268 getTeamMembers().add(index
, person
);
269 addListenerForTeamMember(person
);
270 firePropertyChange("teamMember", null, person
);
275 * Removes one person from the list of members of <i>this</i> team.
277 * @param person the person who should be deleted from <i>this</i> team
278 * @see #getTeamMembers()
280 public void removeTeamMember(Person person
){
281 boolean wasMember
= getTeamMembers().remove(person
);
283 firePropertyChange("teamMember", person
, null);
287 public boolean replaceTeamMember(Person newObject
, Person oldObject
){
288 return replaceInList(this.teamMembers
, newObject
, oldObject
);
292 * Generates or returns the {@link TeamOrPersonBase#getnomenclaturalTitle() nomenclatural identification} string for <i>this</i> team.
293 * This method overrides {@link TeamOrPersonBase#getNomenclaturalTitle() getNomenclaturalTitle}.
294 * This string is built with the {@link TeamOrPersonBase#getNomenclaturalTitle() abbreviated names}
295 * of all persons belonging to its (ordered) members' list if the flag
296 * {@link #protectedNomenclaturalTitleCache protectedNomenclaturalTitleCache} is not set.
297 * Otherwise this method returns the present nomenclatural abbreviation.
298 * In case the string is generated the cache strategy used is defined in
299 * {@link eu.etaxonomy.cdm.strategy.cache.agent.TeamDefaultCacheStrategy TeamDefaultCacheStrategy}.
300 * The result might be kept as nomenclatural abbreviation
301 * by using the {@link #setNomenclaturalTitle(String) setNomenclaturalTitle} method.
303 * @return a string which identifies <i>this</i> team for nomenclature
307 public String
getNomenclaturalTitleCache() {
308 if (protectedNomenclaturalTitleCache
== PROTECTED
){
309 return this.nomenclaturalTitleCache
;
311 if (nomenclaturalTitleCache
== null){
312 this.nomenclaturalTitleCache
= getTruncatedCache(cacheStrategy().getNomenclaturalTitleCache(this));
314 //as long as team members do not inform the team about changes the cache must be created new each time
315 nomenclaturalTitleCache
= getTruncatedCache(cacheStrategy().getNomenclaturalTitleCache(this));
317 return nomenclaturalTitleCache
;
321 * Assigns a {@link TeamOrPersonBase#nomenclaturalTitle nomenclatural identification} string to <i>this</i> team
322 * and a protection flag status to this string.
324 * @see #getNomenclaturalTitleCache()
327 public void setNomenclaturalTitleCache(String nomenclaturalTitleCache
, boolean protectedNomenclaturalTitleCache
) {
328 firePropertyChange("nomenclaturalTitleCache", this.nomenclaturalTitleCache
, nomenclaturalTitleCache
);
329 this.nomenclaturalTitleCache
= CdmUtils
.Nb(nomenclaturalTitleCache
);
330 this.protectedNomenclaturalTitleCache
= protectedNomenclaturalTitleCache
;
334 //@Transient //TODO a.kohlbecker remove??
335 public String
getTitleCache() {
337 if (isProtectedTitleCache()){
338 result
= this.titleCache
;
340 result
= generateTitle();
341 result
= replaceEmptyTitleByNomTitle(result
);
342 result
= getTruncatedCache(result
);
343 this.titleCache
= result
;
350 public String
getCollectorTitleCache(){
351 if (protectedCollectorTitleCache
== PROTECTED
){
352 return this.collectorTitleCache
;
354 if (collectorTitleCache
== null){
355 this.collectorTitleCache
= getTruncatedCache(cacheStrategy().getCollectorTitleCache(this));
359 //as long as team members do not inform the team about changes the cache must be created new each time
360 collectorTitleCache
= getTruncatedCache(cacheStrategy().getCollectorTitleCache(this));
361 } catch (Exception e
) {
362 //see #10090, getCollectorTitleCache is called e.g. if team is a secundum author, in this case if members are not initialized by BeanInitializer, however, jsonlib later tries to initialize the team (the exception is swallowed so it is not critical but a stacktrace is logged, which is not necessary)
363 //see also comment on TitleAndNameCacheAutoInitializer.initialize() for TaxonBase
364 if (logger
.isDebugEnabled()){logger
.debug("LIE during getCollectorTitleCache");}
365 return collectorTitleCache
;
368 return collectorTitleCache
;
372 * Protected nomenclatural title cache flag should be set to true, if
373 * the title cache is to be preferred against the atomized data.
374 * This may be the case if no atomized data exists or if atomization
375 * was incomplete for whatever reason.
378 public boolean isProtectedNomenclaturalTitleCache() {
379 return protectedNomenclaturalTitleCache
;
381 public void setProtectedNomenclaturalTitleCache(boolean protectedNomenclaturalTitleCache
) {
382 this.protectedNomenclaturalTitleCache
= protectedNomenclaturalTitleCache
;
386 public boolean isProtectedCollectorTitleCache() {
387 return protectedCollectorTitleCache
;
389 public void setProtectedCollectorTitleCache(boolean protectedCollectorTitleCache
) {
390 this.protectedCollectorTitleCache
= protectedCollectorTitleCache
;
393 public void setCollectorTitleCache(String collectorTitleCache
, boolean protectedCache
) {
394 firePropertyChange("collectorTitleCache", this.collectorTitleCache
, collectorTitleCache
);
395 this.collectorTitleCache
= CdmUtils
.Nb(collectorTitleCache
);
396 this.protectedCollectorTitleCache
= protectedCache
;
400 * The hasMoreMembers flag is true if this team has more members than
401 * mentioned in the {@link #teamMembers} list. This is usually the case
402 * when "et al." is used in the team representation. Formatters should add
403 * "et al." or an according post fix to the team string representation
404 * if this flag is set.
407 public boolean isHasMoreMembers() {
408 return hasMoreMembers
;
410 public void setHasMoreMembers(boolean hasMoreMembers
) {
411 this.hasMoreMembers
= hasMoreMembers
;
415 public boolean updateCaches(){
416 boolean result
= super.updateCaches();
417 if (this.protectedNomenclaturalTitleCache
== false){
418 String oldNomTitleCache
= this.nomenclaturalTitleCache
;
420 String newNomTitleCache
= cacheStrategy().getNomenclaturalTitleCache(this);
422 if ( oldNomTitleCache
== null || ! oldNomTitleCache
.equals(newNomTitleCache
) ){
423 this.setNomenclaturalTitleCache(null, false);
424 String newCache
= this.getNomenclaturalTitleCache();
426 if (newCache
== null){
427 logger
.warn("New nomTitleCache should never be null");
429 if (oldNomTitleCache
== null){
430 logger
.info("Old nomTitleCache should never be null");
435 if (this.protectedCollectorTitleCache
== false){
436 String oldCollTitleCache
= this.collectorTitleCache
;
437 String newCollTitleCache
= cacheStrategy().getCollectorTitleCache(this);
439 if ( oldCollTitleCache
== null || ! oldCollTitleCache
.equals(newCollTitleCache
) ){
440 this.setCollectorTitleCache(null, false);
441 String newCache
= this.getCollectorTitleCache();
443 if (newCache
== null){
444 logger
.warn("New collectorTitleCache should never be null");
446 if (oldCollTitleCache
== null){
447 logger
.info("Old collectorTitleCache should never be null");
455 //*********************** CLONE ********************************************************/
458 * Clones <i>this</i> Team. This is a shortcut that enables to create
459 * a new instance that differs only slightly from <i>this</i> Team.
460 * The corresponding person is cloned.
462 * @see java.lang.Object#clone()
465 public Team
clone() {
467 Team result
= (Team
)super.clone();
468 result
.teamMembers
= new ArrayList
<>();
469 for (Person teamMember
: this.teamMembers
){
470 result
.addTeamMember(teamMember
);
472 //no changes to protectedNomenclaturalTitleCache
474 } catch (CloneNotSupportedException e
){
475 logger
.warn("Object does not implement cloneable");