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