some bugs in parser and cache strategies && return type for hasProblem() changed...
[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 OriginalSource} 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 ISourceable, IIdentifiableEntity /*, Comparable<IdentifiableEntity> */{
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<OriginalSource> sources = new HashSet<OriginalSource>();
139
140 @XmlTransient
141 @Transient
142 protected S cacheStrategy;
143
144 protected IdentifiableEntity(){
145 PropertyChangeListener listener = new PropertyChangeListener() {
146 public void propertyChange(PropertyChangeEvent e) {
147 if ( !e.getPropertyName().equals("titleCache") && ! isProtectedTitleCache()){
148 titleCache = null;
149 }
150 }
151 };
152 addPropertyChangeListener(listener);
153 }
154
155 /* (non-Javadoc)
156 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getLsid()
157 */
158 public LSID getLsid(){
159 return this.lsid;
160 }
161 /* (non-Javadoc)
162 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setLsid(java.lang.String)
163 */
164 public void setLsid(LSID lsid){
165 this.lsid = lsid;
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 /* (non-Javadoc)
179 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getTitleCache()
180 */
181 //@Transient
182 public String getTitleCache(){
183 if (protectedTitleCache){
184 return this.titleCache;
185 }
186 // is title dirty, i.e. equal NULL?
187 if (titleCache == null){
188 this.setTitleCache(generateTitle(),protectedTitleCache) ; //for truncating
189 }
190 return titleCache;
191 }
192 /* (non-Javadoc)
193 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String)
194 */
195 public void setTitleCache(String titleCache){
196 setTitleCache(titleCache, PROTECTED);
197 }
198
199 /* (non-Javadoc)
200 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String, boolean)
201 */
202 public void setTitleCache(String titleCache, boolean protectCache){
203 //TODO truncation of title cache
204 if (titleCache != null && titleCache.length() > 254){
205 logger.warn("Truncation of title cache: " + this.toString() + "/" + titleCache);
206 titleCache = titleCache.substring(0, 251) + "...";
207 }
208 this.titleCache = titleCache;
209 this.protectedTitleCache = protectCache;
210 }
211
212 /* (non-Javadoc)
213 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getRights()
214 */
215 public Set<Rights> getRights() {
216 return this.rights;
217 }
218
219 /* (non-Javadoc)
220 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addRights(eu.etaxonomy.cdm.model.media.Rights)
221 */
222 public void addRights(Rights right){
223 this.rights.add(right);
224 }
225 /* (non-Javadoc)
226 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeRights(eu.etaxonomy.cdm.model.media.Rights)
227 */
228 public void removeRights(Rights right){
229 this.rights.remove(right);
230 }
231
232
233 public List<Credit> getCredits() {
234 return this.credits;
235 }
236
237 /* (non-Javadoc)
238 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getCredits(int)
239 */
240 public Credit getCredits(int index){
241 return this.credits.get(index);
242 }
243
244 /* (non-Javadoc)
245 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit)
246 */
247 public void addCredit(Credit credit){
248 this.credits.add(credit);
249 }
250
251
252 /* (non-Javadoc)
253 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit, int)
254 */
255 public void addCredit(Credit credit, int index){
256 this.credits.add(index, credit);
257 }
258
259 /* (non-Javadoc)
260 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(eu.etaxonomy.cdm.model.common.Credit)
261 */
262 public void removeCredit(Credit credit){
263 this.credits.remove(credit);
264 }
265
266 /* (non-Javadoc)
267 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(int)
268 */
269 public void removeCredit(int index){
270 this.credits.remove(index);
271 }
272
273
274 /* (non-Javadoc)
275 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getExtensions()
276 */
277 public Set<Extension> getExtensions(){
278 return this.extensions;
279 }
280
281 public void addExtension(String value, ExtensionType extensionType){
282 Extension.NewInstance(this, value, extensionType);
283 }
284
285 /* (non-Javadoc)
286 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addExtension(eu.etaxonomy.cdm.model.common.Extension)
287 */
288 public void addExtension(Extension extension){
289 if (extension != null){
290 extension.setExtendedObj(this);
291 this.extensions.add(extension);
292 }
293 }
294 /* (non-Javadoc)
295 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeExtension(eu.etaxonomy.cdm.model.common.Extension)
296 */
297 public void removeExtension(Extension extension){
298 if (extension != null){
299 extension.setExtendedObj(null);
300 this.extensions.remove(extension);
301 }
302 }
303
304
305 /* (non-Javadoc)
306 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#isProtectedTitleCache()
307 */
308 public boolean isProtectedTitleCache() {
309 return protectedTitleCache;
310 }
311
312 /* (non-Javadoc)
313 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setProtectedTitleCache(boolean)
314 */
315 public void setProtectedTitleCache(boolean protectedTitleCache) {
316 this.protectedTitleCache = protectedTitleCache;
317 }
318
319 /* (non-Javadoc)
320 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getSources()
321 */
322 public Set<OriginalSource> getSources() {
323 return this.sources;
324 }
325 /* (non-Javadoc)
326 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addSource(eu.etaxonomy.cdm.model.common.OriginalSource)
327 */
328 public void addSource(OriginalSource source) {
329 if (source != null){
330 IdentifiableEntity oldSourcedObj = source.getSourcedObj();
331 if (oldSourcedObj != null && oldSourcedObj != this){
332 oldSourcedObj.getSources().remove(source);
333 }
334 this.sources.add(source);
335 source.setSourcedObj(this);
336 }
337 }
338 /* (non-Javadoc)
339 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeSource(eu.etaxonomy.cdm.model.common.OriginalSource)
340 */
341 public void removeSource(OriginalSource source) {
342 this.sources.remove(source);
343 }
344
345 /* (non-Javadoc)
346 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
347 */
348 @Override
349 public String toString() {
350 String result;
351 if (titleCache == null){
352 result = super.toString();
353 }else{
354 result = this.titleCache;
355 }
356 return result;
357 }
358
359 public int compareTo(IdentifiableEntity identifiableEntity) {
360
361 int result = 0;
362
363 if (identifiableEntity == null) {
364 throw new NullPointerException("Cannot compare to null.");
365 }
366
367 // First, compare the name cache.
368 // TODO: Avoid using instanceof operator
369 // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
370
371 String specifiedNameCache = "";
372 String thisNameCache = "";
373 String specifiedTitleCache = "";
374 String thisTitleCache = "";
375 String specifiedReferenceTitleCache = "";
376 String thisReferenceTitleCache = "";
377
378 if(identifiableEntity instanceof NonViralName) {
379 specifiedNameCache = HibernateProxyHelper.deproxy(identifiableEntity, NonViralName.class).getNameCache();
380 specifiedTitleCache = identifiableEntity.getTitleCache();
381
382 } else if(identifiableEntity instanceof TaxonBase) {
383 TaxonBase taxonBase = HibernateProxyHelper.deproxy(identifiableEntity, TaxonBase.class);
384
385 TaxonNameBase<?,?> taxonNameBase = taxonBase.getName();
386 specifiedNameCache = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class).getNameCache();
387 specifiedTitleCache = taxonNameBase.getTitleCache();
388
389 //specifiedReferenceTitleCache = ((TaxonBase)identifiableEntity).getSec().getTitleCache();
390 // ReferenceBase referenceBase = taxonBase.getSec();
391 // if (referenceBase != null) {
392 // FIXME: HibernateProxyHelper.deproxy(referenceBase, ReferenceBase.class) throws exception
393 // referenceBase = HibernateProxyHelper.deproxy(referenceBase, ReferenceBase.class);
394 // specifiedReferenceTitleCache = referenceBase.getTitleCache();
395 // }
396 }
397
398 if(this instanceof NonViralName) {
399 thisNameCache = HibernateProxyHelper.deproxy(this, NonViralName.class).getNameCache();
400 thisTitleCache = getTitleCache();
401 } else if(this instanceof TaxonBase) {
402 TaxonNameBase<?,?> taxonNameBase= HibernateProxyHelper.deproxy(this, TaxonBase.class).getName();
403 thisNameCache = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class).getNameCache();
404 thisTitleCache = taxonNameBase.getTitleCache();
405 thisReferenceTitleCache = getTitleCache();
406 }
407
408 // Compare name cache of taxon names
409
410 if (!specifiedNameCache.equals("") && !thisNameCache.equals("")) {
411 result = thisNameCache.compareTo(specifiedNameCache);
412 }
413
414 // Compare title cache of taxon names
415
416 if ((result == 0) && (!specifiedTitleCache.equals("") || !thisTitleCache.equals(""))) {
417 result = thisTitleCache.compareTo(specifiedTitleCache);
418 }
419
420 // Compare title cache of taxon references
421
422 if ((result == 0) && (!specifiedReferenceTitleCache.equals("") || !thisReferenceTitleCache.equals(""))) {
423 result = thisReferenceTitleCache.compareTo(specifiedReferenceTitleCache);
424 }
425
426 return result;
427 }
428
429 /**
430 * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
431 * several strings corresponding to <i>this</i> identifiable entity
432 * (in particular taxon name caches and author strings).
433 *
434 * @return the cache strategy used for <i>this</i> identifiable entity
435 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
436 */
437 public S getCacheStrategy() {
438 return this.cacheStrategy;
439 }
440 /**
441 * @see #getCacheStrategy()
442 */
443
444 public void setCacheStrategy(S cacheStrategy) {
445 this.cacheStrategy = cacheStrategy;
446 }
447
448 public String generateTitle() {
449 if (cacheStrategy == null){
450 //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
451 return this.getClass() + ": " + this.getUuid();
452 }else{
453 return cacheStrategy.getTitleCache(this);
454 }
455 }
456
457 //****************** CLONE ************************************************/
458
459 /* (non-Javadoc)
460 * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
461 */
462 @Override
463 public Object clone() throws CloneNotSupportedException{
464 IdentifiableEntity result = (IdentifiableEntity)super.clone();
465
466 //Extensions
467 result.extensions = new HashSet<Extension>();
468 for (Extension extension : this.extensions ){
469 Extension newExtension = (Extension)extension.clone();
470 result.addExtension(newExtension);
471 }
472
473 //OriginalSources
474 result.sources = new HashSet<OriginalSource>();
475 for (OriginalSource originalSource : this.sources){
476 OriginalSource newSource = (OriginalSource)originalSource.clone();
477 result.addSource(newSource);
478 }
479
480 //Rights
481 result.rights = new HashSet<Rights>();
482 for(Rights rights : this.rights) {
483 result.addRights(rights);
484 }
485
486
487 //Rights
488 result.credits = new ArrayList<Credit>();
489 for(Credit credit : this.credits) {
490 result.addCredit(credit);
491 }
492
493
494 //result.setLsid(lsid);
495 //result.setTitleCache(titleCache);
496 //result.setProtectedTitleCache(protectedTitleCache); //must be after setTitleCache
497
498 //no changes to: lsid, titleCache, protectedTitleCache
499
500 //empty titleCache
501 if (! protectedTitleCache){
502 result.titleCache = null;
503 }
504 return result;
505 }
506
507
508 }