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