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