Fill empty titleCache only during save and improve classification titleCache generati...
[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 import java.util.UUID;
20
21 import javax.persistence.Column;
22 import javax.persistence.Embedded;
23 import javax.persistence.FetchType;
24 import javax.persistence.MappedSuperclass;
25 import javax.persistence.OneToMany;
26 import javax.persistence.OrderColumn;
27 import javax.persistence.Transient;
28 import javax.validation.constraints.NotNull;
29 import javax.xml.bind.annotation.XmlAccessType;
30 import javax.xml.bind.annotation.XmlAccessorType;
31 import javax.xml.bind.annotation.XmlElement;
32 import javax.xml.bind.annotation.XmlElementWrapper;
33 import javax.xml.bind.annotation.XmlTransient;
34 import javax.xml.bind.annotation.XmlType;
35 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
36
37 import org.apache.commons.lang.StringUtils;
38 import org.apache.log4j.Logger;
39 import org.hibernate.annotations.Cascade;
40 import org.hibernate.annotations.CascadeType;
41 import org.hibernate.envers.Audited;
42 import org.hibernate.search.annotations.Analyze;
43 import org.hibernate.search.annotations.Field;
44 import org.hibernate.search.annotations.FieldBridge;
45 import org.hibernate.search.annotations.Fields;
46 import org.hibernate.search.annotations.Index;
47 import org.hibernate.search.annotations.SortableField;
48 import org.hibernate.search.annotations.Store;
49 import org.hibernate.validator.constraints.NotEmpty;
50
51 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
52 import eu.etaxonomy.cdm.hibernate.search.StripHtmlBridge;
53 import eu.etaxonomy.cdm.jaxb.FormattedTextAdapter;
54 import eu.etaxonomy.cdm.jaxb.LSIDAdapter;
55 import eu.etaxonomy.cdm.model.media.Rights;
56 import eu.etaxonomy.cdm.model.name.BotanicalName;
57 import eu.etaxonomy.cdm.model.name.NonViralName;
58 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
59 import eu.etaxonomy.cdm.model.reference.Reference;
60 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
61 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
62 import eu.etaxonomy.cdm.strategy.match.Match;
63 import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
64 import eu.etaxonomy.cdm.strategy.match.MatchMode;
65 import eu.etaxonomy.cdm.strategy.merge.Merge;
66 import eu.etaxonomy.cdm.strategy.merge.MergeMode;
67 import eu.etaxonomy.cdm.validation.Level2;
68
69 /**
70 * 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.
71 * All subclasses inherit the ability to store additional properties that are stored as {@link Extension Extensions}, basically a string value with a type term.
72 * Any number of right statements can be attached as well as multiple {@link OriginalSourceBase} objects.
73 * 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).
74 * A Taxon for example that was taken from 2 sources like FaunaEuropaea and IPNI would have two originalSource objects.
75 * 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.
76 *
77 * @author m.doering
78 * @created 08-Nov-2007 13:06:27
79 */
80 @XmlAccessorType(XmlAccessType.FIELD)
81 @XmlType(name = "IdentifiableEntity", propOrder = {
82 "lsid",
83 "titleCache",
84 "protectedTitleCache",
85 "credits",
86 "extensions",
87 "identifiers",
88 "rights",
89 "sources"
90 })
91 @Audited
92 @MappedSuperclass
93 public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrategy> extends AnnotatableEntity
94 implements IIdentifiableEntity /*, ISourceable<IdentifiableSource> */ {
95 private static final long serialVersionUID = 7912083412108359559L;
96
97 private static final Logger logger = Logger.getLogger(IdentifiableEntity.class);
98
99 @XmlTransient
100 public static final boolean PROTECTED = true;
101 @XmlTransient
102 public static final boolean NOT_PROTECTED = false;
103
104 @XmlElement(name = "LSID", type = String.class)
105 @XmlJavaTypeAdapter(LSIDAdapter.class)
106 @Embedded
107 private LSID lsid;
108
109 @XmlElement(name = "TitleCache", required = true)
110 @XmlJavaTypeAdapter(FormattedTextAdapter.class)
111 @Column(name="titleCache", length=800) //see #1592
112 @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
113 @NotEmpty(groups = Level2.class) // implictly NotNull
114 @Fields({
115 @Field(store=Store.YES),
116 // If the field is only needed for sorting and nothing else, you may configure it as
117 // un-indexed and un-stored, thus avoid unnecessary index growth.
118 @Field(name = "titleCache__sort", analyze = Analyze.NO, store=Store.NO, index = Index.NO)
119 })
120 @SortableField(forField = "titleCache__sort")
121 @FieldBridge(impl=StripHtmlBridge.class)
122 protected String titleCache;
123
124 //if true titleCache will not be automatically generated/updated
125 @XmlElement(name = "ProtectedTitleCache")
126 protected boolean protectedTitleCache;
127
128 @XmlElementWrapper(name = "Rights", nillable = true)
129 @XmlElement(name = "Rights")
130 @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
131 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
132 //TODO
133 @Merge(MergeMode.ADD_CLONE)
134 @NotNull
135 private Set<Rights> rights = new HashSet<Rights>();
136
137 @XmlElementWrapper(name = "Credits", nillable = true)
138 @XmlElement(name = "Credit")
139 @OrderColumn(name="sortIndex")
140 @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
141 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
142 //TODO
143 @Merge(MergeMode.ADD_CLONE)
144 @NotNull
145 private List<Credit> credits = new ArrayList<Credit>();
146
147 @XmlElementWrapper(name = "Extensions", nillable = true)
148 @XmlElement(name = "Extension")
149 @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
150 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
151 @Merge(MergeMode.ADD_CLONE)
152 @NotNull
153 private Set<Extension> extensions = new HashSet<Extension>();
154
155 @XmlElementWrapper(name = "Identifiers", nillable = true)
156 @XmlElement(name = "Identifier")
157 @OrderColumn(name="sortIndex")
158 @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
159 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
160 @Merge(MergeMode.ADD_CLONE)
161 @NotNull
162 private List<Identifier> identifiers = new ArrayList<Identifier>();
163
164 @XmlElementWrapper(name = "Sources", nillable = true)
165 @XmlElement(name = "IdentifiableSource")
166 @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
167 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
168 @Merge(MergeMode.ADD_CLONE)
169 @NotNull
170 private Set<IdentifiableSource> sources = new HashSet<IdentifiableSource>();
171
172 @XmlTransient
173 @Transient
174 protected S cacheStrategy;
175
176 protected IdentifiableEntity(){
177 initListener();
178 }
179
180 @Override
181 public void initListener(){
182 PropertyChangeListener listener = new PropertyChangeListener() {
183 @Override
184 public void propertyChange(PropertyChangeEvent ev) {
185 if (! "titleCache".equals(ev.getPropertyName()) && !"cacheStrategy".equals(ev.getPropertyName()) && ! isProtectedTitleCache()){
186 titleCache = null;
187 }
188 }
189 };
190 addPropertyChangeListener(listener);
191 }
192
193 /**
194 * By default, we expect most cdm objects to be abstract things
195 * i.e. unable to return a data representation.
196 *
197 * Specific subclasses (e.g. Sequence) can override if necessary.
198 */
199 @Override
200 public byte[] getData() {
201 return null;
202 }
203
204 //******************************** CACHE *****************************************************/
205
206 // @Transient - must not be transient, since this property needs to to be included in all serializations produced by the remote layer
207 @Override
208 public String getTitleCache(){
209 if (protectedTitleCache){
210 return this.titleCache;
211 }
212 // is title dirty, i.e. equal NULL?
213 if (titleCache == null){
214 this.titleCache = generateTitle();
215 this.titleCache = getTruncatedCache(this.titleCache) ;
216 }
217 //removed due to #5849
218 // if(StringUtils.isBlank(titleCache)){
219 // titleCache = this.toString();
220 // }
221 return titleCache;
222 }
223
224 @Deprecated
225 @Override
226 public void setTitleCache(String titleCache){
227 //TODO shouldn't we call setTitleCache(String, boolean),but is this conformant with Java Bean Specification?
228 this.titleCache = getTruncatedCache(titleCache);
229 }
230
231 @Override
232 public void setTitleCache(String titleCache, boolean protectCache){
233 titleCache = getTruncatedCache(titleCache);
234 this.titleCache = titleCache;
235 this.protectedTitleCache = protectCache;
236 }
237
238 /**
239 * @param cache
240 * @return
241 */
242 @Transient
243 protected String getTruncatedCache(String cache) {
244 int maxLength = 800;
245 if (cache != null && cache.length() > maxLength){
246 logger.warn("Truncation of cache: " + this.toString() + "/" + cache);
247 cache = cache.substring(0, maxLength - 4) + "..."; //TODO do we need -4 or is -3 enough
248 }
249 return cache;
250 }
251
252
253 @Override
254 public boolean isProtectedTitleCache() {
255 return protectedTitleCache;
256 }
257
258 @Override
259 public void setProtectedTitleCache(boolean protectedTitleCache) {
260 this.protectedTitleCache = protectedTitleCache;
261 }
262
263 /**
264 *
265 * @return true, if the current state of the titleCache (without generating it new)
266 * is <code>null</code> or the empty string. This is primarily meant for internal use.
267 */
268 public boolean hasEmptyTitleCache(){
269 return this.titleCache == null || "".equals(this.titleCache);
270 }
271
272 //**************************************************************************************
273
274 @Override
275 public LSID getLsid(){
276 return this.lsid;
277 }
278 @Override
279 public void setLsid(LSID lsid){
280 this.lsid = lsid;
281 }
282 @Override
283 public Set<Rights> getRights() {
284 if(rights == null) {
285 this.rights = new HashSet<Rights>();
286 }
287 return this.rights;
288 }
289
290 @Override
291 public void addRights(Rights right){
292 getRights().add(right);
293 }
294 @Override
295 public void removeRights(Rights right){
296 getRights().remove(right);
297 }
298
299
300 @Override
301 public List<Credit> getCredits() {
302 if(credits == null) {
303 this.credits = new ArrayList<Credit>();
304 }
305 return this.credits;
306 }
307
308 @Override
309 public Credit getCredits(Integer index){
310 return getCredits().get(index);
311 }
312
313 @Override
314 public void addCredit(Credit credit){
315 getCredits().add(credit);
316 }
317
318
319 @Override
320 public void addCredit(Credit credit, int index){
321 getCredits().add(index, credit);
322 }
323
324 @Override
325 public void removeCredit(Credit credit){
326 getCredits().remove(credit);
327 }
328
329 @Override
330 public void removeCredit(int index){
331 getCredits().remove(index);
332 }
333
334 @Override
335 public boolean replaceCredit(Credit newObject, Credit oldObject){
336 return replaceInList(this.credits, newObject, oldObject);
337 }
338
339
340 @Override
341 public List<Identifier> getIdentifiers(){
342 if(this.identifiers == null) {
343 this.identifiers = new ArrayList<Identifier>();
344 }
345 return this.identifiers;
346 }
347 /**
348 * @param type
349 * @return a set of identifier value strings
350 */
351 public Set<String> getIdentifiers(DefinedTerm type){
352 return getIdentifiers(type.getUuid());
353 }
354 /**
355 * @param identifierTypeUuid
356 * @return a set of identifier value strings
357 */
358 public Set<String> getIdentifiers(UUID identifierTypeUuid){
359 Set<String> result = new HashSet<String>();
360 for (Identifier<?> identifier : getIdentifiers()){
361 if (identifier.getType().getUuid().equals(identifierTypeUuid)){
362 result.add(identifier.getIdentifier());
363 }
364 }
365 return result;
366 }
367
368 @Override
369 public Identifier addIdentifier(String identifier, DefinedTerm identifierType){
370 Identifier<?> result = Identifier.NewInstance(identifier, identifierType);
371 addIdentifier(result);
372 return result;
373 }
374
375 @Override
376 public void addIdentifier(int index, Identifier identifier){
377 if (identifier != null){
378 //deduplication
379 int oldIndex = getIdentifiers().indexOf(identifier);
380 if(oldIndex > -1){
381 getIdentifiers().remove(identifier);
382 if (oldIndex < index){
383 index--;
384 }
385 }
386 getIdentifiers().add(index, identifier);
387 }
388 }
389
390 @Override
391 public void addIdentifier(Identifier identifier){
392 addIdentifier(getIdentifiers().size(), identifier);
393 }
394
395 @Override
396 public void removeIdentifier(Identifier identifier){
397 if (identifier != null){
398 getIdentifiers().remove(identifier);
399 }
400 }
401 @Override
402 public void removeIdentifier(int index){
403 getIdentifiers().remove(index);
404 }
405
406 @Override
407 public boolean replaceIdentifier(Identifier newObject, Identifier oldObject){
408 return replaceInList(this.identifiers, newObject, oldObject);
409 }
410
411
412 @Override
413 public Set<Extension> getExtensions(){
414 if(extensions == null) {
415 this.extensions = new HashSet<Extension>();
416 }
417 return this.extensions;
418 }
419 /**
420 * @param type
421 * @return a Set of extension value strings
422 */
423 public Set<String> getExtensions(ExtensionType type){
424 return getExtensions(type.getUuid());
425 }
426 /**
427 * @param extensionTypeUuid
428 * @return a Set of extension value strings
429 */
430 public Set<String> getExtensions(UUID extensionTypeUuid){
431 Set<String> result = new HashSet<String>();
432 for (Extension extension : getExtensions()){
433 if (extension.getType().getUuid().equals(extensionTypeUuid)){
434 result.add(extension.getValue());
435 }
436 }
437 return result;
438 }
439
440 public void addExtension(String value, ExtensionType extensionType){
441 Extension.NewInstance(this, value, extensionType);
442 }
443
444 @Override
445 public void addExtension(Extension extension){
446 if (extension != null){
447 getExtensions().add(extension);
448 }
449 }
450 @Override
451 public void removeExtension(Extension extension){
452 if (extension != null){
453 getExtensions().remove(extension);
454 }
455 }
456
457
458 @Override
459 public Set<IdentifiableSource> getSources() {
460 if(sources == null) {
461 this.sources = new HashSet<IdentifiableSource>();
462 }
463 return this.sources;
464 }
465
466 @Override
467 public void addSource(IdentifiableSource source) {
468 if (source != null){
469 getSources().add(source);
470 }
471 }
472
473 @Override
474 public void addSources(Set<IdentifiableSource> sources) {
475 if (sources != null){
476 for (IdentifiableSource source: sources){
477 getSources().add(source);
478 }
479 }
480 }
481
482 @Override
483 public void removeSources() {
484 this.sources.clear();
485 }
486
487 @Override
488 public IdentifiableSource addSource(OriginalSourceType type, String id, String idNamespace, Reference citation, String microCitation) {
489 if (id == null && idNamespace == null && citation == null && microCitation == null){
490 return null;
491 }
492 IdentifiableSource source = IdentifiableSource.NewInstance(type, id, idNamespace, citation, microCitation);
493 addSource(source);
494 return source;
495 }
496
497
498 @Override
499 public IdentifiableSource addImportSource(String id, String idNamespace, Reference citation, String microCitation) {
500 if (id == null && idNamespace == null && citation == null && microCitation == null){
501 return null;
502 }
503 IdentifiableSource source = IdentifiableSource.NewInstance(OriginalSourceType.Import, id, idNamespace, citation, microCitation);
504 addSource(source);
505 return source;
506 }
507
508
509 @Override
510 public void removeSource(IdentifiableSource source) {
511 getSources().remove(source);
512 }
513
514 //******************************** TO STRING *****************************************************/
515
516 @Override
517 public String toString() {
518 String result;
519 if (StringUtils.isBlank(titleCache)){
520 result = super.toString();
521 }else{
522 result = this.titleCache;
523 }
524 return result;
525 }
526
527
528 public int compareTo(IdentifiableEntity identifiableEntity) {
529
530 int result = 0;
531
532 if (identifiableEntity == null) {
533 throw new NullPointerException("Cannot compare to null.");
534 }
535
536 // First, compare the name cache.
537 // TODO: Avoid using instanceof operator
538 // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
539
540 String specifiedNameCache = "";
541 String thisNameCache = "";
542 String specifiedTitleCache = "";
543 String thisTitleCache = "";
544 String specifiedReferenceTitleCache = "";
545 String thisReferenceTitleCache = "";
546 String thisGenusString = "";
547 String specifiedGenusString = "";
548 int thisrank_order = 0;
549 final String HYBRID_SIGN = "\u00D7";
550 final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
551 //TODO we can remove all the deproxies here except for the first one
552 identifiableEntity = HibernateProxyHelper.deproxy(identifiableEntity, IdentifiableEntity.class);
553 if(identifiableEntity instanceof NonViralName) {
554 specifiedNameCache = HibernateProxyHelper.deproxy(identifiableEntity, NonViralName.class).getNameCache();
555 specifiedTitleCache = identifiableEntity.getTitleCache();
556 if (identifiableEntity instanceof BotanicalName){
557 if (((BotanicalName)identifiableEntity).isAutonym()){
558 boolean isProtected = false;
559 String oldNameCache = ((BotanicalName) identifiableEntity).getNameCache();
560 if ( ((BotanicalName)identifiableEntity).isProtectedNameCache()){
561 isProtected = true;
562 }
563 ((BotanicalName)identifiableEntity).setProtectedNameCache(false);
564 ((BotanicalName)identifiableEntity).setNameCache(null, false);
565 specifiedNameCache = ((BotanicalName) identifiableEntity).getNameCache();
566 ((BotanicalName)identifiableEntity).setNameCache(oldNameCache, isProtected);
567
568 }
569 }
570
571 } else if(identifiableEntity instanceof TaxonBase) {
572 TaxonBase taxonBase = HibernateProxyHelper.deproxy(identifiableEntity, TaxonBase.class);
573
574 TaxonNameBase<?,?> taxonNameBase = taxonBase.getName();
575
576
577 NonViralName nonViralName = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class);
578 specifiedNameCache = nonViralName.getNameCache();
579 specifiedTitleCache = taxonNameBase.getTitleCache();
580
581 specifiedReferenceTitleCache = ((TaxonBase)identifiableEntity).getSec().getTitleCache();
582 Reference reference = taxonBase.getSec();
583 if (reference != null) {
584 reference = HibernateProxyHelper.deproxy(reference, Reference.class);
585 specifiedReferenceTitleCache = reference.getTitleCache();
586 }
587 }
588
589 if(this.isInstanceOf(NonViralName.class)) {
590 thisNameCache = HibernateProxyHelper.deproxy(this, NonViralName.class).getNameCache();
591 thisTitleCache = getTitleCache();
592
593 if (this instanceof BotanicalName){
594 if (((BotanicalName)this).isAutonym()){
595 boolean isProtected = false;
596 String oldNameCache = ((BotanicalName) this).getNameCache();
597 if ( ((BotanicalName)this).isProtectedNameCache()){
598 isProtected = true;
599 }
600 ((BotanicalName)this).setProtectedNameCache(false);
601 ((BotanicalName)this).setNameCache(null, false);
602 thisNameCache = ((BotanicalName) this).getNameCache();
603 ((BotanicalName)this).setNameCache(oldNameCache, isProtected);
604 }
605 }
606 } else if(this.isInstanceOf(TaxonBase.class)) {
607 TaxonNameBase<?,?> taxonNameBase= HibernateProxyHelper.deproxy(this, TaxonBase.class).getName();
608 NonViralName nonViralName = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class);
609 thisNameCache = nonViralName.getNameCache();
610 thisTitleCache = taxonNameBase.getTitleCache();
611 thisReferenceTitleCache = ((TaxonBase)this).getSec().getTitleCache();
612 thisGenusString = nonViralName.getGenusOrUninomial();
613 }
614
615 // Compare name cache of taxon names
616
617
618
619 if (!specifiedNameCache.equals("") && !thisNameCache.equals("")) {
620
621 thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
622 thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
623
624
625 specifiedNameCache = specifiedNameCache.replaceAll(HYBRID_SIGN, "");
626 specifiedNameCache = specifiedNameCache.replaceAll(QUOT_SIGN, "");
627
628
629 result = thisNameCache.compareTo(specifiedNameCache);
630 }
631
632 // Compare title cache of taxon names
633
634 if ((result == 0) && (!specifiedTitleCache.equals("") || !thisTitleCache.equals(""))) {
635 thisTitleCache = thisTitleCache.replaceAll(HYBRID_SIGN, "");
636 thisTitleCache = thisTitleCache.replaceAll(QUOT_SIGN, "");
637
638 specifiedTitleCache = specifiedTitleCache.replaceAll(HYBRID_SIGN, "");
639 specifiedTitleCache = specifiedTitleCache.replaceAll(QUOT_SIGN, "");
640 result = thisTitleCache.compareTo(specifiedTitleCache);
641 }
642
643 // Compare title cache of taxon references
644
645 if ((result == 0) && (!specifiedReferenceTitleCache.equals("") || !thisReferenceTitleCache.equals(""))) {
646 result = thisReferenceTitleCache.compareTo(specifiedReferenceTitleCache);
647 }
648
649 return result;
650 }
651
652 /**
653 * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
654 * several strings corresponding to <i>this</i> identifiable entity
655 * (in particular taxon name caches and author strings).
656 *
657 * @return the cache strategy used for <i>this</i> identifiable entity
658 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
659 */
660 public S getCacheStrategy() {
661 return this.cacheStrategy;
662 }
663 /**
664 * @see #getCacheStrategy()
665 */
666
667 public void setCacheStrategy(S cacheStrategy) {
668 this.cacheStrategy = cacheStrategy;
669 }
670
671 @Override
672 public String generateTitle() {
673 if (getCacheStrategy() == null){
674 //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
675 return this.getClass() + ": " + this.getUuid();
676 }else{
677 return getCacheStrategy().getTitleCache(this);
678 }
679 }
680
681 //****************** CLONE ************************************************/
682
683 @Override
684 public Object clone() throws CloneNotSupportedException{
685 IdentifiableEntity<?> result = (IdentifiableEntity<?>)super.clone();
686
687 //Extensions
688 result.extensions = new HashSet<Extension>();
689 for (Extension extension : getExtensions() ){
690 Extension newExtension = (Extension)extension.clone();
691 result.addExtension(newExtension);
692 }
693
694 //Identifier
695 result.identifiers = new ArrayList<Identifier>();
696 for (Identifier<?> identifier : getIdentifiers() ){
697 Identifier<?> newIdentifier = (Identifier<?>)identifier.clone();
698 result.addIdentifier(newIdentifier);
699 }
700
701 //OriginalSources
702 result.sources = new HashSet<IdentifiableSource>();
703 for (IdentifiableSource source : getSources()){
704 IdentifiableSource newSource = (IdentifiableSource)source.clone();
705 result.addSource(newSource);
706 }
707
708 //Rights
709 result.rights = new HashSet<Rights>();
710 for(Rights rights : getRights()) {
711 Rights newRights = (Rights)rights.clone();
712 result.addRights(newRights);
713 }
714
715
716 //Credits
717 result.credits = new ArrayList<Credit>();
718 for(Credit credit : getCredits()) {
719 Credit newCredit = (Credit)credit.clone();
720 result.addCredit(newCredit);
721 }
722
723 //no changes to: lsid, titleCache, protectedTitleCache
724
725 //empty titleCache
726 if (! protectedTitleCache){
727 result.titleCache = null;
728 }
729
730 result.initListener();
731 return result;
732 }
733
734
735 }