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