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