change parent pom version in 3.3
[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 = 800) //see #1592
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() > 1023){
245 logger.warn("Truncation of cache: " + this.toString() + "/" + cache);
246 cache = cache.substring(0, 1020) + "...";
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(OriginalSourceType type, 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(type, id, idNamespace, citation, microCitation);
452 addSource(source);
453 return source;
454 }
455
456
457 /* (non-Javadoc)
458 * @see eu.etaxonomy.cdm.model.common.ISourceable#addImportSource(java.lang.String, java.lang.String, eu.etaxonomy.cdm.model.reference.Reference, java.lang.String)
459 */
460 @Override
461 public IdentifiableSource addImportSource(String id, String idNamespace, Reference<?> citation, String microCitation) {
462 if (id == null && idNamespace == null && citation == null && microCitation == null){
463 return null;
464 }
465 IdentifiableSource source = IdentifiableSource.NewInstance(OriginalSourceType.Import, id, idNamespace, citation, microCitation);
466 addSource(source);
467 return source;
468 }
469
470 /* (non-Javadoc)
471 * @see eu.etaxonomy.cdm.model.common.ISourceable#removeSource(eu.etaxonomy.cdm.model.common.IOriginalSource)
472 */
473 @Override
474 public void removeSource(IdentifiableSource source) {
475 getSources().remove(source);
476 }
477
478 //******************************** TO STRING *****************************************************/
479
480 /* (non-Javadoc)
481 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
482 */
483 @Override
484 public String toString() {
485 String result;
486 if (titleCache == null){
487 result = super.toString();
488 }else{
489 result = this.titleCache;
490 }
491 return result;
492 }
493
494
495 public int compareTo(IdentifiableEntity identifiableEntity) {
496
497 int result = 0;
498
499 if (identifiableEntity == null) {
500 throw new NullPointerException("Cannot compare to null.");
501 }
502
503 // First, compare the name cache.
504 // TODO: Avoid using instanceof operator
505 // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
506
507 String specifiedNameCache = "";
508 String thisNameCache = "";
509 String specifiedTitleCache = "";
510 String thisTitleCache = "";
511 String specifiedReferenceTitleCache = "";
512 String thisReferenceTitleCache = "";
513 String thisGenusString = "";
514 String specifiedGenusString = "";
515 int thisrank_order = 0;
516
517 if(identifiableEntity instanceof NonViralName) {
518 specifiedNameCache = HibernateProxyHelper.deproxy(identifiableEntity, NonViralName.class).getNameCache();
519 specifiedTitleCache = identifiableEntity.getTitleCache();
520
521 } else if(identifiableEntity instanceof TaxonBase) {
522 TaxonBase taxonBase = HibernateProxyHelper.deproxy(identifiableEntity, TaxonBase.class);
523
524 TaxonNameBase<?,?> taxonNameBase = taxonBase.getName();
525
526
527 NonViralName nonViralName = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class);
528 specifiedNameCache = nonViralName.getNameCache();
529 specifiedTitleCache = taxonNameBase.getTitleCache();
530
531 specifiedReferenceTitleCache = ((TaxonBase)identifiableEntity).getSec().getTitleCache();
532 Reference reference = taxonBase.getSec();
533 if (reference != null) {
534 reference = HibernateProxyHelper.deproxy(reference, Reference.class);
535 specifiedReferenceTitleCache = reference.getTitleCache();
536 }
537 }
538
539 if(this instanceof NonViralName) {
540 thisNameCache = HibernateProxyHelper.deproxy(this, NonViralName.class).getNameCache();
541 thisTitleCache = getTitleCache();
542 } else if(this instanceof TaxonBase) {
543 TaxonNameBase<?,?> taxonNameBase= HibernateProxyHelper.deproxy(this, TaxonBase.class).getName();
544 NonViralName nonViralName = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class);
545 thisNameCache = nonViralName.getNameCache();
546 thisTitleCache = taxonNameBase.getTitleCache();
547 thisReferenceTitleCache = getTitleCache();
548 thisGenusString = nonViralName.getGenusOrUninomial();
549 }
550
551 // Compare name cache of taxon names
552
553 if (!specifiedNameCache.equals("") && !thisNameCache.equals("")) {
554 result = thisNameCache.compareTo(specifiedNameCache);
555 }
556
557 // Compare title cache of taxon names
558
559 if ((result == 0) && (!specifiedTitleCache.equals("") || !thisTitleCache.equals(""))) {
560 result = thisTitleCache.compareTo(specifiedTitleCache);
561 }
562
563 // Compare title cache of taxon references
564
565 if ((result == 0) && (!specifiedReferenceTitleCache.equals("") || !thisReferenceTitleCache.equals(""))) {
566 result = thisReferenceTitleCache.compareTo(specifiedReferenceTitleCache);
567 }
568
569 return result;
570 }
571
572 /**
573 * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
574 * several strings corresponding to <i>this</i> identifiable entity
575 * (in particular taxon name caches and author strings).
576 *
577 * @return the cache strategy used for <i>this</i> identifiable entity
578 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
579 */
580 public S getCacheStrategy() {
581 return this.cacheStrategy;
582 }
583 /**
584 * @see #getCacheStrategy()
585 */
586
587 public void setCacheStrategy(S cacheStrategy) {
588 this.cacheStrategy = cacheStrategy;
589 }
590
591 @Override
592 public String generateTitle() {
593 if (cacheStrategy == null){
594 //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
595 return this.getClass() + ": " + this.getUuid();
596 }else{
597 return cacheStrategy.getTitleCache(this);
598 }
599 }
600
601 //****************** CLONE ************************************************/
602
603 /* (non-Javadoc)
604 * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
605 */
606 @Override
607 public Object clone() throws CloneNotSupportedException{
608 IdentifiableEntity result = (IdentifiableEntity)super.clone();
609
610 //Extensions
611 result.extensions = new HashSet<Extension>();
612 for (Extension extension : getExtensions() ){
613 Extension newExtension = (Extension)extension.clone();
614 result.addExtension(newExtension);
615 }
616
617 //OriginalSources
618 result.sources = new HashSet<IdentifiableSource>();
619 for (IdentifiableSource source : getSources()){
620 IdentifiableSource newSource = (IdentifiableSource)source.clone();
621 result.addSource(newSource);
622 }
623
624 //Rights
625 result.rights = new HashSet<Rights>();
626 for(Rights rights : getRights()) {
627 result.addRights(rights);
628 }
629
630
631 //Credits
632 result.credits = new ArrayList<Credit>();
633 for(Credit credit : getCredits()) {
634 result.addCredit(credit);
635 }
636
637 //no changes to: lsid, titleCache, protectedTitleCache
638
639 //empty titleCache
640 if (! protectedTitleCache){
641 result.titleCache = null;
642 }
643
644 result.initListener();
645 return result;
646 }
647
648
649 }