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.common;
|
11
|
|
12
|
import java.lang.reflect.Constructor;
|
13
|
import java.net.URI;
|
14
|
import java.util.HashSet;
|
15
|
import java.util.List;
|
16
|
import java.util.Map;
|
17
|
import java.util.Set;
|
18
|
import java.util.UUID;
|
19
|
|
20
|
import javax.persistence.Column;
|
21
|
import javax.persistence.Entity;
|
22
|
import javax.persistence.FetchType;
|
23
|
import javax.persistence.Inheritance;
|
24
|
import javax.persistence.InheritanceType;
|
25
|
import javax.persistence.ManyToMany;
|
26
|
import javax.persistence.ManyToOne;
|
27
|
import javax.persistence.OneToMany;
|
28
|
import javax.persistence.Transient;
|
29
|
import javax.xml.bind.annotation.XmlAccessType;
|
30
|
import javax.xml.bind.annotation.XmlAccessorType;
|
31
|
import javax.xml.bind.annotation.XmlElement;
|
32
|
import javax.xml.bind.annotation.XmlElementWrapper;
|
33
|
import javax.xml.bind.annotation.XmlIDREF;
|
34
|
import javax.xml.bind.annotation.XmlRootElement;
|
35
|
import javax.xml.bind.annotation.XmlSchemaType;
|
36
|
import javax.xml.bind.annotation.XmlSeeAlso;
|
37
|
import javax.xml.bind.annotation.XmlTransient;
|
38
|
import javax.xml.bind.annotation.XmlType;
|
39
|
|
40
|
import org.apache.log4j.Logger;
|
41
|
import org.codehaus.plexus.util.StringUtils;
|
42
|
import org.hibernate.annotations.Cascade;
|
43
|
import org.hibernate.annotations.CascadeType;
|
44
|
import org.hibernate.envers.Audited;
|
45
|
import org.hibernate.proxy.HibernateProxy;
|
46
|
import org.hibernate.proxy.LazyInitializer;
|
47
|
import org.hibernate.search.annotations.ClassBridge;
|
48
|
|
49
|
import au.com.bytecode.opencsv.CSVWriter;
|
50
|
import eu.etaxonomy.cdm.hibernate.search.DefinedTermBaseClassBridge;
|
51
|
import eu.etaxonomy.cdm.model.ICdmUuidCacher;
|
52
|
import eu.etaxonomy.cdm.model.description.Feature;
|
53
|
import eu.etaxonomy.cdm.model.description.MeasurementUnit;
|
54
|
import eu.etaxonomy.cdm.model.description.StatisticalMeasure;
|
55
|
import eu.etaxonomy.cdm.model.description.TextFormat;
|
56
|
import eu.etaxonomy.cdm.model.location.NamedAreaType;
|
57
|
import eu.etaxonomy.cdm.model.location.ReferenceSystem;
|
58
|
import eu.etaxonomy.cdm.model.media.Media;
|
59
|
import eu.etaxonomy.cdm.model.media.RightsType;
|
60
|
import eu.etaxonomy.cdm.model.name.NomenclaturalCode;
|
61
|
import eu.etaxonomy.cdm.model.occurrence.DerivationEventType;
|
62
|
import eu.etaxonomy.cdm.model.occurrence.PreservationMethod;
|
63
|
|
64
|
|
65
|
/**
|
66
|
* workaround for enumerations, base type according to TDWG. For linear ordering
|
67
|
* use partOf relation and BreadthFirst. Default iterator order should therefore
|
68
|
* be BreadthFirst (not DepthFirst)
|
69
|
* @author m.doering
|
70
|
* @created 08-Nov-2007 13:06:19
|
71
|
*/
|
72
|
@XmlAccessorType(XmlAccessType.FIELD)
|
73
|
@XmlType(name = "DefinedTermBase", propOrder = {
|
74
|
"media",
|
75
|
"vocabulary",
|
76
|
"idInVocabulary",
|
77
|
"symbol"
|
78
|
})
|
79
|
@XmlRootElement(name = "DefinedTermBase")
|
80
|
@XmlSeeAlso({
|
81
|
AnnotationType.class,
|
82
|
DerivationEventType.class,
|
83
|
DefinedTerm.class,
|
84
|
ExtensionType.class,
|
85
|
Feature.class,
|
86
|
Language.class,
|
87
|
MarkerType.class,
|
88
|
MeasurementUnit.class,
|
89
|
NamedAreaType.class,
|
90
|
NomenclaturalCode.class,
|
91
|
PreservationMethod.class,
|
92
|
ReferenceSystem.class,
|
93
|
RightsType.class,
|
94
|
StatisticalMeasure.class,
|
95
|
TextFormat.class
|
96
|
})
|
97
|
@Entity
|
98
|
@Audited
|
99
|
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
|
100
|
@ClassBridge(impl=DefinedTermBaseClassBridge.class)
|
101
|
//TODO Comparable implemented only for fixing failing JAXB import, may be removed when this is fixed
|
102
|
public abstract class DefinedTermBase<T extends DefinedTermBase> extends TermBase implements ILoadableTerm<T>, IDefinedTerm<T>, Comparable<T> {
|
103
|
private static final long serialVersionUID = 2931811562248571531L;
|
104
|
private static final Logger logger = Logger.getLogger(DefinedTermBase.class);
|
105
|
|
106
|
// @XmlElement(name = "KindOf")
|
107
|
// @XmlIDREF
|
108
|
// @XmlSchemaType(name = "IDREF")
|
109
|
@XmlTransient
|
110
|
@ManyToOne(fetch = FetchType.LAZY, targetEntity = DefinedTermBase.class)
|
111
|
@Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
|
112
|
private T kindOf;
|
113
|
/**
|
114
|
* FIXME - Hibernate returns this as a collection of CGLibProxy$$DefinedTermBase objects
|
115
|
* which can't be cast to instances of T - can we explicitly initialize these terms using
|
116
|
* Hibernate.initialize(), does this imply a distinct load, and find methods in the dao?
|
117
|
*/
|
118
|
// @XmlElementWrapper(name = "Generalizations")
|
119
|
// @XmlElement(name = "GeneralizationOf")
|
120
|
// @XmlIDREF
|
121
|
// @XmlSchemaType(name = "IDREF")
|
122
|
@XmlTransient
|
123
|
@OneToMany(fetch=FetchType.LAZY, mappedBy = "kindOf", targetEntity = DefinedTermBase.class)
|
124
|
@Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
|
125
|
private Set<T> generalizationOf = new HashSet<T>();
|
126
|
|
127
|
// @XmlElement(name = "PartOf")
|
128
|
// @XmlIDREF
|
129
|
// @XmlSchemaType(name = "IDREF")
|
130
|
@XmlTransient
|
131
|
@ManyToOne(fetch = FetchType.LAZY, targetEntity = DefinedTermBase.class)
|
132
|
@Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
|
133
|
protected T partOf;
|
134
|
|
135
|
/**
|
136
|
* FIXME - Hibernate retuns this as a collection of CGLibProxy$$DefinedTermBase objects
|
137
|
* which can't be cast to instances of T - can we explicitly initialize these terms using
|
138
|
* Hibernate.initialize(), does this imply a distinct load, and find methods in the dao?
|
139
|
*/
|
140
|
// @XmlElementWrapper(name = "Includes")
|
141
|
// @XmlElement(name = "Include")
|
142
|
// @XmlIDREF
|
143
|
// @XmlSchemaType(name = "IDREF")
|
144
|
@XmlTransient
|
145
|
@OneToMany(fetch=FetchType.LAZY, mappedBy = "partOf", targetEntity = DefinedTermBase.class)
|
146
|
@Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
|
147
|
private Set<T> includes = new HashSet<T>();
|
148
|
|
149
|
@XmlElementWrapper(name = "Media")
|
150
|
@XmlElement(name = "Medium")
|
151
|
@XmlIDREF
|
152
|
@XmlSchemaType(name = "IDREF")
|
153
|
@ManyToMany(fetch = FetchType.LAZY)
|
154
|
@Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
|
155
|
private Set<Media> media = new HashSet<Media>();
|
156
|
|
157
|
@XmlElement(name = "TermVocabulary")
|
158
|
@XmlIDREF
|
159
|
@XmlSchemaType(name = "IDREF")
|
160
|
@ManyToOne(fetch=FetchType.LAZY)
|
161
|
// @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE}) remove cascading #5754
|
162
|
protected TermVocabulary<T> vocabulary;
|
163
|
|
164
|
//the unique iedentifier/name this term uses in its given vocabulary #3479
|
165
|
@XmlElement(name = "idInVocabulary")
|
166
|
@Column(length=255)
|
167
|
//TODO Val #3379, #4245
|
168
|
// @NullOrNotEmpty
|
169
|
private String idInVocabulary; //the unique identifier/name this term uses in its given vocabulary #3479
|
170
|
|
171
|
@XmlElement(name = "symbol")
|
172
|
@Column(length=30)
|
173
|
//the symbol to be used in String representations for this term #5734
|
174
|
//this term can be changed by the database instance even if the term is not managed by this instance as it is only for representation and has no semantic or identifying character
|
175
|
//empty string is explicitly allowed and should be distinguished from NULL!
|
176
|
private String symbol;
|
177
|
|
178
|
//***************************** CONSTRUCTOR *******************************************/
|
179
|
|
180
|
//for javassit only
|
181
|
@Deprecated
|
182
|
protected DefinedTermBase(){};
|
183
|
|
184
|
protected DefinedTermBase(TermType type) {
|
185
|
super(type);
|
186
|
}
|
187
|
public DefinedTermBase(TermType type, String description, String label, String labelAbbrev) {
|
188
|
super(type, description, label, labelAbbrev);
|
189
|
}
|
190
|
|
191
|
|
192
|
//********************** GETTER /SETTER *************************************
|
193
|
|
194
|
@Override
|
195
|
public String getIdInVocabulary() {
|
196
|
return idInVocabulary;
|
197
|
}
|
198
|
|
199
|
@Override
|
200
|
public void setIdInVocabulary(String idInVocabulary) {
|
201
|
|
202
|
this.idInVocabulary = StringUtils.isBlank(idInVocabulary)? null : idInVocabulary;
|
203
|
}
|
204
|
|
205
|
@Override
|
206
|
public T getKindOf(){
|
207
|
|
208
|
if (this instanceof HibernateProxy) {
|
209
|
HibernateProxy proxy = (HibernateProxy) this;
|
210
|
LazyInitializer li = proxy.getHibernateLazyInitializer();
|
211
|
return (T) ((T)li.getImplementation()).getKindOf();
|
212
|
} else {
|
213
|
return (T)DefinedTermBase.deproxy(this.kindOf, this.getClass());
|
214
|
}
|
215
|
}
|
216
|
|
217
|
public void setKindOf(T kindOf){
|
218
|
this.kindOf = kindOf;
|
219
|
}
|
220
|
|
221
|
|
222
|
@Override
|
223
|
public Set<T> getGeneralizationOf(){
|
224
|
return this.generalizationOf;
|
225
|
}
|
226
|
|
227
|
protected void setGeneralizationOf(Set<T> value) {
|
228
|
this.generalizationOf = value;
|
229
|
}
|
230
|
|
231
|
public void addGeneralizationOf(T generalization) {
|
232
|
generalization.setKindOf(this);
|
233
|
this.generalizationOf.add(generalization);
|
234
|
}
|
235
|
|
236
|
|
237
|
public void removeGeneralization(T generalization) {
|
238
|
if(generalizationOf.contains(generalization)){
|
239
|
generalization.setKindOf(null);
|
240
|
this.generalizationOf.remove(generalization);
|
241
|
}
|
242
|
}
|
243
|
|
244
|
@Override
|
245
|
public T getPartOf(){
|
246
|
if (this instanceof HibernateProxy) {
|
247
|
HibernateProxy proxy = (HibernateProxy) this;
|
248
|
LazyInitializer li = proxy.getHibernateLazyInitializer();
|
249
|
return (T) ((T)li.getImplementation()).getPartOf();
|
250
|
} else {
|
251
|
return (T)DefinedTermBase.deproxy(this.partOf, this.getClass());
|
252
|
}
|
253
|
}
|
254
|
|
255
|
/**
|
256
|
* @see #getPartOf()
|
257
|
*/
|
258
|
public void setPartOf(T partOf){
|
259
|
this.partOf = partOf;
|
260
|
}
|
261
|
|
262
|
|
263
|
//TODO Comparable implemented only for fixing failing JAXB imports, may be removed when this is fixed
|
264
|
@Override
|
265
|
@Deprecated //for inner use only
|
266
|
public int compareTo(T other) {
|
267
|
return ((Integer)this.getId()).compareTo(other.getId());
|
268
|
}
|
269
|
|
270
|
@Override
|
271
|
public Set<T> getIncludes(){
|
272
|
return this.includes;
|
273
|
}
|
274
|
|
275
|
/**
|
276
|
* @see #getIncludes()
|
277
|
*/
|
278
|
protected void setIncludes(Set<T> includes) {
|
279
|
this.includes = includes;
|
280
|
}
|
281
|
|
282
|
/**
|
283
|
* @see #getIncludes()
|
284
|
*/
|
285
|
public void addIncludes(T includes) {
|
286
|
includes.setPartOf(this);
|
287
|
this.includes.add(includes);
|
288
|
}
|
289
|
|
290
|
/**
|
291
|
* @see #getIncludes()
|
292
|
*/
|
293
|
public void removeIncludes(T includes) {
|
294
|
if(this.includes.contains(includes)) {
|
295
|
includes.setPartOf(null);
|
296
|
this.includes.remove(includes);
|
297
|
}
|
298
|
}
|
299
|
|
300
|
@Override
|
301
|
public Set<Media> getMedia(){
|
302
|
return this.media;
|
303
|
}
|
304
|
|
305
|
public void addMedia(Media media) {
|
306
|
this.media.add(media);
|
307
|
}
|
308
|
public void removeMedia(Media media) {
|
309
|
this.media.remove(media);
|
310
|
}
|
311
|
|
312
|
/**
|
313
|
* @return
|
314
|
*/
|
315
|
public TermVocabulary<T> getVocabulary() {
|
316
|
return this.vocabulary;
|
317
|
}
|
318
|
|
319
|
//for bedirectional use only, use vocabulary.addTerm instead
|
320
|
/**
|
321
|
* @param newVocabulary
|
322
|
*/
|
323
|
protected void setVocabulary(TermVocabulary<T> newVocabulary) {
|
324
|
this.vocabulary = newVocabulary;
|
325
|
}
|
326
|
|
327
|
|
328
|
public String getSymbol() {
|
329
|
return symbol;
|
330
|
}
|
331
|
public void setSymbol(String symbol) {
|
332
|
this.symbol = symbol;
|
333
|
}
|
334
|
|
335
|
//******************************* METHODS ******************************************************/
|
336
|
|
337
|
|
338
|
@Override
|
339
|
public boolean isKindOf(T ancestor) {
|
340
|
if (kindOf == null || ancestor == null){
|
341
|
return false;
|
342
|
}else if (kindOf.equals(ancestor)){
|
343
|
return true;
|
344
|
}else{
|
345
|
return kindOf.isKindOf(ancestor);
|
346
|
}
|
347
|
}
|
348
|
|
349
|
@Override
|
350
|
public Set<T> getGeneralizationOf(boolean recursive) {
|
351
|
Set<T> result = new HashSet<T>();
|
352
|
result.addAll(this.generalizationOf);
|
353
|
if (recursive){
|
354
|
for (T child : this.generalizationOf){
|
355
|
result.addAll(child.getGeneralizationOf());
|
356
|
}
|
357
|
}
|
358
|
return result;
|
359
|
}
|
360
|
|
361
|
|
362
|
|
363
|
public abstract void resetTerms();
|
364
|
|
365
|
protected abstract void setDefaultTerms(TermVocabulary<T> termVocabulary);
|
366
|
|
367
|
|
368
|
@Override
|
369
|
public T readCsvLine(Class<T> termClass, List<String> csvLine, Map<UUID,DefinedTermBase> terms, boolean abbrevAsId) {
|
370
|
try {
|
371
|
T newInstance = getInstance(termClass);
|
372
|
readCsvLine(newInstance, csvLine, Language.CSV_LANGUAGE(), abbrevAsId);
|
373
|
readIsPartOf(newInstance, csvLine, terms);
|
374
|
return newInstance;
|
375
|
} catch (Exception e) {
|
376
|
logger.error(e);
|
377
|
for(StackTraceElement ste : e.getStackTrace()) {
|
378
|
logger.error(ste);
|
379
|
}
|
380
|
throw new RuntimeException(e);
|
381
|
}
|
382
|
}
|
383
|
|
384
|
protected static <TERM extends DefinedTermBase> TERM readCsvLine(TERM newInstance, List<String> csvLine, Language lang, boolean abbrevAsId) {
|
385
|
newInstance.setUuid(UUID.fromString(csvLine.get(0)));
|
386
|
newInstance.setUri( URI.create(csvLine.get(1)));
|
387
|
String label = csvLine.get(2).trim();
|
388
|
String description = csvLine.get(3);
|
389
|
String abbreviatedLabel = csvLine.get(4);
|
390
|
if (StringUtils.isBlank(abbreviatedLabel)){
|
391
|
abbreviatedLabel = null;
|
392
|
}
|
393
|
if (abbrevAsId){
|
394
|
newInstance.setIdInVocabulary(abbreviatedLabel); //new in 3.3
|
395
|
}
|
396
|
newInstance.addRepresentation(Representation.NewInstance(description, label, abbreviatedLabel, lang) );
|
397
|
|
398
|
return newInstance;
|
399
|
}
|
400
|
|
401
|
protected void readIsPartOf(T newInstance, List<String> csvLine, Map<UUID, DefinedTermBase> terms){
|
402
|
int index = partOfCsvLineIndex();
|
403
|
if (index != -1){
|
404
|
String partOfString = csvLine.get(index);
|
405
|
if(StringUtils.isNotBlank(partOfString)) {
|
406
|
UUID partOfUuid = UUID.fromString(partOfString);
|
407
|
DefinedTermBase partOf = terms.get(partOfUuid);
|
408
|
partOf.addIncludes(newInstance);
|
409
|
}
|
410
|
}
|
411
|
|
412
|
}
|
413
|
|
414
|
/**
|
415
|
* Get the
|
416
|
* @return
|
417
|
*/
|
418
|
protected int partOfCsvLineIndex() {
|
419
|
return -1;
|
420
|
}
|
421
|
|
422
|
|
423
|
private <T extends DefinedTermBase> T getInstance(Class<? extends DefinedTermBase> termClass) {
|
424
|
try {
|
425
|
Constructor<T> c = ((Class<T>)termClass).getDeclaredConstructor();
|
426
|
c.setAccessible(true);
|
427
|
T termInstance = c.newInstance();
|
428
|
return termInstance;
|
429
|
} catch (Exception e) {
|
430
|
throw new RuntimeException(e);
|
431
|
}
|
432
|
}
|
433
|
|
434
|
@Override
|
435
|
public void writeCsvLine(CSVWriter writer, T term) {
|
436
|
String [] line = new String[4];
|
437
|
line[0] = term.getUuid().toString();
|
438
|
line[1] = term.getUri().toString();
|
439
|
line[2] = term.getLabel();
|
440
|
line[3] = term.getDescription();
|
441
|
writer.writeNext(line);
|
442
|
}
|
443
|
|
444
|
@Transient
|
445
|
public T getByUuid(UUID uuid){
|
446
|
return this.vocabulary.findTermByUuid(uuid);
|
447
|
}
|
448
|
|
449
|
|
450
|
//*********************** CLONE ********************************************************/
|
451
|
|
452
|
/**
|
453
|
* Clones <i>this</i> DefinedTermBase. This is a shortcut that enables to create
|
454
|
* a new instance that differs only slightly from <i>this</i> defined term base by
|
455
|
* modifying only some of the attributes.
|
456
|
*
|
457
|
* @see eu.etaxonomy.cdm.model.common.TermBase#clone()
|
458
|
* @see java.lang.Object#clone()
|
459
|
*/
|
460
|
@Override
|
461
|
public Object clone() {
|
462
|
DefinedTermBase result;
|
463
|
try {
|
464
|
result = (DefinedTermBase) super.clone();
|
465
|
}catch (CloneNotSupportedException e) {
|
466
|
logger.warn("Object does not implement cloneable");
|
467
|
e.printStackTrace();
|
468
|
return null;
|
469
|
}
|
470
|
|
471
|
result.generalizationOf = new HashSet<DefinedTermBase>();
|
472
|
for (DefinedTermBase generalizationOf : this.generalizationOf){
|
473
|
result.generalizationOf.add(generalizationOf.clone());
|
474
|
}
|
475
|
|
476
|
result.includes = new HashSet<DefinedTermBase>();
|
477
|
|
478
|
for (DefinedTermBase include: this.includes){
|
479
|
result.includes.add(include.clone());
|
480
|
}
|
481
|
|
482
|
result.media = new HashSet<Media>();
|
483
|
|
484
|
for (Media media: this.media){
|
485
|
result.addMedia(media);
|
486
|
}
|
487
|
|
488
|
return result;
|
489
|
}
|
490
|
|
491
|
// Currently the CDM Caching mechanism is only used for caching terms
|
492
|
private static ICdmUuidCacher cacher;
|
493
|
|
494
|
|
495
|
/**
|
496
|
* Gets the CDM cacher object
|
497
|
*
|
498
|
* @return the CDM cacher object
|
499
|
*/
|
500
|
public static ICdmUuidCacher getCacher() {
|
501
|
return cacher;
|
502
|
}
|
503
|
|
504
|
/**
|
505
|
* Sets the CDM cacher object
|
506
|
*
|
507
|
* @param cacher the CDM cacher object
|
508
|
*/
|
509
|
public static void setCacher(ICdmUuidCacher cacher) {
|
510
|
DefinedTermBase.cacher = cacher;
|
511
|
}
|
512
|
|
513
|
public static <T extends DefinedTermBase> T getTermByClassAndUUID(Class<T> clazz, UUID uuid) {
|
514
|
if(cacher != null) {
|
515
|
Object obj = getCacher().load(uuid);
|
516
|
if(obj != null && obj.getClass().equals(clazz)) {
|
517
|
return (T)obj;
|
518
|
}
|
519
|
}
|
520
|
return null;
|
521
|
}
|
522
|
}
|