Merge branch 'release/5.15.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.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.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.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 public DefinedTermBase(TermType type, String description, String label, String labelAbbrev) {
208 super(type, description, label, labelAbbrev);
209 }
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
222 this.idInVocabulary = CdmUtils.isBlank(idInVocabulary)? null : idInVocabulary;
223 }
224
225 @Override
226 public T getKindOf(){
227
228 if (this instanceof HibernateProxy) {
229 HibernateProxy proxy = (HibernateProxy) this;
230 LazyInitializer li = proxy.getHibernateLazyInitializer();
231 return (T)((T)li.getImplementation()).getKindOf();
232 } else {
233 return (T)DefinedTermBase.deproxy(this.kindOf, this.getClass());
234 }
235 }
236
237 public void setKindOf(T kindOf){
238 this.kindOf = kindOf;
239 }
240
241
242 @Override
243 public Set<T> getGeneralizationOf(){
244 return this.generalizationOf;
245 }
246
247 protected void setGeneralizationOf(Set<T> value) {
248 this.generalizationOf = value;
249 }
250
251 public void addGeneralizationOf(T generalization) {
252 checkTermType(generalization);
253 generalization.setKindOf(this);
254 this.generalizationOf.add(generalization);
255 }
256
257
258 public void removeGeneralization(T generalization) {
259 if(generalizationOf.contains(generalization)){
260 generalization.setKindOf(null);
261 this.generalizationOf.remove(generalization);
262 }
263 }
264
265 @Override
266 public T getPartOf(){
267 if (this instanceof HibernateProxy) {
268 HibernateProxy proxy = (HibernateProxy) this;
269 LazyInitializer li = proxy.getHibernateLazyInitializer();
270 return (T)((T)li.getImplementation()).getPartOf();
271 } else {
272 return (T)DefinedTermBase.deproxy(this.partOf, this.getClass());
273 }
274 }
275
276 /**
277 * @see #getPartOf()
278 */
279 public void setPartOf(T partOf){
280 this.partOf = partOf;
281 }
282
283
284 //TODO Comparable implemented only for fixing failing JAXB imports, may be removed when this is fixed
285 @Override
286 @Deprecated //for inner use only
287 public int compareTo(T other) {
288 return ((Integer)this.getId()).compareTo(other.getId());
289 }
290
291 @Override
292 public Set<T> getIncludes(){
293 return this.includes;
294 }
295
296 /**
297 * @see #getIncludes()
298 */
299 protected void setIncludes(Set<T> includes) {
300 this.includes = includes;
301 }
302
303 /**
304 * @see #getIncludes()
305 */
306 public void addIncludes(T includes) {
307 checkTermType(includes);
308 includes.setPartOf(this);
309 this.includes.add(includes);
310 }
311
312 /**
313 * @see #getIncludes()
314 */
315 public void removeIncludes(T includes) {
316 if(this.includes.contains(includes)) {
317 includes.setPartOf(null);
318 this.includes.remove(includes);
319 }
320 }
321
322 @Override
323 public Set<Media> getMedia(){
324 return this.media;
325 }
326
327 public void addMedia(Media media) {
328 this.media.add(media);
329 }
330 public void removeMedia(Media media) {
331 this.media.remove(media);
332 }
333
334 /**
335 * @return
336 */
337 public TermVocabulary<T> getVocabulary() {
338 return this.vocabulary;
339 }
340
341 //for bedirectional use only, use vocabulary.addTerm instead
342 /**
343 * @param newVocabulary
344 */
345 protected void setVocabulary(TermVocabulary<T> newVocabulary) {
346 this.vocabulary = newVocabulary;
347 }
348
349
350 public String getSymbol() {
351 return symbol;
352 }
353 public void setSymbol(String symbol) {
354 this.symbol = symbol;
355 }
356 /**
357 * @return the symbol2
358 */
359 public String getSymbol2() {
360 return symbol2;
361 }
362
363 /**
364 * @param symbol2 the symbol2 to set
365 */
366 public void setSymbol2(String symbol2) {
367 this.symbol2 = symbol2;
368 }
369
370 //******************************* METHODS ******************************************************/
371
372
373
374 @Override
375 public boolean isKindOf(T ancestor) {
376 if (kindOf == null || ancestor == null){
377 return false;
378 }else if (kindOf.equals(ancestor)){
379 return true;
380 }else{
381 return kindOf.isKindOf(ancestor);
382 }
383 }
384
385 @Override
386 public Set<T> getGeneralizationOf(boolean recursive) {
387 Set<T> result = new HashSet<T>();
388 result.addAll(this.generalizationOf);
389 if (recursive){
390 for (T child : this.generalizationOf){
391 result.addAll(child.getGeneralizationOf());
392 }
393 }
394 return result;
395 }
396
397
398 public abstract void resetTerms();
399
400 protected abstract void setDefaultTerms(TermVocabulary<T> termVocabulary);
401
402
403 @Override
404 public T readCsvLine(Class<T> termClass, List<String> csvLine, TermType termType, Map<UUID,DefinedTermBase> terms, boolean abbrevAsId) {
405 try {
406 T newInstance = getInstance(termClass, termType);
407 readCsvLine(newInstance, csvLine, Language.CSV_LANGUAGE(), abbrevAsId);
408 readIsPartOf(newInstance, csvLine, terms);
409 return newInstance;
410 } catch (Exception e) {
411 logger.error(e);
412 for(StackTraceElement ste : e.getStackTrace()) {
413 logger.error(ste);
414 }
415 throw new RuntimeException(e);
416 }
417 }
418
419 protected static <TERM extends DefinedTermBase> TERM readCsvLine(TERM newInstance, List<String> csvLine, Language lang, boolean abbrevAsId) {
420 newInstance.setUuid(UUID.fromString(csvLine.get(0)));
421 String uriStr = CdmUtils.Ne(csvLine.get(1));
422 newInstance.setUri(uriStr == null? null: URI.create(uriStr));
423 String label = csvLine.get(2).trim();
424 String description = CdmUtils.Ne(csvLine.get(3).trim());
425 String abbreviatedLabel = CdmUtils.Ne(csvLine.get(4).trim());
426 if (CdmUtils.isBlank(abbreviatedLabel)){
427 abbreviatedLabel = null;
428 }
429 if (abbrevAsId){
430 newInstance.setIdInVocabulary(abbreviatedLabel); //new in 3.3
431 }
432 newInstance.addRepresentation(Representation.NewInstance(description, label, abbreviatedLabel, lang) );
433
434 return newInstance;
435 }
436
437 protected void readIsPartOf(T newInstance, List<String> csvLine, Map<UUID, DefinedTermBase> terms){
438 int index = partOfCsvLineIndex();
439 if (index != -1){
440 String partOfString = csvLine.get(index);
441 if(CdmUtils.isNotBlank(partOfString)) {
442 UUID partOfUuid = UUID.fromString(partOfString);
443 DefinedTermBase partOf = terms.get(partOfUuid);
444 partOf.addIncludes(newInstance);
445 }
446 }
447
448 }
449
450 /**
451 * Get the
452 * @return
453 */
454 protected int partOfCsvLineIndex() {
455 return -1;
456 }
457
458
459 private <T extends DefinedTermBase> T getInstance(Class<? extends DefinedTermBase> termClass, TermType termType) {
460 try {
461 Constructor<T> c = ((Class<T>)termClass).getDeclaredConstructor();
462 c.setAccessible(true);
463 T termInstance = c.newInstance();
464 termInstance.setTermType(termType);
465 return termInstance;
466 } catch (Exception e) {
467 throw new RuntimeException(e);
468 }
469 }
470
471 @Override
472 public void writeCsvLine(CSVWriter writer, T term) {
473 String [] line = new String[4];
474 line[0] = term.getUuid().toString();
475 line[1] = term.getUri().toString();
476 line[2] = term.getLabel();
477 line[3] = term.getDescription();
478 writer.writeNext(line);
479 }
480
481 @Transient
482 public T getByUuid(UUID uuid){
483 return this.vocabulary.findTermByUuid(uuid);
484 }
485
486 /**
487 * Throws {@link IllegalArgumentException} if the given
488 * term has not the same term type as this term or if term type is null.
489 * @param term
490 */
491 private void checkTermType(IHasTermType term) {
492 IHasTermType.checkTermTypes(term, this);
493 }
494
495
496 //*********************** CLONE ********************************************************/
497
498 /**
499 * Clones <i>this</i> DefinedTermBase. This is a shortcut that enables to create
500 * a new instance that differs only slightly from <i>this</i> defined term base by
501 * modifying only some of the attributes.
502 *
503 * @see eu.etaxonomy.cdm.model.term.TermBase#clone()
504 * @see java.lang.Object#clone()
505 */
506 @Override
507 public Object clone() {
508 DefinedTermBase result;
509 try {
510 result = (DefinedTermBase) super.clone();
511 }catch (CloneNotSupportedException e) {
512 logger.warn("Object does not implement cloneable");
513 e.printStackTrace();
514 return null;
515 }
516
517 result.generalizationOf = new HashSet<DefinedTermBase>();
518 for (DefinedTermBase generalizationOf : this.generalizationOf){
519 result.generalizationOf.add(generalizationOf.clone());
520 }
521
522 result.includes = new HashSet<DefinedTermBase>();
523
524 for (DefinedTermBase include: this.includes){
525 result.includes.add(include.clone());
526 }
527
528 result.media = new HashSet<Media>();
529
530 for (Media media: this.media){
531 result.addMedia(media);
532 }
533
534 return result;
535 }
536
537 // Currently the CDM Caching mechanism is only used for caching terms
538 private static ICdmUuidCacher cacher;
539
540
541 /**
542 * Gets the CDM cacher object
543 *
544 * @return the CDM cacher object
545 */
546 public static ICdmUuidCacher getCacher() {
547 return cacher;
548 }
549
550 /**
551 * Sets the CDM cacher object
552 *
553 * @param cacher the CDM cacher object
554 */
555 public static void setCacher(ICdmUuidCacher cacher) {
556 DefinedTermBase.cacher = cacher;
557 }
558
559 public static <T extends DefinedTermBase> T getTermByClassAndUUID(Class<T> clazz, UUID uuid) {
560 if(cacher != null) {
561 Object obj = getCacher().load(uuid);
562 if(obj != null && obj.getClass().equals(clazz)) {
563 return (T)obj;
564 }
565 }
566 return null;
567 }
568 }