merge trunk into 36 branch
[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
10 package eu.etaxonomy.cdm.model.agent;
11
12 import java.beans.PropertyChangeEvent;
13 import java.beans.PropertyChangeListener;
14 import java.util.ArrayList;
15 import java.util.List;
16
17 import javax.persistence.Entity;
18 import javax.persistence.FetchType;
19 import javax.persistence.ManyToMany;
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.log4j.Logger;
31 import org.hibernate.annotations.Cascade;
32 import org.hibernate.annotations.CascadeType;
33 import org.hibernate.annotations.IndexColumn;
34 import org.hibernate.envers.Audited;
35 import org.hibernate.search.annotations.Indexed;
36 import org.springframework.beans.factory.annotation.Configurable;
37
38 import eu.etaxonomy.cdm.strategy.cache.agent.TeamDefaultCacheStrategy;
39 import eu.etaxonomy.cdm.strategy.match.Match;
40 import eu.etaxonomy.cdm.strategy.match.MatchMode;
41
42 /**
43 * This class represents teams of {@link Person persons}. A team exists either for itself
44 * or is built with the list of (distinct) persons who belong to it.
45 * In the first case the inherited attribute {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} is to be used.
46 * In the second case at least all abbreviated names
47 * (the inherited attributes {@link TeamOrPersonBase#getNomenclaturalTitle() nomenclaturalTitle})
48 * or all full names (the strings returned by Person.generateTitle)
49 * of the persons must exist. A team is a {@link java.util.List list} of persons.
50 * <P>
51 * This class corresponds to: <ul>
52 * <li> Team according to the TDWG ontology
53 * <li> AgentNames (partially) according to the TCS
54 * <li> MicroAgent (partially) according to the ABCD schema
55 * </ul>
56 *
57 * @author m.doering
58 * @version 1.0
59 * @created 08-Nov-2007 13:06:58
60 */
61 @XmlAccessorType(XmlAccessType.FIELD)
62 @XmlType(name = "Team", propOrder = {
63 "protectedNomenclaturalTitleCache",
64 "protectedCollectorTitleCache",
65 "teamMembers",
66 "hasMoreMembers"
67 })
68 @XmlRootElement(name = "Team")
69 @Entity
70 @Indexed(index = "eu.etaxonomy.cdm.model.agent.AgentBase")
71 @Audited
72 @Configurable
73 public class Team extends TeamOrPersonBase<Team> {
74 private static final long serialVersionUID = 97640416905934622L;
75 public static final Logger logger = Logger.getLogger(Team.class);
76
77 @XmlElement(name = "ProtectedNomenclaturalTitleCache")
78 private boolean protectedNomenclaturalTitleCache = false;
79
80 //under construction #4311
81 @XmlElement(name = "ProtectedCollectorTitleCache")
82 private boolean protectedCollectorTitleCache = false;
83
84 //An abbreviated name for the team (e. g. in case of nomenclatural authorteams).
85 //A non abbreviated name for the team (e. g.
86 //in case of some bibliographical references)
87 @XmlElementWrapper(name = "TeamMembers", nillable = true)
88 @XmlElement(name = "TeamMember")
89 @XmlIDREF
90 @XmlSchemaType(name = "IDREF")
91 @IndexColumn(name="sortIndex", base = 0)
92 @ManyToMany(fetch = FetchType.LAZY)
93 @Cascade(CascadeType.SAVE_UPDATE)
94 @Match(MatchMode.MATCH)
95 private List<Person> teamMembers;
96
97 @XmlElement(name = "hasMoreMembers")
98 private boolean hasMoreMembers;
99
100
101
102 /**
103 * Creates a new team instance without any concrete {@link Person members}.
104 */
105 static public Team NewInstance(){
106 return new Team();
107 }
108
109 /**
110 * Creates a new team instance with a bibliographic and nomenclatural title
111 * but without any {@link Person members}. The caches are set to protected.
112 */
113 static public Team NewTitledInstance(String title, String nomTitle){
114 Team result = new Team();
115 result.setTitleCache(title, true);
116 result.setNomenclaturalTitle(nomTitle, true);
117 return result;
118 }
119
120 /**
121 * Class constructor (including the cache strategy defined in
122 * {@link eu.etaxonomy.cdm.strategy.cache.agent.TeamDefaultCacheStrategy TeamDefaultCacheStrategy}).
123 */
124 public Team() {
125 super();
126 this.cacheStrategy = TeamDefaultCacheStrategy.NewInstance();
127 addListenersToMembers();
128 }
129
130 /**
131 * Adds a property change listener to all team members.
132 */
133 private void addListenersToMembers() {
134 List<Person> members = getTeamMembers();
135 for (Person member : members){
136 addListenerForTeamMember(member);
137 }
138 }
139
140 /**
141 * @return
142 */
143 private void addListenerForTeamMember(Person member) {
144 PropertyChangeListener listener = new PropertyChangeListener() {
145 @Override
146 public void propertyChange(PropertyChangeEvent e) {
147
148 // ---- code with no effect below -----
149 // if (! isProtectedTitleCache()){
150 // titleCache = titleCache;
151 // }
152 // if (! isProtectedNomenclaturalTitleCache()){
153 // nomenclaturalTitle = nomenclaturalTitle;
154 // }
155 }
156 };
157 member.addPropertyChangeListener(listener);
158 }
159
160 /**
161 * Returns the list of {@link Person members} belonging to <i>this</i> team.
162 * A person may be a member of several distinct teams.
163 */
164 public List<Person> getTeamMembers(){
165 if(teamMembers == null) {
166 this.teamMembers = new ArrayList<Person>();
167 }
168 return this.teamMembers;
169 }
170
171 protected void setTeamMembers(List<Person> teamMembers) {
172 this.teamMembers = teamMembers;
173 addListenersToMembers();
174 }
175
176 /**
177 * Adds a new {@link Person person} to <i>this</i> team at the end of the members' list.
178 *
179 * @param person the person who should be added to the other team members
180 * @see #getTeamMembers()
181 * @see Person
182 */
183 public void addTeamMember(Person person){
184 if (person != null){
185 getTeamMembers().add(person);
186 firePropertyChange("teamMember", null, person);
187 addListenerForTeamMember(person);
188 }
189 }
190
191 /**
192 * Adds a new {@link Person person} to <i>this</i> team
193 * at the given index place of the members' list. If the person is already
194 * a member of the list he will be moved to the given index place.
195 * The index must be an integer (>=0). If the index is larger than
196 * the present number of members the person will be added at the end of the list.
197 *
198 * @param person the person who should be added to the other team members
199 * @param index the position at which the person should be placed within the members' list (starting with 0)
200 * @see #getTeamMembers()
201 * @see Person
202 */
203 public void addTeamMember(Person person, int index){
204 if (person != null){
205 int oldIndex = getTeamMembers().indexOf(person);
206 if (oldIndex != -1 ){
207 getTeamMembers().remove(person);
208 }
209 if (index >= getTeamMembers().size()){
210 index = getTeamMembers().size();
211 }
212 getTeamMembers().add(index, person);
213 addListenerForTeamMember(person);
214 firePropertyChange("teamMember", null, person);
215 }
216 }
217
218 /**
219 * Removes one person from the list of members of <i>this</i> team.
220 *
221 * @param person the person who should be deleted from <i>this</i> team
222 * @see #getTeamMembers()
223 */
224 public void removeTeamMember(Person person){
225 boolean wasMember = getTeamMembers().remove(person);
226 if (wasMember){
227 firePropertyChange("teamMember", person, null);
228 }
229 }
230
231
232 /**
233 * Generates or returns the {@link TeamOrPersonBase#getnomenclaturalTitle() nomenclatural identification} string for <i>this</i> team.
234 * This method overrides {@link TeamOrPersonBase#getNomenclaturalTitle() getNomenclaturalTitle}.
235 * This string is built with the {@link TeamOrPersonBase#getNomenclaturalTitle() abbreviated names}
236 * of all persons belonging to its (ordered) members' list if the flag
237 * {@link #protectedNomenclaturalTitleCache protectedNomenclaturalTitleCache} is not set.
238 * Otherwise this method returns the present nomenclatural abbreviation.
239 * In case the string is generated the cache strategy used is defined in
240 * {@link eu.etaxonomy.cdm.strategy.cache.agent.TeamDefaultCacheStrategy TeamDefaultCacheStrategy}.
241 * The result might be kept as nomenclatural abbreviation
242 * by using the {@link #setNomenclaturalTitle(String) setNomenclaturalTitle} method.
243 *
244 * @return a string which identifies <i>this</i> team for nomenclature
245 */
246 @Override
247 @Transient
248 public String getNomenclaturalTitle() {
249 if (protectedNomenclaturalTitleCache == PROTECTED){
250 return this.nomenclaturalTitle;
251 }
252 if (nomenclaturalTitle == null){
253 this.nomenclaturalTitle = cacheStrategy.getNomenclaturalTitle(this);
254 }else{
255 //as long as team members to not inform the team about changes the cache must be created new each time
256 nomenclaturalTitle = cacheStrategy.getNomenclaturalTitle(this);
257 }
258 return nomenclaturalTitle;
259 }
260
261 /**
262 * Assigns a {@link TeamOrPersonBase#nomenclaturalTitle nomenclatural identification} string to <i>this</i> team
263 * and protects it from overwriting.
264 * This method overrides {@link TeamOrPersonBase#setNomenclaturalTitle(String) setNomenclaturalTitle}.
265 *
266 * @see #getNomenclaturalTitle()
267 * @see #setNomenclaturalTitle(String, boolean)
268 */
269 @Override
270 public void setNomenclaturalTitle(String nomenclaturalTitle) {
271 this.setNomenclaturalTitle(nomenclaturalTitle, PROTECTED);
272 }
273
274 /**
275 * Assigns a {@link TeamOrPersonBase#nomenclaturalTitle nomenclatural identification} string to <i>this</i> team
276 * and a protection flag status to this string.
277 *
278 * @see #getNomenclaturalTitle()
279 */
280 public void setNomenclaturalTitle(String nomenclaturalTitle, boolean protectedNomenclaturalTitleCache) {
281 firePropertyChange("nomenclaturalTitle", this.nomenclaturalTitle, nomenclaturalTitle);
282 this.nomenclaturalTitle = nomenclaturalTitle == "" ? null: nomenclaturalTitle;
283 this.protectedNomenclaturalTitleCache = protectedNomenclaturalTitleCache;
284 }
285
286
287 @Override
288 //@Transient //TODO a.kohlbecker remove??
289 public String getTitleCache() {
290 isGeneratingTitleCache = true;
291 String result = "";
292 if (isProtectedTitleCache()){
293 result = this.titleCache;
294 }else{
295 result = generateTitle();
296 result = replaceEmptyTitleByNomTitle(result);
297 result = getTruncatedCache(result);
298 this.titleCache = result;
299 }
300 isGeneratingTitleCache = false;
301 return result;
302 }
303
304 /**
305 * Protected nomenclatural title cache flag should be set to true, if
306 * the title cache is to be preferred against the atomized data.
307 * This may be the case if no atomized data exists or if atomization
308 * was incomplete for whatever reason.
309 * @return
310 */
311 public boolean isProtectedNomenclaturalTitleCache() {
312 return protectedNomenclaturalTitleCache;
313 }
314
315 public void setProtectedNomenclaturalTitleCache(
316 boolean protectedNomenclaturalTitleCache) {
317 this.protectedNomenclaturalTitleCache = protectedNomenclaturalTitleCache;
318 }
319
320
321 /**
322 * The hasMoreMembers flag is true if this team has more members than
323 * mentioned in the {@link #teamMembers} list. This is usually the case
324 * when "et al." is used in the team representation. Formatters should add
325 * "et al." or an according post fix to the team string representation
326 * if this flag is set.
327 * @return
328 */
329 public boolean isHasMoreMembers() {
330 return hasMoreMembers;
331 }
332
333 public void setHasMoreMembers(boolean hasMoreMembers) {
334 this.hasMoreMembers = hasMoreMembers;
335 }
336
337 //*********************** CLONE ********************************************************/
338
339 /**
340 * Clones <i>this</i> Team. This is a shortcut that enables to create
341 * a new instance that differs only slightly from <i>this</i> Team.
342 * The corresponding person is cloned.
343 *
344 * @see eu.etaxonomy.cdm.model.media.IdentifiableMediaEntity#clone()
345 * @see java.lang.Object#clone()
346 */
347 @Override
348 public Object clone() {
349 try{
350 Team result = (Team)super.clone();
351 result.teamMembers = new ArrayList<Person>();
352 for (Person teamMember: this.teamMembers){
353 result.addTeamMember(teamMember);
354 }
355 //no changes to protectedNomenclaturalTitleCache
356 return result;
357 } catch (CloneNotSupportedException e){
358 logger.warn("Object does not implement cloneable");
359 e.printStackTrace();
360 return null;
361 }
362 }
363 }