Project

General

Profile

Download (13.9 KB) Statistics
| Branch: | Tag: | Revision:
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.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.model.EntityCollectionSetterAdapter;
38
import eu.etaxonomy.cdm.model.EntityCollectionSetterAdapter.SetterAdapterException;
39
import eu.etaxonomy.cdm.strategy.cache.agent.TeamDefaultCacheStrategy;
40
import eu.etaxonomy.cdm.strategy.match.Match;
41
import eu.etaxonomy.cdm.strategy.match.MatchMode;
42

    
43
/**
44
 * This class represents teams of {@link Person persons}. A team exists either for itself
45
 * or is built with the list of (distinct) persons who belong to it.
46
 * In the first case the inherited attribute {@link eu.etaxonomy.cdm.model.common.IdentifiableEntity#getTitleCache() titleCache} is to be used.
47
 * In the second case at least all abbreviated names
48
 * (the inherited attributes {@link TeamOrPersonBase#getNomenclaturalTitle() nomenclaturalTitle})
49
 * or all full names (the strings returned by Person.generateTitle)
50
 * of the persons must exist. A team is a {@link java.util.List list} of persons.
51
 * <P>
52
 * This class corresponds to: <ul>
53
 * <li> Team according to the TDWG ontology
54
 * <li> AgentNames (partially) according to the TCS
55
 * <li> MicroAgent (partially) according to the ABCD schema
56
 * </ul>
57
 *
58
 * @author m.doering
59
 * @since 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 disabled to reduce clutter in indexes, since this type is not used by any search
71
//@Indexed(index = "eu.etaxonomy.cdm.model.agent.AgentBase")
72
@Audited
73
@Configurable
74
public class Team extends TeamOrPersonBase<Team> {
75

    
76
    private static final long serialVersionUID = 97640416905934622L;
77
	public static final Logger logger = Logger.getLogger(Team.class);
78

    
79
    @XmlElement(name = "ProtectedNomenclaturalTitleCache")
80
	private boolean protectedNomenclaturalTitleCache = false;
81

    
82
    //under construction #4311
83
    @XmlElement(name = "ProtectedCollectorTitleCache")
84
	private final 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.setNomenclaturalTitle(nomTitle, true);
123
		}
124
		return result;
125
	}
126

    
127
    public static Team NewInstance(Person... members) {
128
        Team team = new Team();
129
        for (Person member : members){
130
            team.addTeamMember(member);
131
        }
132
        return team;
133
    }
134

    
135
//************************ CONSTRUCTOR *******************************/
136

    
137
	/**
138
	 * Class constructor (including the cache strategy defined in
139
	 * {@link eu.etaxonomy.cdm.strategy.cache.agent.TeamDefaultCacheStrategy TeamDefaultCacheStrategy}).
140
	 */
141
	public Team() {
142
		addListenersToMembers();
143
	}
144

    
145
    @Override
146
    protected void initDefaultCacheStrategy() {
147
        this.cacheStrategy = TeamDefaultCacheStrategy.NewInstance();
148
    }
149

    
150
	/**
151
	 * Adds a property change listener to all team members.
152
	 */
153
	private void addListenersToMembers() {
154
		List<Person> members = getTeamMembers();
155
		for (Person member : members){
156
			addListenerForTeamMember(member);
157
		}
158
	}
159

    
160
	private void addListenerForTeamMember(Person member) {
161
		PropertyChangeListener listener = new PropertyChangeListener() {
162
			@Override
163
            public void propertyChange(PropertyChangeEvent e) {
164

    
165
// 			   ---- code with no effect below -----
166
//				if (! isProtectedTitleCache()){
167
//					titleCache = titleCache;
168
//				}
169
//				if (! isProtectedNomenclaturalTitleCache()){
170
//					nomenclaturalTitle = nomenclaturalTitle;
171
//				}
172
			}
173
		};
174
		member.addPropertyChangeListener(listener);
175
	}
176

    
177
	/**
178
	 * Returns the list of {@link Person members} belonging to <i>this</i> team.
179
	 * A person may be a member of several distinct teams.
180
	 */
181
	public List<Person> getTeamMembers(){
182
		if(teamMembers == null) {
183
			this.teamMembers = new ArrayList<>();
184
		}
185
		return this.teamMembers;
186
	}
187

    
188
	public void setTeamMembers(List<Person> teamMembers) throws SetterAdapterException {
189
	    new EntityCollectionSetterAdapter<Team, Person>(Team.class, Person.class, "teamMembers").setCollection(this, teamMembers);
190
    }
191

    
192
	/**
193
	 * Adds a new {@link Person person} to <i>this</i> team at the end of the members' list.
194
	 *
195
	 * @param  person  the person who should be added to the other team members
196
	 * @see     	   #getTeamMembers()
197
	 * @see 		   Person
198
	 */
199
	public void addTeamMember(Person person){
200
		if (person != null){
201
			getTeamMembers().add(person);
202
			firePropertyChange("teamMember", null, person);
203
			addListenerForTeamMember(person);
204
		}
205
	}
206

    
207
	/**
208
	 * Adds a new {@link Person person} to <i>this</i> team
209
	 * at the given index place of the members' list. If the person is already
210
	 * a member of the list he will be moved to the given index place.
211
	 * The index must be an integer (>=0). If the index is larger than
212
	 * the present number of members the person will be added at the end of the list.
213
	 *
214
	 * @param  person  the person who should be added to the other team members
215
	 * @param  index   the position at which the person should be placed within the members' list (starting with 0)
216
	 * @see     	   #getTeamMembers()
217
	 * @see 		   Person
218
	 */
219
	public void addTeamMember(Person person, int index){
220
		if (person != null){
221
			int oldIndex = getTeamMembers().indexOf(person);
222
			if (oldIndex != -1 ){
223
				getTeamMembers().remove(person);
224
			}
225
			if (index >= getTeamMembers().size()){
226
				index = getTeamMembers().size();
227
			}
228
			getTeamMembers().add(index, person);
229
			addListenerForTeamMember(person);
230
			firePropertyChange("teamMember", null, person);
231
		}
232
	}
233

    
234
	/**
235
	 * Removes one person from the list of members of <i>this</i> team.
236
	 *
237
	 * @param  person  the person who should be deleted from <i>this</i> team
238
	 * @see            #getTeamMembers()
239
	 */
240
	public void removeTeamMember(Person person){
241
		boolean wasMember = getTeamMembers().remove(person);
242
		if (wasMember){
243
			firePropertyChange("teamMember", person, null);
244
		}
245
	}
246

    
247
    public boolean replaceTeamMember(Person newObject, Person oldObject){
248
        return replaceInList(this.teamMembers, newObject, oldObject);
249
    }
250

    
251
	/**
252
	 * Generates or returns the {@link TeamOrPersonBase#getnomenclaturalTitle() nomenclatural identification} string for <i>this</i> team.
253
	 * This method overrides {@link TeamOrPersonBase#getNomenclaturalTitle() getNomenclaturalTitle}.
254
	 * This string is built with the {@link TeamOrPersonBase#getNomenclaturalTitle() abbreviated names}
255
	 * of all persons belonging to its (ordered) members' list if the flag
256
	 * {@link #protectedNomenclaturalTitleCache protectedNomenclaturalTitleCache} is not set.
257
	 * Otherwise this method returns the present nomenclatural abbreviation.
258
	 * In case the string is generated the cache strategy used is defined in
259
	 * {@link eu.etaxonomy.cdm.strategy.cache.agent.TeamDefaultCacheStrategy TeamDefaultCacheStrategy}.
260
	 * The result might be kept as nomenclatural abbreviation
261
	 * by using the {@link #setNomenclaturalTitle(String) setNomenclaturalTitle} method.
262
	 *
263
	 * @return  a string which identifies <i>this</i> team for nomenclature
264
	 */
265
	@Override
266
	@Transient
267
	public String getNomenclaturalTitle() {
268
		if (protectedNomenclaturalTitleCache == PROTECTED){
269
			return this.nomenclaturalTitle;
270
		}
271
		if (nomenclaturalTitle == null){
272
			this.nomenclaturalTitle = getCacheStrategy().getNomenclaturalTitle(this);
273
		}else{
274
			//as long as team members to not inform the team about changes the cache must be created new each time
275
			nomenclaturalTitle = getCacheStrategy().getNomenclaturalTitle(this);
276
		}
277
		return nomenclaturalTitle;
278
	}
279

    
280
	/**
281
	 * Assigns a {@link TeamOrPersonBase#nomenclaturalTitle nomenclatural identification} string to <i>this</i> team
282
	 * and protects it from overwriting.
283
	 * This method overrides {@link TeamOrPersonBase#setNomenclaturalTitle(String) setNomenclaturalTitle}.
284
	 *
285
	 * @see  #getNomenclaturalTitle()
286
	 * @see  #setNomenclaturalTitle(String, boolean)
287
	 */
288
	@Override
289
	public void setNomenclaturalTitle(String nomenclaturalTitle) {
290
		this.setNomenclaturalTitle(nomenclaturalTitle, PROTECTED);
291
	}
292

    
293
	/**
294
	 * Assigns a {@link TeamOrPersonBase#nomenclaturalTitle nomenclatural identification} string to <i>this</i> team
295
	 * and a protection flag status to this string.
296
	 *
297
	 * @see  #getNomenclaturalTitle()
298
	 */
299
	public void setNomenclaturalTitle(String nomenclaturalTitle, boolean protectedNomenclaturalTitleCache) {
300
		firePropertyChange("nomenclaturalTitle", this.nomenclaturalTitle, nomenclaturalTitle);
301
		this.nomenclaturalTitle = nomenclaturalTitle == "" ? null: nomenclaturalTitle;
302
		this.protectedNomenclaturalTitleCache = protectedNomenclaturalTitleCache;
303
	}
304

    
305
	@Override
306
	//@Transient //TODO a.kohlbecker remove??
307
	public String getTitleCache() {
308
		isGeneratingTitleCache = true;
309
		String result = "";
310
		if (isProtectedTitleCache()){
311
			result = this.titleCache;
312
		}else{
313
			result = generateTitle();
314
			result = replaceEmptyTitleByNomTitle(result);
315
			result = getTruncatedCache(result);
316
			this.titleCache = result;
317
		}
318
		isGeneratingTitleCache = false;
319
		return result;
320
	}
321

    
322
	/**
323
	 * Protected nomenclatural title cache flag should be set to true, if
324
	 * the title cache is to be preferred against the atomized data.
325
	 * This may be the case if no atomized data exists or if atomization
326
	 * was incomplete for whatever reason.
327
	 * @return
328
	 */
329
	public boolean isProtectedNomenclaturalTitleCache() {
330
		return protectedNomenclaturalTitleCache;
331
	}
332

    
333
	public void setProtectedNomenclaturalTitleCache(
334
			boolean protectedNomenclaturalTitleCache) {
335
		this.protectedNomenclaturalTitleCache = protectedNomenclaturalTitleCache;
336
	}
337

    
338

    
339
	/**
340
	 * The hasMoreMembers flag is true if this team has more members than
341
	 * mentioned in the {@link #teamMembers} list. This is usually the case
342
	 * when "et al." is used in the team representation. Formatters should add
343
	 * "et al." or an according post fix to the team string representation
344
	 *  if this flag is set.
345
	 * @return
346
	 */
347
	public boolean isHasMoreMembers() {
348
		return hasMoreMembers;
349
	}
350
	public void setHasMoreMembers(boolean hasMoreMembers) {
351
		this.hasMoreMembers = hasMoreMembers;
352
	}
353

    
354
    @Override
355
    public boolean updateCaches(){
356
        boolean result = super.updateCaches();
357
        if (this.protectedNomenclaturalTitleCache == false){
358
            String oldNomTitleCache = this.nomenclaturalTitle;
359
            this.protectedNomenclaturalTitleCache = false;
360

    
361
            String newNomTitleCache = getCacheStrategy().getNomenclaturalTitle(this);
362

    
363
            if ( oldNomTitleCache == null   || ! oldNomTitleCache.equals(newNomTitleCache) ){
364
                 this.setNomenclaturalTitle(null, false);
365
                 String newCache = this.getNomenclaturalTitle();
366

    
367
                 if (newCache == null){
368
                     logger.warn("New nomTitleCache should never be null");
369
                 }
370
                 if (oldNomTitleCache == null){
371
                     logger.info("Old nomTitleCache should never be null");
372
                 }
373
                 result = true;
374
             }
375
         }
376
        return result;
377
     }
378

    
379
//*********************** CLONE ********************************************************/
380

    
381
	/**
382
	 * Clones <i>this</i> Team. This is a shortcut that enables to create
383
	 * a new instance that differs only slightly from <i>this</i> Team.
384
	 * The corresponding person is cloned.
385
	 *
386
	 * @see java.lang.Object#clone()
387
	 */
388
	@Override
389
	public Team clone() {
390
		try{
391
			Team result = (Team)super.clone();
392
			result.teamMembers = new ArrayList<>();
393
			for (Person teamMember: this.teamMembers){
394
				result.addTeamMember(teamMember);
395
			}
396
			//no changes to protectedNomenclaturalTitleCache
397
			return result;
398
		} catch (CloneNotSupportedException e){
399
			logger.warn("Object does not implement cloneable");
400
			e.printStackTrace();
401
			return null;
402
		}
403
	}
404

    
405
}
(9-9/12)