Project

General

Profile

Download (24.6 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.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.log4j.Logger;
38
import org.hibernate.annotations.Cascade;
39
import org.hibernate.annotations.CascadeType;
40
import org.hibernate.envers.Audited;
41
import org.hibernate.search.annotations.Analyze;
42
import org.hibernate.search.annotations.Field;
43
import org.hibernate.search.annotations.FieldBridge;
44
import org.hibernate.search.annotations.Fields;
45
import org.hibernate.search.annotations.Index;
46
import org.hibernate.search.annotations.SortableField;
47
import org.hibernate.search.annotations.Store;
48
import org.hibernate.validator.constraints.NotEmpty;
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 = Logger.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 /*, orphanRemoval=false*/)  //#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 S 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 Java Bean 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 = getCacheStrategy().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.getCacheStrategy();
307
        this.cacheStrategy = entityCacheStrategy != null? entityCacheStrategy : this.getCacheStrategy();
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
     * @param type
420
     * @return a set of identifier value strings
421
     */
422
    public Set<String> getIdentifiers(DefinedTerm type){
423
       return getIdentifiers(type == null? null :type.getUuid());
424
    }
425
    /**
426
     * @param identifierTypeUuid
427
     * @return a set of identifier value strings
428
     */
429
    public Set<String> getIdentifiers(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 getIdentifier(UUID identifierTypeUuid){
445
        Set<Identifier> set = getIdentifiers_(identifierTypeUuid);
446
        return set.isEmpty()? null : set.iterator().next().getIdentifier();
447
    }
448

    
449

    
450
    public Set<Identifier> getIdentifiers_(UUID identifierTypeUuid){
451
        Set<Identifier> result = new HashSet<>();
452
        for (Identifier identifier : getIdentifiers()){
453
            if ( (identifier.getType()== null && identifierTypeUuid == null)
454
                || (identifier.getType().getUuid().equals(identifierTypeUuid))){
455
                result.add(identifier);
456
            }
457
        }
458
        return result;
459
    }
460

    
461
    @Override
462
    public Identifier addIdentifier(String identifier, DefinedTerm identifierType){
463
    	Identifier result = Identifier.NewInstance(identifier, identifierType);
464
    	addIdentifier(result);
465
    	return result;
466
    }
467

    
468
    @Override
469
    public void addIdentifier(Integer index, Identifier identifier){
470
        if (identifier != null){
471
        	//deduplication
472
        	int oldIndex = getIdentifiers().indexOf(identifier);
473
        	if(oldIndex > -1){
474
        		getIdentifiers().remove(identifier);
475
        		if (index != null && oldIndex < index){
476
        			index--;
477
        		}
478
        	}
479

    
480
        	if (index != null){
481
        	    getIdentifiers().add(index, identifier);
482
        	}else{
483
        	    getIdentifiers().add(identifier);
484
        	}
485
        }
486
    }
487

    
488
    @Override
489
    public void addIdentifier(Identifier identifier){
490
        addIdentifier(null, identifier);
491
    }
492

    
493
    @Override
494
    public void removeIdentifier(Identifier identifier){
495
        if (identifier != null){
496
            getIdentifiers().remove(identifier);
497
        }
498
    }
499
    @Override
500
    public void removeIdentifier(int index){
501
    	getIdentifiers().remove(index);
502
    }
503

    
504
    @Override
505
    public boolean replaceIdentifier(Identifier newObject, Identifier oldObject){
506
        return replaceInList(this.identifiers, newObject, oldObject);
507
    }
508

    
509

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

    
548
    /**
549
     * @see #getExtensionsConcat(Collection, String)
550
     */
551
    public String getExtensionsConcat(UUID extensionTypeUuid, String separator){
552
        String result = null;
553
        for (Extension extension : getExtensions()){
554
            if (extension.getType() != null && extension.getType().getUuid().equals(extensionTypeUuid)){
555
                result = CdmUtils.concat(separator, result, extension.getValue());
556
            }
557
        }
558
        return result;
559
    }
560

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

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

    
597
    /**
598
     * @see #hasExtension(UUID, String)
599
     */
600
    public boolean hasExtension(ExtensionType extensionType, String value) {
601
        return hasExtension(extensionType.getUuid(), value);
602
    }
603

    
604
    @Override
605
    public void addExtension(String value, ExtensionType extensionType){
606
        Extension.NewInstance(this, value, extensionType);
607
    }
608

    
609
    @Override
610
    public void addExtension(Extension extension){
611
        if (extension != null){
612
            getExtensions().add(extension);
613
        }
614
    }
615
    @Override
616
    public void removeExtension(Extension extension){
617
        if (extension != null){
618
            getExtensions().remove(extension);
619
        }
620
    }
621

    
622
    @Override
623
    public void addSource(IdentifiableSource source) {
624
        if (source != null){
625
            getSources().add(source);
626
        }
627
    }
628

    
629
    @Override
630
    public void addSources(Set<IdentifiableSource> sources) {
631
        if (sources != null){
632
        	for (IdentifiableSource source: sources){
633
	            getSources().add(source);
634
        	}
635
        }
636
    }
637

    
638
    @Override
639
    protected IdentifiableSource createNewSource(OriginalSourceType type, String idInSource, String idNamespace,
640
            Reference reference, String microReference, String originalInfo, ICdmTarget target) {
641
        return IdentifiableSource.NewInstance(type, idInSource, idNamespace, reference, microReference, originalInfo, target);
642
    }
643

    
644
//******************************** TO STRING *****************************************************/
645

    
646
    @Override
647
    public String toString() {
648
        String result;
649
        if (isBlank(titleCache)){
650
            result = super.toString();
651
        }else{
652
            result = this.titleCache;
653
        }
654
        return result;
655
    }
656

    
657

    
658
    /**
659
     * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
660
     * several strings corresponding to <i>this</i> identifiable entity
661
     * (in particular taxon name caches and author strings).
662
     *
663
     * @return  the cache strategy used for <i>this</i> identifiable entity
664
     * @see     eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
665
     */
666
    public S getCacheStrategy() {
667
        if (this.cacheStrategy == null){
668
            initDefaultCacheStrategy();
669
        }
670
        return this.cacheStrategy;
671
    }
672
    public void setCacheStrategy(S cacheStrategy) {
673
        this.cacheStrategy = cacheStrategy;
674
    }
675

    
676
    @Override
677
    public String generateTitle() {
678
        if (getCacheStrategy() == null){
679
            //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
680
            return this.getClass() + ": " + this.getUuid();
681
        }else{
682
            S cacheStrategy = getCacheStrategy();
683
            return cacheStrategy.getTitleCache(this);
684
        }
685
    }
686

    
687
    /**
688
     * Subclasses should implement setting the default cache strategy
689
     */
690
    protected abstract void initDefaultCacheStrategy();
691

    
692
//****************** CLONE ************************************************/
693

    
694
    @Override
695
    public IdentifiableEntity<S> clone() throws CloneNotSupportedException{
696

    
697
        @SuppressWarnings("unchecked")
698
        IdentifiableEntity<S> result = (IdentifiableEntity<S>)super.clone();
699

    
700
        //Extensions
701
        result.extensions = new HashSet<>();
702
        for (Extension extension : getExtensions() ){
703
            Extension newExtension = extension.clone();
704
            result.addExtension(newExtension);
705
        }
706

    
707
        //Identifier
708
        result.identifiers = new ArrayList<>();
709
        for (Identifier identifier : getIdentifiers() ){
710
        	Identifier newIdentifier = identifier.clone();
711
            result.addIdentifier(newIdentifier);
712
        }
713

    
714
        //Rights  - reusable since #5762
715
        result.rights = new HashSet<>();
716
        for(Rights right : getRights()) {
717
            result.addRights(right);
718
        }
719

    
720
        //Credits
721
        result.credits = new ArrayList<>();
722
        for(Credit credit : getCredits()) {
723
            Credit newCredit = credit.clone();
724
            result.addCredit(newCredit);
725
        }
726

    
727
        //Links
728
        result.links = new HashSet<>();
729
        for(ExternalLink link : getLinks()) {
730
            ExternalLink newLink = link.clone();
731
            result.addLink(newLink);
732
        }
733

    
734
        //no changes to: lsid, titleCache, protectedTitleCache
735

    
736
        //empty titleCache
737
        if (! protectedTitleCache){
738
            result.titleCache = null;
739
        }
740

    
741
        result.initListener();  //TODO why do we need this, isnt't the listener in constructor enough?
742
        return result;
743
    }
744
}
(32-32/58)