merge hibernate4 migration branch into trunk
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / common / IdentifiableEntity.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
13 import java.beans.PropertyChangeEvent;
14 import java.beans.PropertyChangeListener;
15 import java.util.ArrayList;
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.Set;
19 import java.util.UUID;
20
21 import javax.persistence.Column;
22 import javax.persistence.Embedded;
23 import javax.persistence.FetchType;
24 import javax.persistence.MappedSuperclass;
25 import javax.persistence.OneToMany;
26 import javax.persistence.Transient;
27 import javax.validation.constraints.NotNull;
28 import javax.validation.constraints.Size;
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.XmlTransient;
34 import javax.xml.bind.annotation.XmlType;
35 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
36
37 import org.apache.log4j.Logger;
38 import org.hibernate.annotations.Cascade;
39 import org.hibernate.annotations.CascadeType;
40 import org.hibernate.annotations.IndexColumn;
41 import org.hibernate.envers.Audited;
42 import org.hibernate.search.annotations.Analyze;
43 import org.hibernate.search.annotations.Field;
44 import org.hibernate.search.annotations.FieldBridge;
45 import org.hibernate.search.annotations.Fields;
46 import org.hibernate.search.annotations.Store;
47 import org.hibernate.validator.constraints.NotEmpty;
48
49 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
50 import eu.etaxonomy.cdm.hibernate.search.StripHtmlBridge;
51 import eu.etaxonomy.cdm.jaxb.FormattedTextAdapter;
52 import eu.etaxonomy.cdm.jaxb.LSIDAdapter;
53 import eu.etaxonomy.cdm.model.media.Rights;
54 import eu.etaxonomy.cdm.model.name.NonViralName;
55 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
56 import eu.etaxonomy.cdm.model.reference.Reference;
57 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
58 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
59 import eu.etaxonomy.cdm.strategy.match.Match;
60 import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
61 import eu.etaxonomy.cdm.strategy.match.MatchMode;
62 import eu.etaxonomy.cdm.strategy.merge.Merge;
63 import eu.etaxonomy.cdm.strategy.merge.MergeMode;
64 import eu.etaxonomy.cdm.validation.Level2;
65
66 /**
67 * Superclass for the primary CDM classes that can be referenced from outside via LSIDs and contain a simple generated title string as a label for human reading.
68 * All subclasses inherit the ability to store additional properties that are stored as {@link Extension Extensions}, basically a string value with a type term.
69 * Any number of right statements can be attached as well as multiple {@link OriginalSourceBase} objects.
70 * Original sources carry a reference to the source, an ID within that source and the original title/label of this object as it was used in that source (originalNameString).
71 * A Taxon for example that was taken from 2 sources like FaunaEuropaea and IPNI would have two originalSource objects.
72 * The originalSource representing that taxon as it was found in IPNI would contain IPNI as the reference, the IPNI id of the taxon and the name of the taxon exactly as it was used in IPNI.
73 *
74 * @author m.doering
75 * @version 1.0
76 * @created 08-Nov-2007 13:06:27
77 */
78 @XmlAccessorType(XmlAccessType.FIELD)
79 @XmlType(name = "IdentifiableEntity", propOrder = {
80 "lsid",
81 "titleCache",
82 "protectedTitleCache",
83 "rights",
84 "extensions",
85 "credits",
86 "sources"
87 })
88 @Audited
89 @MappedSuperclass
90 public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrategy> extends AnnotatableEntity
91 implements IIdentifiableEntity /*, ISourceable<IdentifiableSource> */ {
92 private static final long serialVersionUID = -5610995424730659058L;
93 private static final Logger logger = Logger.getLogger(IdentifiableEntity.class);
94
95 @XmlTransient
96 public static final boolean PROTECTED = true;
97 @XmlTransient
98 public static final boolean NOT_PROTECTED = false;
99
100 @XmlElement(name = "LSID", type = String.class)
101 @XmlJavaTypeAdapter(LSIDAdapter.class)
102 @Embedded
103 private LSID lsid;
104
105 @XmlElement(name = "TitleCache", required = true)
106 @XmlJavaTypeAdapter(FormattedTextAdapter.class)
107 @Column(length=255, name="titleCache")
108 @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
109 @NotEmpty(groups = Level2.class) // implictly NotNull
110 @Size(max = 255)
111 @Fields({
112 @Field(store=Store.YES),
113 @Field(name = "titleCache__sort", analyze = Analyze.NO, store=Store.YES)
114 })
115 @FieldBridge(impl=StripHtmlBridge.class)
116 protected String titleCache;
117
118 //if true titleCache will not be automatically generated/updated
119 @XmlElement(name = "ProtectedTitleCache")
120 protected boolean protectedTitleCache;
121
122 @XmlElementWrapper(name = "Rights", nillable = true)
123 @XmlElement(name = "Rights")
124 @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
125 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
126 //TODO
127 @Merge(MergeMode.ADD_CLONE)
128 @NotNull
129 private Set<Rights> rights = new HashSet<Rights>();
130
131 @XmlElementWrapper(name = "Credits", nillable = true)
132 @XmlElement(name = "Credit")
133 @IndexColumn(name="sortIndex", base = 0)
134 @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
135 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
136 //TODO
137 @Merge(MergeMode.ADD_CLONE)
138 @NotNull
139 private List<Credit> credits = new ArrayList<Credit>();
140
141 @XmlElementWrapper(name = "Extensions", nillable = true)
142 @XmlElement(name = "Extension")
143 @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
144 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
145 @Merge(MergeMode.ADD_CLONE)
146 @NotNull
147 private Set<Extension> extensions = new HashSet<Extension>();
148
149 @XmlElementWrapper(name = "Sources", nillable = true)
150 @XmlElement(name = "IdentifiableSource")
151 @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
152 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
153 @Merge(MergeMode.ADD_CLONE)
154 @NotNull
155 private Set<IdentifiableSource> sources = new HashSet<IdentifiableSource>();
156
157 @XmlTransient
158 @Transient
159 protected S cacheStrategy;
160
161 protected IdentifiableEntity(){
162 initListener();
163 }
164
165 protected void initListener(){
166 PropertyChangeListener listener = new PropertyChangeListener() {
167 @Override
168 public void propertyChange(PropertyChangeEvent e) {
169 if (!e.getPropertyName().equals("titleCache") && !e.getPropertyName().equals("cacheStrategy") && ! isProtectedTitleCache()){
170 titleCache = null;
171 }
172 }
173 };
174 addPropertyChangeListener(listener);
175 }
176
177 /**
178 * By default, we expect most cdm objects to be abstract things
179 * i.e. unable to return a data representation.
180 *
181 * Specific subclasses (e.g. Sequence) can override if necessary.
182 */
183 @Override
184 public byte[] getData() {
185 return null;
186 }
187
188 //******************************** CACHE *****************************************************/
189
190
191 /* (non-Javadoc)
192 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getTitleCache()
193 */
194 // @Transient - must not be transient, since this property needs to to be included in all serializations produced by the remote layer
195 @Override
196 public String getTitleCache(){
197 if (protectedTitleCache){
198 return this.titleCache;
199 }
200 // is title dirty, i.e. equal NULL?
201 if (titleCache == null){
202 this.titleCache = generateTitle();
203 this.titleCache = getTruncatedCache(this.titleCache) ;
204 }
205 return titleCache;
206 }
207
208 /**
209 * The titleCache will be regenerated from scratch if not protected
210 * @return <code>true</code> if title cache was regenerated, <code>false</code> otherwise
211 */
212 protected boolean regenerateTitleCache() {
213 if (!protectedTitleCache) {
214 this.titleCache = null;
215 getTitleCache();
216 }
217 return protectedTitleCache;
218 }
219 /* (non-Javadoc)
220 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String)
221 */
222 @Override
223 public void setTitleCache(String titleCache){
224 this.titleCache = titleCache;
225 }
226
227 /* (non-Javadoc)
228 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String, boolean)
229 */
230 @Override
231 public void setTitleCache(String titleCache, boolean protectCache){
232 titleCache = getTruncatedCache(titleCache);
233 this.titleCache = titleCache;
234 this.protectedTitleCache = protectCache;
235 }
236
237
238 /**
239 * @param cache
240 * @return
241 */
242 @Transient
243 protected String getTruncatedCache(String cache) {
244 if (cache != null && cache.length() > 255){
245 logger.warn("Truncation of cache: " + this.toString() + "/" + cache);
246 cache = cache.substring(0, 252) + "...";
247 }
248 return cache;
249 }
250
251 //**************************************************************************************
252
253 /* (non-Javadoc)
254 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getLsid()
255 */
256 @Override
257 public LSID getLsid(){
258 return this.lsid;
259 }
260 /* (non-Javadoc)
261 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setLsid(java.lang.String)
262 */
263 @Override
264 public void setLsid(LSID lsid){
265 this.lsid = lsid;
266 }
267 /* (non-Javadoc)
268 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getRights()
269 */
270 @Override
271 public Set<Rights> getRights() {
272 if(rights == null) {
273 this.rights = new HashSet<Rights>();
274 }
275 return this.rights;
276 }
277
278 /* (non-Javadoc)
279 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addRights(eu.etaxonomy.cdm.model.media.Rights)
280 */
281 @Override
282 public void addRights(Rights right){
283 getRights().add(right);
284 }
285 /* (non-Javadoc)
286 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeRights(eu.etaxonomy.cdm.model.media.Rights)
287 */
288 @Override
289 public void removeRights(Rights right){
290 getRights().remove(right);
291 }
292
293
294 @Override
295 public List<Credit> getCredits() {
296 if(credits == null) {
297 this.credits = new ArrayList<Credit>();
298 }
299 return this.credits;
300 }
301
302 /* (non-Javadoc)
303 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getCredits(int)
304 */
305 @Override
306 public Credit getCredits(Integer index){
307 return getCredits().get(index);
308 }
309
310 /* (non-Javadoc)
311 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit)
312 */
313 @Override
314 public void addCredit(Credit credit){
315 getCredits().add(credit);
316 }
317
318
319 /* (non-Javadoc)
320 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit, int)
321 */
322 @Override
323 public void addCredit(Credit credit, int index){
324 getCredits().add(index, credit);
325 }
326
327 /* (non-Javadoc)
328 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(eu.etaxonomy.cdm.model.common.Credit)
329 */
330 @Override
331 public void removeCredit(Credit credit){
332 getCredits().remove(credit);
333 }
334
335 /* (non-Javadoc)
336 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(int)
337 */
338 @Override
339 public void removeCredit(int index){
340 getCredits().remove(index);
341 }
342
343
344 /* (non-Javadoc)
345 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getExtensions()
346 */
347 @Override
348 public Set<Extension> getExtensions(){
349 if(extensions == null) {
350 this.extensions = new HashSet<Extension>();
351 }
352 return this.extensions;
353 }
354 /**
355 * @param type
356 * @return a Set of extension value strings
357 */
358 public Set<String> getExtensions(ExtensionType type){
359 return getExtensions(type.getUuid());
360 }
361 /**
362 * @param extensionTypeUuid
363 * @return a Set of extension value strings
364 */
365 public Set<String> getExtensions(UUID extensionTypeUuid){
366 Set<String> result = new HashSet<String>();
367 for (Extension extension : getExtensions()){
368 if (extension.getType().getUuid().equals(extensionTypeUuid)){
369 result.add(extension.getValue());
370 }
371 }
372 return result;
373 }
374
375 public void addExtension(String value, ExtensionType extensionType){
376 Extension.NewInstance(this, value, extensionType);
377 }
378
379 /* (non-Javadoc)
380 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addExtension(eu.etaxonomy.cdm.model.common.Extension)
381 */
382 @Override
383 public void addExtension(Extension extension){
384 if (extension != null){
385 extension.setExtendedObj(this);
386 getExtensions().add(extension);
387 }
388 }
389 /* (non-Javadoc)
390 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeExtension(eu.etaxonomy.cdm.model.common.Extension)
391 */
392 @Override
393 public void removeExtension(Extension extension){
394 if (extension != null){
395 extension.setExtendedObj(null);
396 getExtensions().remove(extension);
397 }
398 }
399
400
401 /* (non-Javadoc)
402 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#isProtectedTitleCache()
403 */
404 @Override
405 public boolean isProtectedTitleCache() {
406 return protectedTitleCache;
407 }
408
409 /* (non-Javadoc)
410 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setProtectedTitleCache(boolean)
411 */
412 @Override
413 public void setProtectedTitleCache(boolean protectedTitleCache) {
414 this.protectedTitleCache = protectedTitleCache;
415 }
416
417 /* (non-Javadoc)
418 * @see eu.etaxonomy.cdm.model.common.ISourceable#getSources()
419 */
420 @Override
421 public Set<IdentifiableSource> getSources() {
422 if(sources == null) {
423 this.sources = new HashSet<IdentifiableSource>();
424 }
425 return this.sources;
426 }
427
428 /* (non-Javadoc)
429 * @see eu.etaxonomy.cdm.model.common.ISourceable#addSource(eu.etaxonomy.cdm.model.common.OriginalSourceBase)
430 */
431 @Override
432 public void addSource(IdentifiableSource source) {
433 if (source != null){
434 IdentifiableEntity oldSourcedObj = source.getSourcedObj();
435 if (oldSourcedObj != null && oldSourcedObj != this){
436 oldSourcedObj.getSources().remove(source);
437 }
438 getSources().add(source);
439 source.setSourcedObj(this);
440 }
441 }
442
443 /* (non-Javadoc)
444 * @see eu.etaxonomy.cdm.model.common.ISourceable#addSource(java.lang.String, java.lang.String, eu.etaxonomy.cdm.model.reference.Reference, java.lang.String)
445 */
446 @Override
447 public IdentifiableSource addSource(String id, String idNamespace, Reference citation, String microCitation) {
448 if (id == null && idNamespace == null && citation == null && microCitation == null){
449 return null;
450 }
451 IdentifiableSource source = IdentifiableSource.NewInstance(id, idNamespace, citation, microCitation);
452 addSource(source);
453 return source;
454 }
455
456 /* (non-Javadoc)
457 * @see eu.etaxonomy.cdm.model.common.ISourceable#removeSource(eu.etaxonomy.cdm.model.common.IOriginalSource)
458 */
459 @Override
460 public void removeSource(IdentifiableSource source) {
461 getSources().remove(source);
462 }
463
464 //******************************** TO STRING *****************************************************/
465
466 /* (non-Javadoc)
467 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
468 */
469 @Override
470 public String toString() {
471 String result;
472 if (titleCache == null){
473 result = super.toString();
474 }else{
475 result = this.titleCache;
476 }
477 return result;
478 }
479
480
481 public int compareTo(IdentifiableEntity identifiableEntity) {
482
483 int result = 0;
484
485 if (identifiableEntity == null) {
486 throw new NullPointerException("Cannot compare to null.");
487 }
488
489 // First, compare the name cache.
490 // TODO: Avoid using instanceof operator
491 // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
492
493 String specifiedNameCache = "";
494 String thisNameCache = "";
495 String specifiedTitleCache = "";
496 String thisTitleCache = "";
497 String specifiedReferenceTitleCache = "";
498 String thisReferenceTitleCache = "";
499 String thisGenusString = "";
500 String specifiedGenusString = "";
501 int thisrank_order = 0;
502
503 if(identifiableEntity instanceof NonViralName) {
504 specifiedNameCache = HibernateProxyHelper.deproxy(identifiableEntity, NonViralName.class).getNameCache();
505 specifiedTitleCache = identifiableEntity.getTitleCache();
506
507 } else if(identifiableEntity instanceof TaxonBase) {
508 TaxonBase taxonBase = HibernateProxyHelper.deproxy(identifiableEntity, TaxonBase.class);
509
510 TaxonNameBase<?,?> taxonNameBase = taxonBase.getName();
511
512
513 NonViralName nonViralName = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class);
514 specifiedNameCache = nonViralName.getNameCache();
515 specifiedTitleCache = taxonNameBase.getTitleCache();
516
517 specifiedReferenceTitleCache = ((TaxonBase)identifiableEntity).getSec().getTitleCache();
518 Reference reference = taxonBase.getSec();
519 if (reference != null) {
520 reference = HibernateProxyHelper.deproxy(reference, Reference.class);
521 specifiedReferenceTitleCache = reference.getTitleCache();
522 }
523 }
524
525 if(this instanceof NonViralName) {
526 thisNameCache = HibernateProxyHelper.deproxy(this, NonViralName.class).getNameCache();
527 thisTitleCache = getTitleCache();
528 } else if(this instanceof TaxonBase) {
529 TaxonNameBase<?,?> taxonNameBase= HibernateProxyHelper.deproxy(this, TaxonBase.class).getName();
530 NonViralName nonViralName = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class);
531 thisNameCache = nonViralName.getNameCache();
532 thisTitleCache = taxonNameBase.getTitleCache();
533 thisReferenceTitleCache = getTitleCache();
534 thisGenusString = nonViralName.getGenusOrUninomial();
535 }
536
537 // Compare name cache of taxon names
538
539 if (!specifiedNameCache.equals("") && !thisNameCache.equals("")) {
540 result = thisNameCache.compareTo(specifiedNameCache);
541 }
542
543 // Compare title cache of taxon names
544
545 if ((result == 0) && (!specifiedTitleCache.equals("") || !thisTitleCache.equals(""))) {
546 result = thisTitleCache.compareTo(specifiedTitleCache);
547 }
548
549 // Compare title cache of taxon references
550
551 if ((result == 0) && (!specifiedReferenceTitleCache.equals("") || !thisReferenceTitleCache.equals(""))) {
552 result = thisReferenceTitleCache.compareTo(specifiedReferenceTitleCache);
553 }
554
555 return result;
556 }
557
558 /**
559 * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
560 * several strings corresponding to <i>this</i> identifiable entity
561 * (in particular taxon name caches and author strings).
562 *
563 * @return the cache strategy used for <i>this</i> identifiable entity
564 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
565 */
566 public S getCacheStrategy() {
567 return this.cacheStrategy;
568 }
569 /**
570 * @see #getCacheStrategy()
571 */
572
573 public void setCacheStrategy(S cacheStrategy) {
574 this.cacheStrategy = cacheStrategy;
575 }
576
577 @Override
578 public String generateTitle() {
579 if (cacheStrategy == null){
580 //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
581 return this.getClass() + ": " + this.getUuid();
582 }else{
583 return cacheStrategy.getTitleCache(this);
584 }
585 }
586
587 //****************** CLONE ************************************************/
588
589 /* (non-Javadoc)
590 * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
591 */
592 @Override
593 public Object clone() throws CloneNotSupportedException{
594 IdentifiableEntity result = (IdentifiableEntity)super.clone();
595
596 //Extensions
597 result.extensions = new HashSet<Extension>();
598 for (Extension extension : getExtensions() ){
599 Extension newExtension = (Extension)extension.clone();
600 result.addExtension(newExtension);
601 }
602
603 //OriginalSources
604 result.sources = new HashSet<IdentifiableSource>();
605 for (IdentifiableSource source : getSources()){
606 IdentifiableSource newSource = (IdentifiableSource)source.clone();
607 result.addSource(newSource);
608 }
609
610 //Rights
611 result.rights = new HashSet<Rights>();
612 for(Rights rights : getRights()) {
613 result.addRights(rights);
614 }
615
616
617 //Credits
618 result.credits = new ArrayList<Credit>();
619 for(Credit credit : getCredits()) {
620 result.addCredit(credit);
621 }
622
623 //no changes to: lsid, titleCache, protectedTitleCache
624
625 //empty titleCache
626 if (! protectedTitleCache){
627 result.titleCache = null;
628 }
629
630 result.initListener();
631 return result;
632 }
633
634
635 }