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;
39
import org.apache.logging.log4j.Logger;
40
import org.hibernate.annotations.Cascade;
41
import org.hibernate.annotations.CascadeType;
42
import org.hibernate.envers.Audited;
43
import org.hibernate.search.annotations.Analyze;
44
import org.hibernate.search.annotations.Field;
45
import org.hibernate.search.annotations.FieldBridge;
46
import org.hibernate.search.annotations.Fields;
47
import org.hibernate.search.annotations.Index;
48
import org.hibernate.search.annotations.SortableField;
49
import org.hibernate.search.annotations.Store;
50

    
51
import eu.etaxonomy.cdm.common.CdmUtils;
52
import eu.etaxonomy.cdm.common.URI;
53
import eu.etaxonomy.cdm.hibernate.search.StripHtmlBridge;
54
import eu.etaxonomy.cdm.jaxb.FormattedTextAdapter;
55
import eu.etaxonomy.cdm.jaxb.LSIDAdapter;
56
import eu.etaxonomy.cdm.model.media.ExternalLink;
57
import eu.etaxonomy.cdm.model.media.Rights;
58
import eu.etaxonomy.cdm.model.reference.ICdmTarget;
59
import eu.etaxonomy.cdm.model.reference.OriginalSourceBase;
60
import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
61
import eu.etaxonomy.cdm.model.reference.Reference;
62
import eu.etaxonomy.cdm.model.term.DefinedTerm;
63
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
64
import eu.etaxonomy.cdm.strategy.match.Match;
65
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
66
import eu.etaxonomy.cdm.strategy.match.MatchMode;
67
import eu.etaxonomy.cdm.strategy.merge.Merge;
68
import eu.etaxonomy.cdm.strategy.merge.MergeMode;
69
import eu.etaxonomy.cdm.validation.Level2;
70

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
208
//******************************** CACHE *****************************************************/
209

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

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

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

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

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

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

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

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

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

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

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

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

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

    
314
//**************************************************************************************
315

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

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

    
341

    
342
//********************** External Links **********************************************
343

    
344

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

    
370
//********************** CREDITS **********************************************
371

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

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

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

    
390

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

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

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

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

    
411

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

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

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

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

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

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

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

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

    
517

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

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

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

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

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

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

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

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

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

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

    
652
//******************************** TO STRING *****************************************************/
653

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

    
665

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

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

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

    
702
//****************** CLONE ************************************************/
703

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

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

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

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

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

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

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

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

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

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