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