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