ref #9772: deproxy the term in getTermByClassAndUUID
[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.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 = Logger.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) {
210 super(type, description, label, labelAbbrev);
211 }
212
213 //********************** GETTER /SETTER *************************************
214
215 @Override
216 public String getIdInVocabulary() {
217 return idInVocabulary;
218 }
219
220 @Override
221 public void setIdInVocabulary(String idInVocabulary) {
222 this.idInVocabulary = CdmUtils.isBlank(idInVocabulary)? null : idInVocabulary;
223 }
224
225 @Override
226 public T getKindOf(){
227 if (this instanceof HibernateProxy) {
228 HibernateProxy proxy = (HibernateProxy) this;
229 LazyInitializer li = proxy.getHibernateLazyInitializer();
230 return (T)((T)li.getImplementation()).getKindOf();
231 } else {
232 return (T)DefinedTermBase.deproxy(this.kindOf, this.getClass());
233 }
234 }
235
236 public void setKindOf(T kindOf){
237 this.kindOf = kindOf;
238 }
239
240 @Override
241 public Set<T> getGeneralizationOf(){
242 return this.generalizationOf;
243 }
244 protected void setGeneralizationOf(Set<T> value) {
245 this.generalizationOf = value;
246 }
247 public void addGeneralizationOf(T generalization) {
248 checkTermType(generalization);
249 generalization.setKindOf(this);
250 this.generalizationOf.add(generalization);
251 }
252 public void removeGeneralization(T generalization) {
253 if(generalizationOf.contains(generalization)){
254 generalization.setKindOf(null);
255 this.generalizationOf.remove(generalization);
256 }
257 }
258
259 @Override
260 public T getPartOf(){
261 if (this instanceof HibernateProxy) {
262 HibernateProxy proxy = (HibernateProxy) this;
263 LazyInitializer li = proxy.getHibernateLazyInitializer();
264 return (T)((T)li.getImplementation()).getPartOf();
265 } else {
266 return (T)DefinedTermBase.deproxy(this.partOf, this.getClass());
267 }
268 }
269
270 /**
271 * @see #getPartOf()
272 */
273 public void setPartOf(T partOf){
274 this.partOf = partOf;
275 }
276
277
278 //TODO Comparable implemented only for fixing failing JAXB imports, may be removed when this is fixed
279 @Override
280 @Deprecated //for inner use only
281 public int compareTo(T other) {
282 return ((Integer)this.getId()).compareTo(other.getId());
283 }
284
285 @Override
286 public Set<T> getIncludes(){
287 return this.includes;
288 }
289
290 /**
291 * @see #getIncludes()
292 */
293 protected void setIncludes(Set<T> includes) {
294 this.includes = includes;
295 }
296
297 /**
298 * @see #getIncludes()
299 */
300 public void addIncludes(T includes) {
301 checkTermType(includes);
302 includes.setPartOf(this);
303 this.includes.add(includes);
304 }
305
306 /**
307 * @see #getIncludes()
308 */
309 public void removeIncludes(T includes) {
310 if(this.includes.contains(includes)) {
311 includes.setPartOf(null);
312 this.includes.remove(includes);
313 }
314 }
315
316 @Override
317 public Set<Media> getMedia(){
318 return this.media;
319 }
320
321 public void addMedia(Media media) {
322 this.media.add(media);
323 }
324 public void removeMedia(Media media) {
325 this.media.remove(media);
326 }
327
328 public TermVocabulary<T> getVocabulary() {
329 return this.vocabulary;
330 }
331
332 //for bedirectional use only, use vocabulary.addTerm instead
333 protected void setVocabulary(TermVocabulary<T> newVocabulary) {
334 this.vocabulary = newVocabulary;
335 }
336
337 public String getSymbol() {
338 return symbol;
339 }
340 public void setSymbol(String symbol) {
341 this.symbol = symbol;
342 }
343
344 public String getSymbol2() {
345 return symbol2;
346 }
347 public void setSymbol2(String symbol2) {
348 this.symbol2 = symbol2;
349 }
350
351 //******************************* METHODS ******************************************************/
352
353 @Override
354 public boolean isKindOf(T ancestor) {
355 if (kindOf == null || ancestor == null){
356 return false;
357 }else if (kindOf.equals(ancestor)){
358 return true;
359 }else{
360 return kindOf.isKindOf(ancestor);
361 }
362 }
363
364 @Override
365 public Set<T> getGeneralizationOf(boolean recursive) {
366 Set<T> result = new HashSet<T>();
367 result.addAll(this.generalizationOf);
368 if (recursive){
369 for (T child : this.generalizationOf){
370 result.addAll(child.getGeneralizationOf());
371 }
372 }
373 return result;
374 }
375
376 public abstract void resetTerms();
377
378 protected abstract void setDefaultTerms(TermVocabulary<T> termVocabulary);
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 = HibernateProxyHelper.deproxy(getCacher().load(uuid));
532
533 if(obj != null && obj.getClass().equals(clazz)) {
534 return (T)obj;
535 }
536 }
537 return null;
538 }
539 }