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.common;
|
10
|
|
11
|
import java.beans.PropertyChangeEvent;
|
12
|
import java.beans.PropertyChangeListener;
|
13
|
import java.util.ArrayList;
|
14
|
import java.util.Collection;
|
15
|
import java.util.HashSet;
|
16
|
import java.util.List;
|
17
|
import java.util.Set;
|
18
|
import java.util.UUID;
|
19
|
|
20
|
import javax.persistence.Column;
|
21
|
import javax.persistence.Embedded;
|
22
|
import javax.persistence.FetchType;
|
23
|
import javax.persistence.ManyToMany;
|
24
|
import javax.persistence.MappedSuperclass;
|
25
|
import javax.persistence.OneToMany;
|
26
|
import javax.persistence.OrderColumn;
|
27
|
import javax.persistence.Transient;
|
28
|
import javax.validation.constraints.NotEmpty;
|
29
|
import javax.validation.constraints.NotNull;
|
30
|
import javax.xml.bind.annotation.XmlAccessType;
|
31
|
import javax.xml.bind.annotation.XmlAccessorType;
|
32
|
import javax.xml.bind.annotation.XmlElement;
|
33
|
import javax.xml.bind.annotation.XmlElementWrapper;
|
34
|
import javax.xml.bind.annotation.XmlTransient;
|
35
|
import javax.xml.bind.annotation.XmlType;
|
36
|
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
37
|
|
38
|
import org.apache.logging.log4j.LogManager;
|
39
|
import org.apache.logging.log4j.Logger;
|
40
|
import org.hibernate.annotations.Cascade;
|
41
|
import org.hibernate.annotations.CascadeType;
|
42
|
import org.hibernate.envers.Audited;
|
43
|
import org.hibernate.search.annotations.Analyze;
|
44
|
import org.hibernate.search.annotations.Field;
|
45
|
import org.hibernate.search.annotations.FieldBridge;
|
46
|
import org.hibernate.search.annotations.Fields;
|
47
|
import org.hibernate.search.annotations.Index;
|
48
|
import org.hibernate.search.annotations.SortableField;
|
49
|
import org.hibernate.search.annotations.Store;
|
50
|
|
51
|
import eu.etaxonomy.cdm.common.CdmUtils;
|
52
|
import eu.etaxonomy.cdm.common.URI;
|
53
|
import eu.etaxonomy.cdm.hibernate.search.StripHtmlBridge;
|
54
|
import eu.etaxonomy.cdm.jaxb.FormattedTextAdapter;
|
55
|
import eu.etaxonomy.cdm.jaxb.LSIDAdapter;
|
56
|
import eu.etaxonomy.cdm.model.media.ExternalLink;
|
57
|
import eu.etaxonomy.cdm.model.media.Rights;
|
58
|
import eu.etaxonomy.cdm.model.reference.ICdmTarget;
|
59
|
import eu.etaxonomy.cdm.model.reference.OriginalSourceBase;
|
60
|
import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
|
61
|
import eu.etaxonomy.cdm.model.reference.Reference;
|
62
|
import eu.etaxonomy.cdm.model.term.DefinedTerm;
|
63
|
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
|
64
|
import eu.etaxonomy.cdm.strategy.match.Match;
|
65
|
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
|
66
|
import eu.etaxonomy.cdm.strategy.match.MatchMode;
|
67
|
import eu.etaxonomy.cdm.strategy.merge.Merge;
|
68
|
import eu.etaxonomy.cdm.strategy.merge.MergeMode;
|
69
|
import eu.etaxonomy.cdm.validation.Level2;
|
70
|
|
71
|
/**
|
72
|
* Superclass for the primary CDM classes that can be referenced from outside via LSIDs and contain a simple generated title string as a label for human reading.
|
73
|
* All subclasses inherit the ability to store additional properties that are stored as {@link Extension Extensions}, basically a string value with a type term.
|
74
|
* Any number of right statements can be attached as well as multiple {@link OriginalSourceBase} objects.
|
75
|
* Original sources carry a reference to the source, an ID within that source and the original title/label of this object as it was used in that source (originalNameString).
|
76
|
* A Taxon for example that was taken from 2 sources like FaunaEuropaea and IPNI would have two originalSource objects.
|
77
|
* The originalSource representing that taxon as it was found in IPNI would contain IPNI as the reference, the IPNI id of the taxon and the name of the taxon exactly as it was used in IPNI.
|
78
|
*
|
79
|
* @author m.doering
|
80
|
* @since 08-Nov-2007 13:06:27
|
81
|
*/
|
82
|
@XmlAccessorType(XmlAccessType.FIELD)
|
83
|
@XmlType(name = "IdentifiableEntity", propOrder = {
|
84
|
"lsid",
|
85
|
"titleCache",
|
86
|
"protectedTitleCache",
|
87
|
"credits",
|
88
|
"extensions",
|
89
|
"identifiers",
|
90
|
"links",
|
91
|
"rights"
|
92
|
})
|
93
|
@Audited
|
94
|
@MappedSuperclass
|
95
|
public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrategy>
|
96
|
extends SourcedEntityBase<IdentifiableSource>
|
97
|
implements IIdentifiableEntity /*, ISourceable<IdentifiableSource> */ {
|
98
|
|
99
|
private static final long serialVersionUID = 7912083412108359559L;
|
100
|
private static final Logger logger = LogManager.getLogger(IdentifiableEntity.class);
|
101
|
|
102
|
@XmlTransient
|
103
|
public static final boolean PROTECTED = true;
|
104
|
@XmlTransient
|
105
|
public static final boolean NOT_PROTECTED = false;
|
106
|
|
107
|
@XmlElement(name = "LSID", type = String.class)
|
108
|
@XmlJavaTypeAdapter(LSIDAdapter.class)
|
109
|
@Embedded
|
110
|
private LSID lsid;
|
111
|
|
112
|
@XmlElement(name = "TitleCache", required = true)
|
113
|
@XmlJavaTypeAdapter(FormattedTextAdapter.class)
|
114
|
@Column(name="titleCache", length=800) //see #1592
|
115
|
@Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
|
116
|
@NotEmpty(groups = Level2.class) // implicitly NotNull
|
117
|
@Fields({
|
118
|
@Field(store=Store.YES),
|
119
|
// If the field is only needed for sorting and nothing else, you may configure it as
|
120
|
// un-indexed and un-stored, thus avoid unnecessary index growth.
|
121
|
@Field(name = "titleCache__sort", analyze = Analyze.NO, store=Store.NO, index = Index.NO)
|
122
|
})
|
123
|
@SortableField(forField = "titleCache__sort")
|
124
|
@FieldBridge(impl=StripHtmlBridge.class)
|
125
|
protected String titleCache;
|
126
|
|
127
|
//if true titleCache will not be automatically generated/updated
|
128
|
@XmlElement(name = "ProtectedTitleCache")
|
129
|
protected boolean protectedTitleCache;
|
130
|
|
131
|
@XmlElementWrapper(name = "Rights", nillable = true)
|
132
|
@XmlElement(name = "Rights")
|
133
|
@ManyToMany(fetch = FetchType.LAZY) //#5762 M:N now
|
134
|
@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
|
135
|
//TODO
|
136
|
@Merge(MergeMode.ADD_CLONE)
|
137
|
@NotNull
|
138
|
private Set<Rights> rights = new HashSet<>();
|
139
|
|
140
|
@XmlElementWrapper(name = "Credits", nillable = true)
|
141
|
@XmlElement(name = "Credit")
|
142
|
@OrderColumn(name="sortIndex")
|
143
|
@OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
|
144
|
@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
|
145
|
//TODO
|
146
|
@Merge(MergeMode.ADD_CLONE)
|
147
|
@NotNull
|
148
|
private List<Credit> credits = new ArrayList<>();
|
149
|
|
150
|
@XmlElementWrapper(name = "Extensions", nillable = true)
|
151
|
@XmlElement(name = "Extension")
|
152
|
@OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
|
153
|
@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
|
154
|
@Merge(MergeMode.ADD_CLONE)
|
155
|
@NotNull
|
156
|
private Set<Extension> extensions = new HashSet<>();
|
157
|
|
158
|
@XmlElementWrapper(name = "Identifiers", nillable = true)
|
159
|
@XmlElement(name = "Identifier")
|
160
|
@OrderColumn(name="sortIndex")
|
161
|
@OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
|
162
|
@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
|
163
|
@Merge(MergeMode.ADD_CLONE)
|
164
|
@NotNull
|
165
|
private List<Identifier> identifiers = new ArrayList<>();
|
166
|
|
167
|
@XmlElementWrapper(name = "Links", nillable = true)
|
168
|
@XmlElement(name = "Link")
|
169
|
@OneToMany(fetch=FetchType.LAZY, orphanRemoval=true)
|
170
|
@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
|
171
|
@Merge(MergeMode.ADD_CLONE)
|
172
|
private Set<ExternalLink> links = new HashSet<>();
|
173
|
|
174
|
@XmlTransient
|
175
|
@Transient
|
176
|
protected IIdentifiableEntityCacheStrategy cacheStrategy;
|
177
|
|
178
|
protected IdentifiableEntity(){
|
179
|
initListener();
|
180
|
}
|
181
|
|
182
|
@Override
|
183
|
public void initListener(){
|
184
|
PropertyChangeListener listener = new PropertyChangeListener() {
|
185
|
@Override
|
186
|
public void propertyChange(PropertyChangeEvent ev) {
|
187
|
if (! "titleCache".equals(ev.getPropertyName())
|
188
|
&& !"cacheStrategy".equals(ev.getPropertyName())
|
189
|
&& ! isProtectedTitleCache()){
|
190
|
titleCache = null;
|
191
|
}
|
192
|
}
|
193
|
};
|
194
|
addPropertyChangeListener(listener);
|
195
|
}
|
196
|
|
197
|
/**
|
198
|
* By default, we expect most cdm objects to be abstract things
|
199
|
* i.e. unable to return a data representation.
|
200
|
*
|
201
|
* Specific subclasses (e.g. Sequence) can override if necessary.
|
202
|
*/
|
203
|
@Override
|
204
|
public byte[] getData() {
|
205
|
return null;
|
206
|
}
|
207
|
|
208
|
//******************************** CACHE *****************************************************/
|
209
|
|
210
|
// @Transient - must not be transient, since this property needs to to be included in all serializations produced by the remote layer
|
211
|
@Override
|
212
|
public String getTitleCache(){
|
213
|
if (protectedTitleCache){
|
214
|
return this.titleCache;
|
215
|
}
|
216
|
// is title dirty, i.e. equal NULL?
|
217
|
if (titleCache == null){
|
218
|
this.titleCache = generateTitle();
|
219
|
this.titleCache = getTruncatedCache(this.titleCache) ;
|
220
|
}
|
221
|
//removed due to #5849
|
222
|
// if(StringUtils.isBlank(titleCache)){
|
223
|
// titleCache = this.toString();
|
224
|
// }
|
225
|
return titleCache;
|
226
|
}
|
227
|
|
228
|
@Deprecated
|
229
|
@Override
|
230
|
public void setTitleCache(String titleCache){
|
231
|
//TODO shouldn't we call setTitleCache(String, boolean), but is this conformant with JavaBean specification?
|
232
|
this.titleCache = getTruncatedCache(titleCache);
|
233
|
}
|
234
|
|
235
|
@Override
|
236
|
public void setTitleCache(String titleCache, boolean protectCache){
|
237
|
titleCache = getTruncatedCache(titleCache);
|
238
|
this.titleCache = titleCache;
|
239
|
this.protectedTitleCache = protectCache;
|
240
|
}
|
241
|
|
242
|
@Override
|
243
|
public String resetTitleCache() {
|
244
|
if(!protectedTitleCache){
|
245
|
titleCache = null;
|
246
|
}
|
247
|
return getTitleCache();
|
248
|
}
|
249
|
|
250
|
@Transient
|
251
|
protected String getTruncatedCache(String cache) {
|
252
|
int maxLength = 800;
|
253
|
if (cache != null && cache.length() > maxLength){
|
254
|
logger.warn("Truncation of cache: " + this.toString() + "/" + cache);
|
255
|
cache = cache.substring(0, maxLength - 4) + "..."; //TODO do we need -4 or is -3 enough
|
256
|
}
|
257
|
return cache;
|
258
|
}
|
259
|
|
260
|
@Override
|
261
|
public boolean isProtectedTitleCache() {
|
262
|
return protectedTitleCache;
|
263
|
}
|
264
|
|
265
|
@Override
|
266
|
public void setProtectedTitleCache(boolean protectedTitleCache) {
|
267
|
this.protectedTitleCache = protectedTitleCache;
|
268
|
}
|
269
|
|
270
|
/**
|
271
|
* @return true, if the current state of the titleCache (without generating it new)
|
272
|
* is <code>null</code> or the empty string. This is primarily meant for internal use.
|
273
|
*/
|
274
|
public boolean hasEmptyTitleCache(){
|
275
|
return this.titleCache == null || "".equals(this.titleCache);
|
276
|
}
|
277
|
|
278
|
public boolean updateCaches(){
|
279
|
if (this.protectedTitleCache == false){
|
280
|
String oldTitleCache = this.titleCache;
|
281
|
|
282
|
@SuppressWarnings("unchecked")
|
283
|
String newTitleCache = cacheStrategy().getTitleCache(this);
|
284
|
|
285
|
if ( oldTitleCache == null || ! oldTitleCache.equals(newTitleCache) ){
|
286
|
this.setTitleCache(null, false);
|
287
|
String newCache = this.getTitleCache();
|
288
|
|
289
|
if (newCache == null){
|
290
|
logger.warn("newCache should never be null");
|
291
|
}
|
292
|
if (oldTitleCache == null){
|
293
|
logger.info("oldTitleCache was illegaly null and has been fixed");
|
294
|
}
|
295
|
return true;
|
296
|
}
|
297
|
}
|
298
|
return false;
|
299
|
}
|
300
|
|
301
|
/**
|
302
|
* Updates the caches with the given cache strategy
|
303
|
* @param entityCacheStrategy
|
304
|
* @return <code>true</code> if some cache was updated, <code>false</code> otherwise
|
305
|
*/
|
306
|
public boolean updateCaches(S entityCacheStrategy){
|
307
|
S oldCacheStrategy = this.cacheStrategy();
|
308
|
this.cacheStrategy = entityCacheStrategy != null? entityCacheStrategy : this.cacheStrategy();
|
309
|
boolean result = this.updateCaches();
|
310
|
this.cacheStrategy = oldCacheStrategy;
|
311
|
return result;
|
312
|
}
|
313
|
|
314
|
//**************************************************************************************
|
315
|
|
316
|
@Override
|
317
|
public LSID getLsid(){
|
318
|
return this.lsid;
|
319
|
}
|
320
|
@Override
|
321
|
public void setLsid(LSID lsid){
|
322
|
this.lsid = lsid;
|
323
|
}
|
324
|
@Override
|
325
|
public Set<Rights> getRights() {
|
326
|
if(rights == null) {
|
327
|
this.rights = new HashSet<>();
|
328
|
}
|
329
|
return this.rights;
|
330
|
}
|
331
|
|
332
|
@Override
|
333
|
public void addRights(Rights right){
|
334
|
getRights().add(right);
|
335
|
}
|
336
|
@Override
|
337
|
public void removeRights(Rights right){
|
338
|
getRights().remove(right);
|
339
|
}
|
340
|
|
341
|
|
342
|
//********************** External Links **********************************************
|
343
|
|
344
|
|
345
|
public Set<ExternalLink> getLinks(){
|
346
|
return this.links;
|
347
|
}
|
348
|
public void setLinks(Set<ExternalLink> links){
|
349
|
this.links = links;
|
350
|
}
|
351
|
public void addLink(ExternalLink link){
|
352
|
if (link != null){
|
353
|
links.add(link);
|
354
|
}
|
355
|
}
|
356
|
public ExternalLink addLinkWebsite(URI uri, String description, Language descriptionLanguage){
|
357
|
ExternalLink link = null;
|
358
|
if (uri != null || description != null || descriptionLanguage != null){
|
359
|
link = ExternalLink.NewWebSiteInstance(uri, description, descriptionLanguage);
|
360
|
links.add(link);
|
361
|
}
|
362
|
return link;
|
363
|
}
|
364
|
public void removeLink(ExternalLink link){
|
365
|
if(links.contains(link)) {
|
366
|
links.remove(link);
|
367
|
}
|
368
|
}
|
369
|
|
370
|
//********************** CREDITS **********************************************
|
371
|
|
372
|
@Override
|
373
|
public List<Credit> getCredits() {
|
374
|
if(credits == null) {
|
375
|
this.credits = new ArrayList<>();
|
376
|
}
|
377
|
return this.credits;
|
378
|
}
|
379
|
|
380
|
@Override
|
381
|
public Credit getCredits(Integer index){
|
382
|
return getCredits().get(index);
|
383
|
}
|
384
|
|
385
|
@Override
|
386
|
public void addCredit(Credit credit){
|
387
|
getCredits().add(credit);
|
388
|
}
|
389
|
|
390
|
|
391
|
@Override
|
392
|
public void addCredit(Credit credit, int index){
|
393
|
getCredits().add(index, credit);
|
394
|
}
|
395
|
|
396
|
@Override
|
397
|
public void removeCredit(Credit credit){
|
398
|
getCredits().remove(credit);
|
399
|
}
|
400
|
|
401
|
@Override
|
402
|
public void removeCredit(int index){
|
403
|
getCredits().remove(index);
|
404
|
}
|
405
|
|
406
|
@Override
|
407
|
public boolean replaceCredit(Credit newObject, Credit oldObject){
|
408
|
return replaceInList(this.credits, newObject, oldObject);
|
409
|
}
|
410
|
|
411
|
|
412
|
@Override
|
413
|
public List<Identifier> getIdentifiers(){
|
414
|
if(this.identifiers == null) {
|
415
|
this.identifiers = new ArrayList<>();
|
416
|
}
|
417
|
return this.identifiers;
|
418
|
}
|
419
|
/**
|
420
|
* @return a set of identifier value strings
|
421
|
*/
|
422
|
public Set<String> getIdentifierStrings(DefinedTerm type){
|
423
|
return getIdentifierStrings(type == null? null :type.getUuid());
|
424
|
}
|
425
|
/**
|
426
|
* @param identifierTypeUuid
|
427
|
* @return a set of identifier value strings
|
428
|
*/
|
429
|
public Set<String> getIdentifierStrings(UUID identifierTypeUuid){
|
430
|
Set<String> result = new HashSet<>();
|
431
|
for (Identifier identifier : getIdentifiers()){
|
432
|
if ( (identifier.getType()== null && identifierTypeUuid == null)
|
433
|
|| (identifier.getType().getUuid().equals(identifierTypeUuid))){
|
434
|
result.add(identifier.getIdentifier());
|
435
|
}
|
436
|
}
|
437
|
return result;
|
438
|
}
|
439
|
/**
|
440
|
* Returns the first identifier value of the given type.
|
441
|
* <code>null</code> if no such identifier exists.
|
442
|
* @param identifierTypeUuid
|
443
|
*/
|
444
|
public String getIdentifierString(UUID identifierTypeUuid){
|
445
|
Set<Identifier> set = getIdentifiers(identifierTypeUuid);
|
446
|
return set.isEmpty()? null : set.iterator().next().getIdentifier();
|
447
|
}
|
448
|
/**
|
449
|
* Returns the first identifier of the given type.
|
450
|
* <code>null</code> if no such identifier exists.
|
451
|
* @param identifierTypeUuid
|
452
|
*/
|
453
|
public Identifier getIdentifier(UUID identifierTypeUuid){
|
454
|
Set<Identifier> set = getIdentifiers(identifierTypeUuid);
|
455
|
return set.isEmpty()? null : set.iterator().next();
|
456
|
}
|
457
|
|
458
|
public Set<Identifier> getIdentifiers(UUID identifierTypeUuid){
|
459
|
Set<Identifier> result = new HashSet<>();
|
460
|
for (Identifier identifier : getIdentifiers()){
|
461
|
if ( (identifier.getType()== null && identifierTypeUuid == null)
|
462
|
|| (identifier.getType().getUuid().equals(identifierTypeUuid))){
|
463
|
result.add(identifier);
|
464
|
}
|
465
|
}
|
466
|
return result;
|
467
|
}
|
468
|
|
469
|
@Override
|
470
|
public Identifier addIdentifier(String identifier, DefinedTerm identifierType){
|
471
|
Identifier result = Identifier.NewInstance(identifier, identifierType);
|
472
|
addIdentifier(result);
|
473
|
return result;
|
474
|
}
|
475
|
|
476
|
@Override
|
477
|
public void addIdentifier(Integer index, Identifier identifier){
|
478
|
if (identifier != null){
|
479
|
//deduplication
|
480
|
int oldIndex = getIdentifiers().indexOf(identifier);
|
481
|
if(oldIndex > -1){
|
482
|
getIdentifiers().remove(identifier);
|
483
|
if (index != null && oldIndex < index){
|
484
|
index--;
|
485
|
}
|
486
|
}
|
487
|
|
488
|
if (index != null){
|
489
|
getIdentifiers().add(index, identifier);
|
490
|
}else{
|
491
|
getIdentifiers().add(identifier);
|
492
|
}
|
493
|
}
|
494
|
}
|
495
|
|
496
|
@Override
|
497
|
public void addIdentifier(Identifier identifier){
|
498
|
addIdentifier(null, identifier);
|
499
|
}
|
500
|
|
501
|
@Override
|
502
|
public void removeIdentifier(Identifier identifier){
|
503
|
if (identifier != null){
|
504
|
getIdentifiers().remove(identifier);
|
505
|
}
|
506
|
}
|
507
|
@Override
|
508
|
public void removeIdentifier(int index){
|
509
|
getIdentifiers().remove(index);
|
510
|
}
|
511
|
|
512
|
@Override
|
513
|
public boolean replaceIdentifier(Identifier newObject, Identifier oldObject){
|
514
|
return replaceInList(this.identifiers, newObject, oldObject);
|
515
|
}
|
516
|
|
517
|
|
518
|
@Override
|
519
|
public Set<Extension> getExtensions(){
|
520
|
if(extensions == null) {
|
521
|
this.extensions = new HashSet<>();
|
522
|
}
|
523
|
return this.extensions;
|
524
|
}
|
525
|
public Set<Extension> getFilteredExtensions(UUID extensionTypeUuid){
|
526
|
Set<Extension> result = new HashSet<>();
|
527
|
for (Extension extension : getExtensions()){
|
528
|
if (extension.getType() != null && extension.getType().getUuid().equals(extensionTypeUuid)){
|
529
|
result.add(extension);
|
530
|
}
|
531
|
}
|
532
|
return result;
|
533
|
}
|
534
|
/**
|
535
|
* @param type
|
536
|
* @return a Set of extension value strings
|
537
|
*/
|
538
|
public Set<String> getExtensions(ExtensionType type){
|
539
|
return getExtensions(type.getUuid());
|
540
|
}
|
541
|
/**
|
542
|
* @param extensionTypeUuid
|
543
|
* @return a Set of extension value strings
|
544
|
* @see #hasExtension(UUID, String)
|
545
|
*/
|
546
|
public Set<String> getExtensions(UUID extensionTypeUuid){
|
547
|
Set<String> result = new HashSet<>();
|
548
|
for (Extension extension : getExtensions()){
|
549
|
if (extension.getType() != null && extension.getType().getUuid().equals(extensionTypeUuid)){
|
550
|
result.add(extension.getValue());
|
551
|
}
|
552
|
}
|
553
|
return result;
|
554
|
}
|
555
|
|
556
|
/**
|
557
|
* @see #getExtensionsConcat(Collection, String)
|
558
|
*/
|
559
|
public String getExtensionsConcat(UUID extensionTypeUuid, String separator){
|
560
|
String result = null;
|
561
|
for (Extension extension : getExtensions()){
|
562
|
if (extension.getType() != null && extension.getType().getUuid().equals(extensionTypeUuid)){
|
563
|
result = CdmUtils.concat(separator, result, extension.getValue());
|
564
|
}
|
565
|
}
|
566
|
return result;
|
567
|
}
|
568
|
|
569
|
/**
|
570
|
* Return all extensions matching the given extension type as
|
571
|
* concatenated string. If extensionTypeUuids is a sorted collection
|
572
|
* it is given in the correct order.
|
573
|
* @param extensionTypeUuids collection of the extension types to be considered
|
574
|
* @param separator the separator for concatenation
|
575
|
* @return the concatenated extension string
|
576
|
* @see #getExtensionsConcat(Collection, String)
|
577
|
*/
|
578
|
public String getExtensionsConcat(Collection<UUID> extensionTypeUuids, String separator){
|
579
|
String result = null;
|
580
|
for (UUID uuid : extensionTypeUuids){
|
581
|
String extension = getExtensionsConcat(uuid, separator);
|
582
|
result = CdmUtils.concat(separator, result, extension);
|
583
|
}
|
584
|
return result;
|
585
|
}
|
586
|
|
587
|
/**
|
588
|
* Has this entity an extension of given type with value 'value'.
|
589
|
* If value is <code>null</code> <code>true</code> is returned if
|
590
|
* an Extension exists with given type and 'value' is <code>null</code>.
|
591
|
* @param extensionTypeUuid
|
592
|
* @param value
|
593
|
* @see #hasExtension(ExtensionType, String)
|
594
|
* @see #getExtensions(UUID)
|
595
|
*/
|
596
|
public boolean hasExtension(UUID extensionTypeUuid, String value) {
|
597
|
for (String ext : this.getExtensions(extensionTypeUuid)){
|
598
|
if (CdmUtils.nullSafeEqual(ext, value)){
|
599
|
return true;
|
600
|
}
|
601
|
}
|
602
|
return false;
|
603
|
}
|
604
|
|
605
|
/**
|
606
|
* @see #hasExtension(UUID, String)
|
607
|
*/
|
608
|
public boolean hasExtension(ExtensionType extensionType, String value) {
|
609
|
return hasExtension(extensionType.getUuid(), value);
|
610
|
}
|
611
|
|
612
|
@Override
|
613
|
public void addExtension(String value, ExtensionType extensionType){
|
614
|
Extension.NewInstance(this, value, extensionType);
|
615
|
}
|
616
|
|
617
|
@Override
|
618
|
public void addExtension(Extension extension){
|
619
|
if (extension != null){
|
620
|
getExtensions().add(extension);
|
621
|
}
|
622
|
}
|
623
|
@Override
|
624
|
public void removeExtension(Extension extension){
|
625
|
if (extension != null){
|
626
|
getExtensions().remove(extension);
|
627
|
}
|
628
|
}
|
629
|
|
630
|
@Override
|
631
|
public void addSource(IdentifiableSource source) {
|
632
|
if (source != null){
|
633
|
getSources().add(source);
|
634
|
}
|
635
|
}
|
636
|
|
637
|
@Override
|
638
|
public void addSources(Set<IdentifiableSource> sources) {
|
639
|
if (sources != null){
|
640
|
for (IdentifiableSource source: sources){
|
641
|
getSources().add(source);
|
642
|
}
|
643
|
}
|
644
|
}
|
645
|
|
646
|
@Override
|
647
|
protected IdentifiableSource createNewSource(OriginalSourceType type, String idInSource, String idNamespace,
|
648
|
Reference reference, String microReference, String originalInfo, ICdmTarget target) {
|
649
|
return IdentifiableSource.NewInstance(type, idInSource, idNamespace, reference, microReference, originalInfo, target);
|
650
|
}
|
651
|
|
652
|
//******************************** TO STRING *****************************************************/
|
653
|
|
654
|
@Override
|
655
|
public String toString() {
|
656
|
String result;
|
657
|
if (isBlank(titleCache)){
|
658
|
result = super.toString();
|
659
|
}else{
|
660
|
result = this.titleCache;
|
661
|
}
|
662
|
return result;
|
663
|
}
|
664
|
|
665
|
|
666
|
/**
|
667
|
* Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
|
668
|
* several strings corresponding to <i>this</i> identifiable entity
|
669
|
* (in particular taxon name caches and author strings).
|
670
|
*
|
671
|
* @return the cache strategy used for <i>this</i> identifiable entity
|
672
|
* @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
|
673
|
*/
|
674
|
@Transient
|
675
|
@java.beans.Transient
|
676
|
public S cacheStrategy() {
|
677
|
if (this.cacheStrategy == null){
|
678
|
initDefaultCacheStrategy();
|
679
|
}
|
680
|
return (S)this.cacheStrategy;
|
681
|
}
|
682
|
public void setCacheStrategy(S cacheStrategy) {
|
683
|
this.cacheStrategy = cacheStrategy;
|
684
|
}
|
685
|
|
686
|
@Override
|
687
|
public String generateTitle() {
|
688
|
if (cacheStrategy() == null){
|
689
|
//logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
|
690
|
return this.getClass() + ": " + this.getUuid();
|
691
|
}else{
|
692
|
S cacheStrategy = cacheStrategy();
|
693
|
return cacheStrategy.getTitleCache(this);
|
694
|
}
|
695
|
}
|
696
|
|
697
|
/**
|
698
|
* Subclasses should implement setting the default cache strategy
|
699
|
*/
|
700
|
protected abstract void initDefaultCacheStrategy();
|
701
|
|
702
|
//****************** CLONE ************************************************/
|
703
|
|
704
|
@Override
|
705
|
public IdentifiableEntity<S> clone() throws CloneNotSupportedException{
|
706
|
|
707
|
@SuppressWarnings("unchecked")
|
708
|
IdentifiableEntity<S> result = (IdentifiableEntity<S>)super.clone();
|
709
|
|
710
|
//Extensions
|
711
|
result.extensions = new HashSet<>();
|
712
|
for (Extension extension : getExtensions() ){
|
713
|
Extension newExtension = extension.clone();
|
714
|
result.addExtension(newExtension);
|
715
|
}
|
716
|
|
717
|
//Identifier
|
718
|
result.identifiers = new ArrayList<>();
|
719
|
for (Identifier identifier : getIdentifiers() ){
|
720
|
Identifier newIdentifier = identifier.clone();
|
721
|
result.addIdentifier(newIdentifier);
|
722
|
}
|
723
|
|
724
|
//Rights - reusable since #5762
|
725
|
result.rights = new HashSet<>();
|
726
|
for(Rights right : getRights()) {
|
727
|
result.addRights(right);
|
728
|
}
|
729
|
|
730
|
//Credits
|
731
|
result.credits = new ArrayList<>();
|
732
|
for(Credit credit : getCredits()) {
|
733
|
Credit newCredit = credit.clone();
|
734
|
result.addCredit(newCredit);
|
735
|
}
|
736
|
|
737
|
//Links
|
738
|
result.links = new HashSet<>();
|
739
|
for(ExternalLink link : getLinks()) {
|
740
|
ExternalLink newLink = link.clone();
|
741
|
result.addLink(newLink);
|
742
|
}
|
743
|
|
744
|
//no changes to: lsid, titleCache, protectedTitleCache
|
745
|
|
746
|
//empty titleCache
|
747
|
if (! protectedTitleCache){
|
748
|
result.titleCache = null;
|
749
|
}
|
750
|
|
751
|
result.initListener(); //TODO why do we need this, isnt't the listener in constructor enough?
|
752
|
return result;
|
753
|
}
|
754
|
}
|