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