Project

General

Profile

Download (19.7 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

    
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.ManyToMany;
25
import javax.persistence.MappedSuperclass;
26
import javax.persistence.OneToMany;
27
import javax.persistence.OrderColumn;
28
import javax.persistence.Transient;
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.commons.lang.StringUtils;
39
import org.apache.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
import org.hibernate.validator.constraints.NotEmpty;
51

    
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.reference.Reference;
57
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
58
import eu.etaxonomy.cdm.strategy.match.Match;
59
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
60
import eu.etaxonomy.cdm.strategy.match.MatchMode;
61
import eu.etaxonomy.cdm.strategy.merge.Merge;
62
import eu.etaxonomy.cdm.strategy.merge.MergeMode;
63
import eu.etaxonomy.cdm.validation.Level2;
64

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

    
93
    private static final Logger logger = Logger.getLogger(IdentifiableEntity.class);
94

    
95
    @XmlTransient
96
    public static final boolean PROTECTED = true;
97
    @XmlTransient
98
    public static final boolean NOT_PROTECTED = false;
99

    
100
    @XmlElement(name = "LSID", type = String.class)
101
    @XmlJavaTypeAdapter(LSIDAdapter.class)
102
    @Embedded
103
    private LSID lsid;
104

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

    
120
    //if true titleCache will not be automatically generated/updated
121
    @XmlElement(name = "ProtectedTitleCache")
122
    protected boolean protectedTitleCache;
123

    
124
    @XmlElementWrapper(name = "Rights", nillable = true)
125
    @XmlElement(name = "Rights")
126
    @ManyToMany(fetch = FetchType.LAZY /*, orphanRemoval=false*/)  //#5762 M:N now
127
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
128
    //TODO
129
    @Merge(MergeMode.ADD_CLONE)
130
    @NotNull
131
    private Set<Rights> rights = new HashSet<>();
132

    
133
    @XmlElementWrapper(name = "Credits", nillable = true)
134
    @XmlElement(name = "Credit")
135
    @OrderColumn(name="sortIndex")
136
    @OneToMany(fetch = FetchType.LAZY)
137
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
138
    //TODO
139
    @Merge(MergeMode.ADD_CLONE)
140
    @NotNull
141
    private List<Credit> credits = new ArrayList<>();
142

    
143
    @XmlElementWrapper(name = "Extensions", nillable = true)
144
    @XmlElement(name = "Extension")
145
    @OneToMany(fetch = FetchType.LAZY)
146
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
147
    @Merge(MergeMode.ADD_CLONE)
148
    @NotNull
149
    private Set<Extension> extensions = new HashSet<>();
150

    
151
    @XmlElementWrapper(name = "Identifiers", nillable = true)
152
    @XmlElement(name = "Identifier")
153
    @OrderColumn(name="sortIndex")
154
    @OneToMany(fetch = FetchType.LAZY)
155
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
156
    @Merge(MergeMode.ADD_CLONE)
157
    @NotNull
158
    private List<Identifier> identifiers = new ArrayList<>();
159

    
160
    @XmlElementWrapper(name = "Sources", nillable = true)
161
    @XmlElement(name = "IdentifiableSource")
162
    @OneToMany(fetch = FetchType.LAZY)
163
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
164
    @Merge(MergeMode.ADD_CLONE)
165
    @NotNull
166
    private Set<IdentifiableSource> sources = new HashSet<>();
167

    
168
    @XmlTransient
169
    @Transient
170
    protected S cacheStrategy;
171

    
172
    protected IdentifiableEntity(){
173
        initListener();
174
    }
175

    
176
    @Override
177
    public void initListener(){
178
        PropertyChangeListener listener = new PropertyChangeListener() {
179
            @Override
180
            public void propertyChange(PropertyChangeEvent ev) {
181
                if (! "titleCache".equals(ev.getPropertyName()) && !"cacheStrategy".equals(ev.getPropertyName()) && ! isProtectedTitleCache()){
182
                    titleCache = null;
183
                }
184
            }
185
        };
186
        addPropertyChangeListener(listener);
187
    }
188

    
189
    /**
190
     * By default, we expect most cdm objects to be abstract things
191
     * i.e. unable to return a data representation.
192
     *
193
     * Specific subclasses (e.g. Sequence) can override if necessary.
194
     */
195
    @Override
196
    public byte[] getData() {
197
        return null;
198
    }
199

    
200
//******************************** CACHE *****************************************************/
201

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

    
220
    @Deprecated
221
    @Override
222
    public void setTitleCache(String titleCache){
223
    	//TODO shouldn't we call setTitleCache(String, boolean),but is this conformant with Java Bean Specification?
224
    	this.titleCache = getTruncatedCache(titleCache);
225
    }
226

    
227
    @Override
228
    public void setTitleCache(String titleCache, boolean protectCache){
229
        titleCache = getTruncatedCache(titleCache);
230
        this.titleCache = titleCache;
231
        this.protectedTitleCache = protectCache;
232
    }
233

    
234
    /**
235
     * @param cache
236
     * @return
237
     */
238
    @Transient
239
    protected String getTruncatedCache(String cache) {
240
        int maxLength = 800;
241
    	if (cache != null && cache.length() > maxLength){
242
            logger.warn("Truncation of cache: " + this.toString() + "/" + cache);
243
            cache = cache.substring(0, maxLength - 4) + "...";   //TODO do we need -4 or is -3 enough
244
        }
245
        return cache;
246
    }
247

    
248

    
249
    @Override
250
    public boolean isProtectedTitleCache() {
251
        return protectedTitleCache;
252
    }
253

    
254
    @Override
255
    public void setProtectedTitleCache(boolean protectedTitleCache) {
256
        this.protectedTitleCache = protectedTitleCache;
257
    }
258

    
259
    /**
260
     *
261
     * @return true, if the current state of the titleCache (without generating it new)
262
     * is <code>null</code> or the empty string. This is primarily meant for internal use.
263
     */
264
    public boolean hasEmptyTitleCache(){
265
        return this.titleCache == null || "".equals(this.titleCache);
266
    }
267

    
268
//**************************************************************************************
269

    
270
    @Override
271
    public LSID getLsid(){
272
        return this.lsid;
273
    }
274
    @Override
275
    public void setLsid(LSID lsid){
276
        this.lsid = lsid;
277
    }
278
    @Override
279
    public Set<Rights> getRights() {
280
        if(rights == null) {
281
            this.rights = new HashSet<Rights>();
282
        }
283
        return this.rights;
284
    }
285

    
286
    @Override
287
    public void addRights(Rights right){
288
        getRights().add(right);
289
    }
290
    @Override
291
    public void removeRights(Rights right){
292
        getRights().remove(right);
293
    }
294

    
295

    
296
    @Override
297
    public List<Credit> getCredits() {
298
        if(credits == null) {
299
            this.credits = new ArrayList<Credit>();
300
        }
301
        return this.credits;
302
    }
303

    
304
    @Override
305
    public Credit getCredits(Integer index){
306
        return getCredits().get(index);
307
    }
308

    
309
    @Override
310
    public void addCredit(Credit credit){
311
        getCredits().add(credit);
312
    }
313

    
314

    
315
    @Override
316
    public void addCredit(Credit credit, int index){
317
        getCredits().add(index, credit);
318
    }
319

    
320
    @Override
321
    public void removeCredit(Credit credit){
322
        getCredits().remove(credit);
323
    }
324

    
325
    @Override
326
    public void removeCredit(int index){
327
        getCredits().remove(index);
328
    }
329

    
330
    @Override
331
    public boolean replaceCredit(Credit newObject, Credit oldObject){
332
        return replaceInList(this.credits, newObject, oldObject);
333
    }
334

    
335

    
336
    @Override
337
    public List<Identifier> getIdentifiers(){
338
        if(this.identifiers == null) {
339
            this.identifiers = new ArrayList<Identifier>();
340
        }
341
        return this.identifiers;
342
    }
343
    /**
344
     * @param type
345
     * @return a set of identifier value strings
346
     */
347
    public Set<String> getIdentifiers(DefinedTerm type){
348
       return getIdentifiers(type.getUuid());
349
    }
350
    /**
351
     * @param identifierTypeUuid
352
     * @return a set of identifier value strings
353
     */
354
    public Set<String> getIdentifiers(UUID identifierTypeUuid){
355
        Set<String> result = new HashSet<String>();
356
        for (Identifier<?> identifier : getIdentifiers()){
357
            if (identifier.getType().getUuid().equals(identifierTypeUuid)){
358
                result.add(identifier.getIdentifier());
359
            }
360
        }
361
        return result;
362
    }
363

    
364
    @Override
365
    public Identifier addIdentifier(String identifier, DefinedTerm identifierType){
366
    	Identifier<?> result = Identifier.NewInstance(identifier, identifierType);
367
    	addIdentifier(result);
368
    	return result;
369
    }
370

    
371
    @Override
372
    public void addIdentifier(Integer index, Identifier identifier){
373
        if (identifier != null){
374
        	//deduplication
375
        	int oldIndex = getIdentifiers().indexOf(identifier);
376
        	if(oldIndex > -1){
377
        		getIdentifiers().remove(identifier);
378
        		if (index != null && oldIndex < index){
379
        			index--;
380
        		}
381
        	}
382

    
383
        	if (index != null){
384
        	    getIdentifiers().add(index, identifier);
385
        	}else{
386
        	    getIdentifiers().add(identifier);
387
        	}
388
        }
389
    }
390

    
391
    @Override
392
    public void addIdentifier(Identifier identifier){
393
        addIdentifier(null, identifier);
394
    }
395

    
396
    @Override
397
    public void removeIdentifier(Identifier identifier){
398
        if (identifier != null){
399
            getIdentifiers().remove(identifier);
400
        }
401
    }
402
    @Override
403
    public void removeIdentifier(int index){
404
    	getIdentifiers().remove(index);
405
    }
406

    
407
    @Override
408
    public boolean replaceIdentifier(Identifier newObject, Identifier oldObject){
409
        return replaceInList(this.identifiers, newObject, oldObject);
410
    }
411

    
412

    
413
    @Override
414
    public Set<Extension> getExtensions(){
415
        if(extensions == null) {
416
            this.extensions = new HashSet<Extension>();
417
        }
418
        return this.extensions;
419
    }
420
    /**
421
     * @param type
422
     * @return a Set of extension value strings
423
     */
424
    public Set<String> getExtensions(ExtensionType type){
425
       return getExtensions(type.getUuid());
426
    }
427
    /**
428
     * @param extensionTypeUuid
429
     * @return a Set of extension value strings
430
     */
431
    public Set<String> getExtensions(UUID extensionTypeUuid){
432
        Set<String> result = new HashSet<String>();
433
        for (Extension extension : getExtensions()){
434
            if (extension.getType().getUuid().equals(extensionTypeUuid)){
435
                result.add(extension.getValue());
436
            }
437
        }
438
        return result;
439
    }
440

    
441
    @Override
442
    public void addExtension(String value, ExtensionType extensionType){
443
        Extension.NewInstance(this, value, extensionType);
444
    }
445

    
446
    @Override
447
    public void addExtension(Extension extension){
448
        if (extension != null){
449
            getExtensions().add(extension);
450
        }
451
    }
452
    @Override
453
    public void removeExtension(Extension extension){
454
        if (extension != null){
455
            getExtensions().remove(extension);
456
        }
457
    }
458

    
459

    
460
    @Override
461
    public Set<IdentifiableSource> getSources() {
462
        if(sources == null) {
463
            this.sources = new HashSet<IdentifiableSource>();
464
        }
465
        return this.sources;
466
    }
467

    
468
    @Override
469
    public void addSource(IdentifiableSource source) {
470
        if (source != null){
471
            getSources().add(source);
472
        }
473
    }
474

    
475
    @Override
476
    public void addSources(Set<IdentifiableSource> sources) {
477
        if (sources != null){
478
        	for (IdentifiableSource source: sources){
479
	            getSources().add(source);
480
        	}
481
        }
482
    }
483

    
484
    @Override
485
    public void removeSources() {
486
       this.sources.clear();
487
    }
488

    
489
    @Override
490
    public IdentifiableSource addSource(OriginalSourceType type, String id, String idNamespace, Reference citation, String microCitation) {
491
        if (id == null && idNamespace == null && citation == null && microCitation == null){
492
            return null;
493
        }
494
        IdentifiableSource source = IdentifiableSource.NewInstance(type, id, idNamespace, citation, microCitation);
495
        addSource(source);
496
        return source;
497
    }
498

    
499

    
500
    @Override
501
    public IdentifiableSource addImportSource(String id, String idNamespace, Reference citation, String microCitation) {
502
        if (id == null && idNamespace == null && citation == null && microCitation == null){
503
            return null;
504
        }
505
        IdentifiableSource source = IdentifiableSource.NewInstance(OriginalSourceType.Import, id, idNamespace, citation, microCitation);
506
        addSource(source);
507
        return source;
508
    }
509

    
510
    @Override
511
    public IdentifiableSource addPrimaryTaxonomicSource(Reference citation, String microCitation) {
512
        if (citation == null && microCitation == null){
513
            return null;
514
        }
515
        IdentifiableSource source = IdentifiableSource.NewPrimarySourceInstance(citation, microCitation);
516
        addSource(source);
517
        return source;
518
    }
519

    
520
    @Override
521
    public IdentifiableSource addPrimaryTaxonomicSource(Reference citation) {
522
        return addPrimaryTaxonomicSource(citation, null);
523
    }
524

    
525

    
526
    @Override
527
    public void removeSource(IdentifiableSource source) {
528
        getSources().remove(source);
529
    }
530

    
531
//******************************** TO STRING *****************************************************/
532

    
533
    @Override
534
    public String toString() {
535
        String result;
536
        if (StringUtils.isBlank(titleCache)){
537
            result = super.toString();
538
        }else{
539
            result = this.titleCache;
540
        }
541
        return result;
542
    }
543

    
544

    
545
    /**
546
     * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
547
     * several strings corresponding to <i>this</i> identifiable entity
548
     * (in particular taxon name caches and author strings).
549
     *
550
     * @return  the cache strategy used for <i>this</i> identifiable entity
551
     * @see     eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
552
     */
553
    public S getCacheStrategy() {
554
        return this.cacheStrategy;
555
    }
556
    /**
557
     * @see 	#getCacheStrategy()
558
     */
559

    
560
    public void setCacheStrategy(S cacheStrategy) {
561
        this.cacheStrategy = cacheStrategy;
562
    }
563

    
564
    @Override
565
    public String generateTitle() {
566
        if (getCacheStrategy() == null){
567
            //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
568
            return this.getClass() + ": " + this.getUuid();
569
        }else{
570
            return getCacheStrategy().getTitleCache(this);
571
        }
572
    }
573

    
574
//****************** CLONE ************************************************/
575

    
576
    @Override
577
    public Object clone() throws CloneNotSupportedException{
578
        IdentifiableEntity<?> result = (IdentifiableEntity<?>)super.clone();
579

    
580
        //Extensions
581
        result.extensions = new HashSet<>();
582
        for (Extension extension : getExtensions() ){
583
            Extension newExtension = (Extension)extension.clone();
584
            result.addExtension(newExtension);
585
        }
586

    
587
        //Identifier
588
        result.identifiers = new ArrayList<>();
589
        for (Identifier<?> identifier : getIdentifiers() ){
590
        	Identifier<?> newIdentifier = (Identifier<?>)identifier.clone();
591
            result.addIdentifier(newIdentifier);
592
        }
593

    
594
        //OriginalSources
595
        result.sources = new HashSet<>();
596
        for (IdentifiableSource source : getSources()){
597
            IdentifiableSource newSource = (IdentifiableSource)source.clone();
598
            result.addSource(newSource);
599
        }
600

    
601
        //Rights
602
        result.rights = new HashSet<>();
603
        for(Rights rights : getRights()) {
604
            Rights newRights = (Rights)rights.clone();
605
            result.addRights(newRights);
606
        }
607

    
608

    
609
        //Credits
610
        result.credits = new ArrayList<>();
611
        for(Credit credit : getCredits()) {
612
            Credit newCredit = (Credit)credit.clone();
613
            result.addCredit(newCredit);
614
        }
615

    
616
        //no changes to: lsid, titleCache, protectedTitleCache
617

    
618
        //empty titleCache
619
        if (! protectedTitleCache){
620
            result.titleCache = null;
621
        }
622

    
623
        result.initListener();
624
        return result;
625
    }
626

    
627

    
628
}
(37-37/77)