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