Project

General

Profile

Download (25 KB) Statistics
| Branch: | Tag: | Revision:
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
package eu.etaxonomy.cdm.model.common;
10

    
11
import java.beans.PropertyChangeEvent;
12
import java.beans.PropertyChangeListener;
13
import java.util.ArrayList;
14
import java.util.Collection;
15
import java.util.HashSet;
16
import java.util.List;
17
import java.util.Set;
18
import java.util.UUID;
19

    
20
import javax.persistence.Column;
21
import javax.persistence.Embedded;
22
import javax.persistence.FetchType;
23
import javax.persistence.ManyToMany;
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.NotEmpty;
29
import javax.validation.constraints.NotNull;
30
import javax.xml.bind.annotation.XmlAccessType;
31
import javax.xml.bind.annotation.XmlAccessorType;
32
import javax.xml.bind.annotation.XmlElement;
33
import javax.xml.bind.annotation.XmlElementWrapper;
34
import javax.xml.bind.annotation.XmlTransient;
35
import javax.xml.bind.annotation.XmlType;
36
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
37

    
38
import org.apache.logging.log4j.LogManager;import org.apache.logging.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

    
50
import eu.etaxonomy.cdm.common.CdmUtils;
51
import eu.etaxonomy.cdm.common.URI;
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.ExternalLink;
56
import eu.etaxonomy.cdm.model.media.Rights;
57
import eu.etaxonomy.cdm.model.reference.ICdmTarget;
58
import eu.etaxonomy.cdm.model.reference.OriginalSourceBase;
59
import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
60
import eu.etaxonomy.cdm.model.reference.Reference;
61
import eu.etaxonomy.cdm.model.term.DefinedTerm;
62
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
63
import eu.etaxonomy.cdm.strategy.match.Match;
64
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
65
import eu.etaxonomy.cdm.strategy.match.MatchMode;
66
import eu.etaxonomy.cdm.strategy.merge.Merge;
67
import eu.etaxonomy.cdm.strategy.merge.MergeMode;
68
import eu.etaxonomy.cdm.validation.Level2;
69

    
70
/**
71
 * 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.
72
 * All subclasses inherit the ability to store additional properties that are stored as {@link Extension Extensions}, basically a string value with a type term.
73
 * Any number of right statements can be attached as well as multiple {@link OriginalSourceBase} objects.
74
 * 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).
75
 * A Taxon for example that was taken from 2 sources like FaunaEuropaea and IPNI would have two originalSource objects.
76
 * 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.
77
 *
78
 * @author m.doering
79
 * @since 08-Nov-2007 13:06:27
80
 */
81
@XmlAccessorType(XmlAccessType.FIELD)
82
@XmlType(name = "IdentifiableEntity", propOrder = {
83
    "lsid",
84
    "titleCache",
85
    "protectedTitleCache",
86
    "credits",
87
    "extensions",
88
    "identifiers",
89
    "links",
90
    "rights"
91
})
92
@Audited
93
@MappedSuperclass
94
public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrategy>
95
        extends SourcedEntityBase<IdentifiableSource>
96
        implements IIdentifiableEntity /*, ISourceable<IdentifiableSource> */ {
97

    
98
    private static final long serialVersionUID = 7912083412108359559L;
99
    private static final Logger logger = LogManager.getLogger(IdentifiableEntity.class);
100

    
101
    @XmlTransient
102
    public static final boolean PROTECTED = true;
103
    @XmlTransient
104
    public static final boolean NOT_PROTECTED = false;
105

    
106
    @XmlElement(name = "LSID", type = String.class)
107
    @XmlJavaTypeAdapter(LSIDAdapter.class)
108
    @Embedded
109
    private LSID lsid;
110

    
111
    @XmlElement(name = "TitleCache", required = true)
112
    @XmlJavaTypeAdapter(FormattedTextAdapter.class)
113
    @Column(name="titleCache", length=800) //see #1592
114
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
115
    @NotEmpty(groups = Level2.class) // implicitly NotNull
116
    @Fields({
117
        @Field(store=Store.YES),
118
        //  If the field is only needed for sorting and nothing else, you may configure it as
119
        //  un-indexed and un-stored, thus avoid unnecessary index growth.
120
        @Field(name = "titleCache__sort", analyze = Analyze.NO, store=Store.NO, index = Index.NO)
121
    })
122
    @SortableField(forField = "titleCache__sort")
123
    @FieldBridge(impl=StripHtmlBridge.class)
124
    protected String titleCache;
125

    
126
    //if true titleCache will not be automatically generated/updated
127
    @XmlElement(name = "ProtectedTitleCache")
128
    protected boolean protectedTitleCache;
129

    
130
    @XmlElementWrapper(name = "Rights", nillable = true)
131
    @XmlElement(name = "Rights")
132
    @ManyToMany(fetch = FetchType.LAZY)  //#5762 M:N now
133
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
134
    //TODO
135
    @Merge(MergeMode.ADD_CLONE)
136
    @NotNull
137
    private Set<Rights> rights = new HashSet<>();
138

    
139
    @XmlElementWrapper(name = "Credits", nillable = true)
140
    @XmlElement(name = "Credit")
141
    @OrderColumn(name="sortIndex")
142
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
143
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
144
    //TODO
145
    @Merge(MergeMode.ADD_CLONE)
146
    @NotNull
147
    private List<Credit> credits = new ArrayList<>();
148

    
149
    @XmlElementWrapper(name = "Extensions", nillable = true)
150
    @XmlElement(name = "Extension")
151
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
152
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
153
    @Merge(MergeMode.ADD_CLONE)
154
    @NotNull
155
    private Set<Extension> extensions = new HashSet<>();
156

    
157
    @XmlElementWrapper(name = "Identifiers", nillable = true)
158
    @XmlElement(name = "Identifier")
159
    @OrderColumn(name="sortIndex")
160
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
161
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
162
    @Merge(MergeMode.ADD_CLONE)
163
    @NotNull
164
    private List<Identifier> identifiers = new ArrayList<>();
165

    
166
    @XmlElementWrapper(name = "Links", nillable = true)
167
    @XmlElement(name = "Link")
168
    @OneToMany(fetch=FetchType.LAZY, orphanRemoval=true)
169
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
170
    @Merge(MergeMode.ADD_CLONE)
171
    private Set<ExternalLink> links = new HashSet<>();
172

    
173
    @XmlTransient
174
    @Transient
175
    protected IIdentifiableEntityCacheStrategy cacheStrategy;
176

    
177
    protected IdentifiableEntity(){
178
        initListener();
179
    }
180

    
181
    @Override
182
    public void initListener(){
183
        PropertyChangeListener listener = new PropertyChangeListener() {
184
            @Override
185
            public void propertyChange(PropertyChangeEvent ev) {
186
                if (! "titleCache".equals(ev.getPropertyName())
187
                        && !"cacheStrategy".equals(ev.getPropertyName())
188
                        && ! isProtectedTitleCache()){
189
                    titleCache = null;
190
                }
191
            }
192
        };
193
        addPropertyChangeListener(listener);
194
    }
195

    
196
    /**
197
     * By default, we expect most cdm objects to be abstract things
198
     * i.e. unable to return a data representation.
199
     *
200
     * Specific subclasses (e.g. Sequence) can override if necessary.
201
     */
202
    @Override
203
    public byte[] getData() {
204
        return null;
205
    }
206

    
207
//******************************** CACHE *****************************************************/
208

    
209
    // @Transient  - must not be transient, since this property needs to to be included in all serializations produced by the remote layer
210
    @Override
211
    public String getTitleCache(){
212
        if (protectedTitleCache){
213
            return this.titleCache;
214
        }
215
        // is title dirty, i.e. equal NULL?
216
        if (titleCache == null){
217
            this.titleCache = generateTitle();
218
            this.titleCache = getTruncatedCache(this.titleCache) ;
219
        }
220
        //removed due to #5849
221
//        if(StringUtils.isBlank(titleCache)){
222
//            titleCache = this.toString();
223
//        }
224
        return titleCache;
225
    }
226

    
227
    @Deprecated
228
    @Override
229
    public void setTitleCache(String titleCache){
230
    	//TODO shouldn't we call setTitleCache(String, boolean), but is this conformant with JavaBean specification?
231
    	this.titleCache = getTruncatedCache(titleCache);
232
    }
233

    
234
    @Override
235
    public void setTitleCache(String titleCache, boolean protectCache){
236
        titleCache = getTruncatedCache(titleCache);
237
        this.titleCache = titleCache;
238
        this.protectedTitleCache = protectCache;
239
    }
240

    
241
    @Override
242
    public String resetTitleCache() {
243
        if(!protectedTitleCache){
244
            titleCache = null;
245
        }
246
        return getTitleCache();
247
    }
248

    
249
    @Transient
250
    protected String getTruncatedCache(String cache) {
251
        int maxLength = 800;
252
    	if (cache != null && cache.length() > maxLength){
253
            logger.warn("Truncation of cache: " + this.toString() + "/" + cache);
254
            cache = cache.substring(0, maxLength - 4) + "...";   //TODO do we need -4 or is -3 enough
255
        }
256
        return cache;
257
    }
258

    
259
    @Override
260
    public boolean isProtectedTitleCache() {
261
        return protectedTitleCache;
262
    }
263

    
264
    @Override
265
    public void setProtectedTitleCache(boolean protectedTitleCache) {
266
        this.protectedTitleCache = protectedTitleCache;
267
    }
268

    
269
    /**
270
     * @return true, if the current state of the titleCache (without generating it new)
271
     * is <code>null</code> or the empty string. This is primarily meant for internal use.
272
     */
273
    public boolean hasEmptyTitleCache(){
274
        return this.titleCache == null || "".equals(this.titleCache);
275
    }
276

    
277
    public boolean updateCaches(){
278
        if (this.protectedTitleCache == false){
279
            String oldTitleCache = this.titleCache;
280

    
281
            @SuppressWarnings("unchecked")
282
            String newTitleCache = cacheStrategy().getTitleCache(this);
283

    
284
            if ( oldTitleCache == null   || ! oldTitleCache.equals(newTitleCache) ){
285
                this.setTitleCache(null, false);
286
                String newCache = this.getTitleCache();
287

    
288
                if (newCache == null){
289
                    logger.warn("newCache should never be null");
290
                }
291
                if (oldTitleCache == null){
292
                    logger.info("oldTitleCache was illegaly null and has been fixed");
293
                }
294
                return true;
295
            }
296
        }
297
        return false;
298
    }
299

    
300
    /**
301
     * Updates the caches with the given cache strategy
302
     * @param entityCacheStrategy
303
     * @return <code>true</code> if some cache was updated, <code>false</code> otherwise
304
     */
305
    public boolean updateCaches(S entityCacheStrategy){
306
        S oldCacheStrategy = this.cacheStrategy();
307
        this.cacheStrategy = entityCacheStrategy != null? entityCacheStrategy : this.cacheStrategy();
308
        boolean result = this.updateCaches();
309
        this.cacheStrategy = oldCacheStrategy;
310
        return result;
311
    }
312

    
313
//**************************************************************************************
314

    
315
    @Override
316
    public LSID getLsid(){
317
        return this.lsid;
318
    }
319
    @Override
320
    public void setLsid(LSID lsid){
321
        this.lsid = lsid;
322
    }
323
    @Override
324
    public Set<Rights> getRights() {
325
        if(rights == null) {
326
            this.rights = new HashSet<>();
327
        }
328
        return this.rights;
329
    }
330

    
331
    @Override
332
    public void addRights(Rights right){
333
        getRights().add(right);
334
    }
335
    @Override
336
    public void removeRights(Rights right){
337
        getRights().remove(right);
338
    }
339

    
340

    
341
//********************** External Links **********************************************
342

    
343

    
344
    public Set<ExternalLink> getLinks(){
345
        return this.links;
346
    }
347
    public void setLinks(Set<ExternalLink> links){
348
        this.links = links;
349
    }
350
    public void addLink(ExternalLink link){
351
        if (link != null){
352
            links.add(link);
353
        }
354
    }
355
    public ExternalLink addLinkWebsite(URI uri, String description, Language descriptionLanguage){
356
        ExternalLink link = null;
357
        if (uri != null || description != null || descriptionLanguage != null){
358
            link = ExternalLink.NewWebSiteInstance(uri, description, descriptionLanguage);
359
            links.add(link);
360
        }
361
        return link;
362
    }
363
    public void removeLink(ExternalLink link){
364
        if(links.contains(link)) {
365
            links.remove(link);
366
        }
367
    }
368

    
369
//********************** CREDITS **********************************************
370

    
371
    @Override
372
    public List<Credit> getCredits() {
373
        if(credits == null) {
374
            this.credits = new ArrayList<>();
375
        }
376
        return this.credits;
377
    }
378

    
379
    @Override
380
    public Credit getCredits(Integer index){
381
        return getCredits().get(index);
382
    }
383

    
384
    @Override
385
    public void addCredit(Credit credit){
386
        getCredits().add(credit);
387
    }
388

    
389

    
390
    @Override
391
    public void addCredit(Credit credit, int index){
392
        getCredits().add(index, credit);
393
    }
394

    
395
    @Override
396
    public void removeCredit(Credit credit){
397
        getCredits().remove(credit);
398
    }
399

    
400
    @Override
401
    public void removeCredit(int index){
402
        getCredits().remove(index);
403
    }
404

    
405
    @Override
406
    public boolean replaceCredit(Credit newObject, Credit oldObject){
407
        return replaceInList(this.credits, newObject, oldObject);
408
    }
409

    
410

    
411
    @Override
412
    public List<Identifier> getIdentifiers(){
413
        if(this.identifiers == null) {
414
            this.identifiers = new ArrayList<>();
415
        }
416
        return this.identifiers;
417
    }
418
    /**
419
     * @return a set of identifier value strings
420
     */
421
    public Set<String> getIdentifierStrings(DefinedTerm type){
422
       return getIdentifierStrings(type == null? null :type.getUuid());
423
    }
424
    /**
425
     * @param identifierTypeUuid
426
     * @return a set of identifier value strings
427
     */
428
    public Set<String> getIdentifierStrings(UUID identifierTypeUuid){
429
        Set<String> result = new HashSet<>();
430
        for (Identifier identifier : getIdentifiers()){
431
            if ( (identifier.getType()== null && identifierTypeUuid == null)
432
                || (identifier.getType().getUuid().equals(identifierTypeUuid))){
433
                result.add(identifier.getIdentifier());
434
            }
435
        }
436
        return result;
437
    }
438
    /**
439
     * Returns the first identifier value of the given type.
440
     * <code>null</code> if no such identifier exists.
441
     * @param identifierTypeUuid
442
     */
443
    public String getIdentifierString(UUID identifierTypeUuid){
444
        Set<Identifier> set = getIdentifiers(identifierTypeUuid);
445
        return set.isEmpty()? null : set.iterator().next().getIdentifier();
446
    }
447
    /**
448
     * Returns the first identifier of the given type.
449
     * <code>null</code> if no such identifier exists.
450
     * @param identifierTypeUuid
451
     */
452
    public Identifier getIdentifier(UUID identifierTypeUuid){
453
        Set<Identifier> set = getIdentifiers(identifierTypeUuid);
454
        return set.isEmpty()? null : set.iterator().next();
455
    }
456

    
457
    public Set<Identifier> getIdentifiers(UUID identifierTypeUuid){
458
        Set<Identifier> result = new HashSet<>();
459
        for (Identifier identifier : getIdentifiers()){
460
            if ( (identifier.getType()== null && identifierTypeUuid == null)
461
                || (identifier.getType().getUuid().equals(identifierTypeUuid))){
462
                result.add(identifier);
463
            }
464
        }
465
        return result;
466
    }
467

    
468
    @Override
469
    public Identifier addIdentifier(String identifier, DefinedTerm identifierType){
470
    	Identifier result = Identifier.NewInstance(identifier, identifierType);
471
    	addIdentifier(result);
472
    	return result;
473
    }
474

    
475
    @Override
476
    public void addIdentifier(Integer index, Identifier identifier){
477
        if (identifier != null){
478
        	//deduplication
479
        	int oldIndex = getIdentifiers().indexOf(identifier);
480
        	if(oldIndex > -1){
481
        		getIdentifiers().remove(identifier);
482
        		if (index != null && oldIndex < index){
483
        			index--;
484
        		}
485
        	}
486

    
487
        	if (index != null){
488
        	    getIdentifiers().add(index, identifier);
489
        	}else{
490
        	    getIdentifiers().add(identifier);
491
        	}
492
        }
493
    }
494

    
495
    @Override
496
    public void addIdentifier(Identifier identifier){
497
        addIdentifier(null, identifier);
498
    }
499

    
500
    @Override
501
    public void removeIdentifier(Identifier identifier){
502
        if (identifier != null){
503
            getIdentifiers().remove(identifier);
504
        }
505
    }
506
    @Override
507
    public void removeIdentifier(int index){
508
    	getIdentifiers().remove(index);
509
    }
510

    
511
    @Override
512
    public boolean replaceIdentifier(Identifier newObject, Identifier oldObject){
513
        return replaceInList(this.identifiers, newObject, oldObject);
514
    }
515

    
516

    
517
    @Override
518
    public Set<Extension> getExtensions(){
519
        if(extensions == null) {
520
            this.extensions = new HashSet<>();
521
        }
522
        return this.extensions;
523
    }
524
    public Set<Extension> getFilteredExtensions(UUID extensionTypeUuid){
525
        Set<Extension> result = new HashSet<>();
526
        for (Extension extension : getExtensions()){
527
            if (extension.getType() != null && extension.getType().getUuid().equals(extensionTypeUuid)){
528
                result.add(extension);
529
            }
530
        }
531
        return result;
532
     }
533
    /**
534
     * @param type
535
     * @return a Set of extension value strings
536
     */
537
    public Set<String> getExtensions(ExtensionType type){
538
       return getExtensions(type.getUuid());
539
    }
540
    /**
541
     * @param extensionTypeUuid
542
     * @return a Set of extension value strings
543
     * @see #hasExtension(UUID, String)
544
     */
545
    public Set<String> getExtensions(UUID extensionTypeUuid){
546
        Set<String> result = new HashSet<>();
547
        for (Extension extension : getExtensions()){
548
            if (extension.getType() != null && extension.getType().getUuid().equals(extensionTypeUuid)){
549
                result.add(extension.getValue());
550
            }
551
        }
552
        return result;
553
    }
554

    
555
    /**
556
     * @see #getExtensionsConcat(Collection, String)
557
     */
558
    public String getExtensionsConcat(UUID extensionTypeUuid, String separator){
559
        String result = null;
560
        for (Extension extension : getExtensions()){
561
            if (extension.getType() != null && extension.getType().getUuid().equals(extensionTypeUuid)){
562
                result = CdmUtils.concat(separator, result, extension.getValue());
563
            }
564
        }
565
        return result;
566
    }
567

    
568
    /**
569
     * Return all extensions matching the given extension type as
570
     * concatenated string. If extensionTypeUuids is a sorted collection
571
     * it is given in the correct order.
572
     * @param extensionTypeUuids collection of the extension types to be considered
573
     * @param separator the separator for concatenation
574
     * @return the concatenated extension string
575
     * @see #getExtensionsConcat(Collection, String)
576
     */
577
    public String getExtensionsConcat(Collection<UUID> extensionTypeUuids, String separator){
578
        String result = null;
579
        for (UUID uuid : extensionTypeUuids){
580
            String extension = getExtensionsConcat(uuid, separator);
581
            result = CdmUtils.concat(separator, result, extension);
582
        }
583
        return result;
584
    }
585

    
586
    /**
587
     * Has this entity an extension of given type with value 'value'.
588
     * If value is <code>null</code> <code>true</code> is returned if
589
     * an Extension exists with given type and 'value' is <code>null</code>.
590
     * @param extensionTypeUuid
591
     * @param value
592
     * @see #hasExtension(ExtensionType, String)
593
     * @see #getExtensions(UUID)
594
     */
595
    public boolean hasExtension(UUID extensionTypeUuid, String value) {
596
        for (String ext : this.getExtensions(extensionTypeUuid)){
597
            if (CdmUtils.nullSafeEqual(ext, value)){
598
                return true;
599
            }
600
        }
601
        return false;
602
    }
603

    
604
    /**
605
     * @see #hasExtension(UUID, String)
606
     */
607
    public boolean hasExtension(ExtensionType extensionType, String value) {
608
        return hasExtension(extensionType.getUuid(), value);
609
    }
610

    
611
    @Override
612
    public void addExtension(String value, ExtensionType extensionType){
613
        Extension.NewInstance(this, value, extensionType);
614
    }
615

    
616
    @Override
617
    public void addExtension(Extension extension){
618
        if (extension != null){
619
            getExtensions().add(extension);
620
        }
621
    }
622
    @Override
623
    public void removeExtension(Extension extension){
624
        if (extension != null){
625
            getExtensions().remove(extension);
626
        }
627
    }
628

    
629
    @Override
630
    public void addSource(IdentifiableSource source) {
631
        if (source != null){
632
            getSources().add(source);
633
        }
634
    }
635

    
636
    @Override
637
    public void addSources(Set<IdentifiableSource> sources) {
638
        if (sources != null){
639
        	for (IdentifiableSource source: sources){
640
	            getSources().add(source);
641
        	}
642
        }
643
    }
644

    
645
    @Override
646
    protected IdentifiableSource createNewSource(OriginalSourceType type, String idInSource, String idNamespace,
647
            Reference reference, String microReference, String originalInfo, ICdmTarget target) {
648
        return IdentifiableSource.NewInstance(type, idInSource, idNamespace, reference, microReference, originalInfo, target);
649
    }
650

    
651
//******************************** TO STRING *****************************************************/
652

    
653
    @Override
654
    public String toString() {
655
        String result;
656
        if (isBlank(titleCache)){
657
            result = super.toString();
658
        }else{
659
            result = this.titleCache;
660
        }
661
        return result;
662
    }
663

    
664

    
665
    /**
666
     * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
667
     * several strings corresponding to <i>this</i> identifiable entity
668
     * (in particular taxon name caches and author strings).
669
     *
670
     * @return  the cache strategy used for <i>this</i> identifiable entity
671
     * @see     eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
672
     */
673
    @Transient
674
    @java.beans.Transient
675
    public S cacheStrategy() {
676
        if (this.cacheStrategy == null){
677
            initDefaultCacheStrategy();
678
        }
679
        return (S)this.cacheStrategy;
680
    }
681
    public void setCacheStrategy(S cacheStrategy) {
682
        this.cacheStrategy = cacheStrategy;
683
    }
684

    
685
    @Override
686
    public String generateTitle() {
687
        if (cacheStrategy() == null){
688
            //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
689
            return this.getClass() + ": " + this.getUuid();
690
        }else{
691
            S cacheStrategy = cacheStrategy();
692
            return cacheStrategy.getTitleCache(this);
693
        }
694
    }
695

    
696
    /**
697
     * Subclasses should implement setting the default cache strategy
698
     */
699
    protected abstract void initDefaultCacheStrategy();
700

    
701
//****************** CLONE ************************************************/
702

    
703
    @Override
704
    public IdentifiableEntity<S> clone() throws CloneNotSupportedException{
705

    
706
        @SuppressWarnings("unchecked")
707
        IdentifiableEntity<S> result = (IdentifiableEntity<S>)super.clone();
708

    
709
        //Extensions
710
        result.extensions = new HashSet<>();
711
        for (Extension extension : getExtensions() ){
712
            Extension newExtension = extension.clone();
713
            result.addExtension(newExtension);
714
        }
715

    
716
        //Identifier
717
        result.identifiers = new ArrayList<>();
718
        for (Identifier identifier : getIdentifiers() ){
719
        	Identifier newIdentifier = identifier.clone();
720
            result.addIdentifier(newIdentifier);
721
        }
722

    
723
        //Rights  - reusable since #5762
724
        result.rights = new HashSet<>();
725
        for(Rights right : getRights()) {
726
            result.addRights(right);
727
        }
728

    
729
        //Credits
730
        result.credits = new ArrayList<>();
731
        for(Credit credit : getCredits()) {
732
            Credit newCredit = credit.clone();
733
            result.addCredit(newCredit);
734
        }
735

    
736
        //Links
737
        result.links = new HashSet<>();
738
        for(ExternalLink link : getLinks()) {
739
            ExternalLink newLink = link.clone();
740
            result.addLink(newLink);
741
        }
742

    
743
        //no changes to: lsid, titleCache, protectedTitleCache
744

    
745
        //empty titleCache
746
        if (! protectedTitleCache){
747
            result.titleCache = null;
748
        }
749

    
750
        result.initListener();  //TODO why do we need this, isnt't the listener in constructor enough?
751
        return result;
752
    }
753
}
(30-30/56)