Project

General

Profile

Download (21.5 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.MappedSuperclass;
25
import javax.persistence.OneToMany;
26
import javax.persistence.Transient;
27
import javax.validation.constraints.NotNull;
28
import javax.validation.constraints.Size;
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.annotations.IndexColumn;
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.Store;
47
import org.hibernate.validator.constraints.NotEmpty;
48

    
49
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
50
import eu.etaxonomy.cdm.hibernate.search.StripHtmlBridge;
51
import eu.etaxonomy.cdm.jaxb.FormattedTextAdapter;
52
import eu.etaxonomy.cdm.jaxb.LSIDAdapter;
53
import eu.etaxonomy.cdm.model.media.Rights;
54
import eu.etaxonomy.cdm.model.name.BotanicalName;
55
import eu.etaxonomy.cdm.model.name.NonViralName;
56
import eu.etaxonomy.cdm.model.name.TaxonNameBase;
57
import eu.etaxonomy.cdm.model.reference.Reference;
58
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
59
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
60
import eu.etaxonomy.cdm.strategy.match.Match;
61
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
62
import eu.etaxonomy.cdm.strategy.match.MatchMode;
63
import eu.etaxonomy.cdm.strategy.merge.Merge;
64
import eu.etaxonomy.cdm.strategy.merge.MergeMode;
65
import eu.etaxonomy.cdm.validation.Level2;
66

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

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

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

    
106
    @XmlElement(name = "TitleCache", required = true)
107
    @XmlJavaTypeAdapter(FormattedTextAdapter.class)
108
    @Column(length=255, name="titleCache")
109
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
110
    @NotEmpty(groups = Level2.class) // implictly NotNull
111
    @Size(max = 800)  //see #1592
112
    @Fields({
113
        @Field(store=Store.YES),
114
        @Field(name = "titleCache__sort", analyze = Analyze.NO, store=Store.YES)
115
    })
116
    @FieldBridge(impl=StripHtmlBridge.class)
117
    protected String titleCache;
118

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

    
123
    @XmlElementWrapper(name = "Rights", nillable = true)
124
    @XmlElement(name = "Rights")
125
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
126
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
127
    //TODO
128
    @Merge(MergeMode.ADD_CLONE)
129
    @NotNull
130
    private Set<Rights> rights = new HashSet<Rights>();
131

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

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

    
150
    @XmlElementWrapper(name = "Sources", nillable = true)
151
    @XmlElement(name = "IdentifiableSource")
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<IdentifiableSource> sources = new HashSet<IdentifiableSource>();
157

    
158
    @XmlTransient
159
    @Transient
160
    protected S cacheStrategy;
161

    
162
    protected IdentifiableEntity(){
163
        initListener();
164
    }
165

    
166
    protected void initListener(){
167
        PropertyChangeListener listener = new PropertyChangeListener() {
168
            @Override
169
            public void propertyChange(PropertyChangeEvent e) {
170
                if (!e.getPropertyName().equals("titleCache") && !e.getPropertyName().equals("cacheStrategy") && ! isProtectedTitleCache()){
171
                    titleCache = null;
172
                }
173
            }
174
        };
175
        addPropertyChangeListener(listener);
176
    }
177

    
178
    /**
179
     * By default, we expect most cdm objects to be abstract things
180
     * i.e. unable to return a data representation.
181
     *
182
     * Specific subclasses (e.g. Sequence) can override if necessary.
183
     */
184
    @Override
185
    public byte[] getData() {
186
        return null;
187
    }
188

    
189
//******************************** CACHE *****************************************************/
190

    
191

    
192
    /* (non-Javadoc)
193
     * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getTitleCache()
194
     */
195
    // @Transient  - must not be transient, since this property needs to to be included in all serializations produced by the remote layer
196
    @Override
197
    public String getTitleCache(){
198
        if (protectedTitleCache){
199
            return this.titleCache;
200
        }
201
        // is title dirty, i.e. equal NULL?
202
        if (titleCache == null){
203
            this.titleCache = generateTitle();
204
            this.titleCache = getTruncatedCache(this.titleCache) ;
205
        }
206
        return titleCache;
207
    }
208

    
209
    /**
210
     * The titleCache will be regenerated from scratch if not protected
211
     * @return <code>true</code> if title cache was regenerated, <code>false</code> otherwise
212
     */
213
    protected boolean regenerateTitleCache() {
214
        if (!protectedTitleCache) {
215
            this.titleCache = null;
216
            getTitleCache();
217
        }
218
        return protectedTitleCache;
219
    }
220

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

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

    
235

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

    
250
//**************************************************************************************
251

    
252
    @Override
253
    public LSID getLsid(){
254
        return this.lsid;
255
    }
256
    @Override
257
    public void setLsid(LSID lsid){
258
        this.lsid = lsid;
259
    }
260
    @Override
261
    public Set<Rights> getRights() {
262
        if(rights == null) {
263
            this.rights = new HashSet<Rights>();
264
        }
265
        return this.rights;
266
    }
267

    
268
    @Override
269
    public void addRights(Rights right){
270
        getRights().add(right);
271
    }
272
    @Override
273
    public void removeRights(Rights right){
274
        getRights().remove(right);
275
    }
276

    
277

    
278
    @Override
279
    public List<Credit> getCredits() {
280
        if(credits == null) {
281
            this.credits = new ArrayList<Credit>();
282
        }
283
        return this.credits;
284
    }
285

    
286
    @Override
287
    public Credit getCredits(Integer index){
288
        return getCredits().get(index);
289
    }
290

    
291
    @Override
292
    public void addCredit(Credit credit){
293
        getCredits().add(credit);
294
    }
295

    
296

    
297
    @Override
298
    public void addCredit(Credit credit, int index){
299
        getCredits().add(index, credit);
300
    }
301

    
302
    @Override
303
    public void removeCredit(Credit credit){
304
        getCredits().remove(credit);
305
    }
306

    
307
    @Override
308
    public void removeCredit(int index){
309
        getCredits().remove(index);
310
    }
311

    
312
    @Override
313
    public Set<Extension> getExtensions(){
314
        if(extensions == null) {
315
            this.extensions = new HashSet<Extension>();
316
        }
317
        return this.extensions;
318
    }
319
    /**
320
     * @param type
321
     * @return a Set of extension value strings
322
     */
323
    public Set<String> getExtensions(ExtensionType type){
324
       return getExtensions(type.getUuid());
325
    }
326
    /**
327
     * @param extensionTypeUuid
328
     * @return a Set of extension value strings
329
     */
330
    public Set<String> getExtensions(UUID extensionTypeUuid){
331
        Set<String> result = new HashSet<String>();
332
        for (Extension extension : getExtensions()){
333
            if (extension.getType().getUuid().equals(extensionTypeUuid)){
334
                result.add(extension.getValue());
335
            }
336
        }
337
        return result;
338
    }
339

    
340
    public void addExtension(String value, ExtensionType extensionType){
341
        Extension.NewInstance(this, value, extensionType);
342
    }
343

    
344
    @Override
345
    public void addExtension(Extension extension){
346
        if (extension != null){
347
            extension.setExtendedObj(this);
348
            getExtensions().add(extension);
349
        }
350
    }
351
    @Override
352
    public void removeExtension(Extension extension){
353
        if (extension != null){
354
            extension.setExtendedObj(null);
355
            getExtensions().remove(extension);
356
        }
357
    }
358

    
359
    @Override
360
    public boolean isProtectedTitleCache() {
361
        return protectedTitleCache;
362
    }
363

    
364
    @Override
365
    public void setProtectedTitleCache(boolean protectedTitleCache) {
366
        this.protectedTitleCache = protectedTitleCache;
367
    }
368

    
369
    @Override
370
    public Set<IdentifiableSource> getSources() {
371
        if(sources == null) {
372
            this.sources = new HashSet<IdentifiableSource>();
373
        }
374
        return this.sources;
375
    }
376

    
377
    @Override
378
    public void addSource(IdentifiableSource source) {
379
        if (source != null){
380
            IdentifiableEntity<?> oldSourcedObj = source.getSourcedObj();
381
            if (oldSourcedObj != null && oldSourcedObj != this){
382
                oldSourcedObj.getSources().remove(source);
383
            }
384
            getSources().add(source);
385
            source.setSourcedObj(this);
386
        }
387
    }
388

    
389
    @Override
390
    public IdentifiableSource addSource(OriginalSourceType type, String id, String idNamespace, Reference citation, String microCitation) {
391
        if (id == null && idNamespace == null && citation == null && microCitation == null){
392
            return null;
393
        }
394
        IdentifiableSource source = IdentifiableSource.NewInstance(type, id, idNamespace, citation, microCitation);
395
        addSource(source);
396
        return source;
397
    }
398
    
399
    
400
    @Override
401
    public IdentifiableSource addImportSource(String id, String idNamespace, Reference<?> citation, String microCitation) {
402
        if (id == null && idNamespace == null && citation == null && microCitation == null){
403
            return null;
404
        }
405
        IdentifiableSource source = IdentifiableSource.NewInstance(OriginalSourceType.Import, id, idNamespace, citation, microCitation);
406
        addSource(source);
407
        return source;
408
    }
409

    
410

    
411
    @Override
412
    public void removeSource(IdentifiableSource source) {
413
        getSources().remove(source);
414
    }
415

    
416
//******************************** TO STRING *****************************************************/
417

    
418
    /* (non-Javadoc)
419
     * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
420
     */
421
     @Override
422
    public String toString() {
423
        String result;
424
        if (titleCache == null){
425
            result = super.toString();
426
        }else{
427
            result = this.titleCache;
428
        }
429
        return result;
430
    }
431

    
432

    
433
    public int compareTo(IdentifiableEntity identifiableEntity) {
434

    
435
         int result = 0;
436

    
437
         if (identifiableEntity == null) {
438
             throw new NullPointerException("Cannot compare to null.");
439
         }
440

    
441
         // First, compare the name cache.
442
         // TODO: Avoid using instanceof operator
443
         // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
444

    
445
         String specifiedNameCache = "";
446
         String thisNameCache = "";
447
         String specifiedTitleCache = "";
448
         String thisTitleCache = "";
449
         String specifiedReferenceTitleCache = "";
450
         String thisReferenceTitleCache = "";
451
         String thisGenusString = "";
452
         String specifiedGenusString = "";
453
         int thisrank_order = 0;
454

    
455
         //TODO we can remove all the deproxies here except for the first one
456
         identifiableEntity = HibernateProxyHelper.deproxy(identifiableEntity, IdentifiableEntity.class);
457
         if(identifiableEntity instanceof NonViralName) {
458
             specifiedNameCache = HibernateProxyHelper.deproxy(identifiableEntity, NonViralName.class).getNameCache();
459
             specifiedTitleCache = identifiableEntity.getTitleCache();
460
            if (identifiableEntity instanceof BotanicalName){
461
            	 if (((BotanicalName)identifiableEntity).isAutonym()){
462
            		 boolean isProtected = false;
463
            		 String oldNameCache = ((BotanicalName) identifiableEntity).getNameCache();
464
            		 if ( ((BotanicalName)identifiableEntity).isProtectedNameCache()){
465
            			 isProtected = true;
466
            		 }
467
            		 ((BotanicalName)identifiableEntity).setProtectedNameCache(false);
468
            		 ((BotanicalName)identifiableEntity).setNameCache(null, false);
469
            		 specifiedNameCache = ((BotanicalName) identifiableEntity).getNameCache();
470
            		 ((BotanicalName)identifiableEntity).setNameCache(oldNameCache, isProtected);
471
            		 
472
            	 }
473
             }
474

    
475
         } else if(identifiableEntity instanceof TaxonBase) {
476
             TaxonBase taxonBase = HibernateProxyHelper.deproxy(identifiableEntity, TaxonBase.class);
477

    
478
             TaxonNameBase<?,?> taxonNameBase = taxonBase.getName();
479

    
480

    
481
             NonViralName nonViralName = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class);
482
             specifiedNameCache = nonViralName.getNameCache();
483
             specifiedTitleCache = taxonNameBase.getTitleCache();
484

    
485
             specifiedReferenceTitleCache = ((TaxonBase)identifiableEntity).getSec().getTitleCache();
486
             Reference reference = taxonBase.getSec();
487
             if (reference != null) {
488
                 reference = HibernateProxyHelper.deproxy(reference, Reference.class);
489
                 specifiedReferenceTitleCache = reference.getTitleCache();
490
             }
491
         }
492

    
493
         if(this.isInstanceOf(NonViralName.class)) {
494
             thisNameCache = HibernateProxyHelper.deproxy(this, NonViralName.class).getNameCache();
495
             thisTitleCache = getTitleCache();
496
             
497
             if (this instanceof BotanicalName){
498
            	 if (((BotanicalName)this).isAutonym()){
499
            		 boolean isProtected = false;
500
            		 String oldNameCache = ((BotanicalName) this).getNameCache();
501
            		 if ( ((BotanicalName)this).isProtectedNameCache()){
502
            			 isProtected = true;
503
            		 }
504
            		 ((BotanicalName)this).setProtectedNameCache(false);
505
            		 ((BotanicalName)this).setNameCache(null, false);
506
            		 thisNameCache = ((BotanicalName) this).getNameCache();
507
            		 ((BotanicalName)this).setNameCache(oldNameCache, isProtected);
508
            	 }
509
             }
510
         } else if(this.isInstanceOf(TaxonBase.class)) {
511
             TaxonNameBase<?,?> taxonNameBase= HibernateProxyHelper.deproxy(this, TaxonBase.class).getName();
512
             NonViralName nonViralName = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class);
513
             thisNameCache = nonViralName.getNameCache();
514
             thisTitleCache = taxonNameBase.getTitleCache();
515
             thisReferenceTitleCache = ((TaxonBase)this).getSec().getTitleCache();
516
             thisGenusString = nonViralName.getGenusOrUninomial();
517
         }
518

    
519
         // Compare name cache of taxon names
520
         
521
        
522

    
523
         if (!specifiedNameCache.equals("") && !thisNameCache.equals("")) {
524
             result = thisNameCache.compareTo(specifiedNameCache);
525
         }
526

    
527
         // Compare title cache of taxon names
528

    
529
         if ((result == 0) && (!specifiedTitleCache.equals("") || !thisTitleCache.equals(""))) {
530
             result = thisTitleCache.compareTo(specifiedTitleCache);
531
         }
532

    
533
         // Compare title cache of taxon references
534

    
535
         if ((result == 0) && (!specifiedReferenceTitleCache.equals("") || !thisReferenceTitleCache.equals(""))) {
536
             result = thisReferenceTitleCache.compareTo(specifiedReferenceTitleCache);
537
         }
538

    
539
         return result;
540
     }
541

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

    
557
        public void setCacheStrategy(S cacheStrategy) {
558
            this.cacheStrategy = cacheStrategy;
559
        }
560

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

    
571
//****************** CLONE ************************************************/
572

    
573
    /* (non-Javadoc)
574
     * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
575
     */
576
    @Override
577
    public Object clone() throws CloneNotSupportedException{
578
        IdentifiableEntity result = (IdentifiableEntity)super.clone();
579

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

    
587
        //OriginalSources
588
        result.sources = new HashSet<IdentifiableSource>();
589
        for (IdentifiableSource source : getSources()){
590
            IdentifiableSource newSource = (IdentifiableSource)source.clone();
591
            result.addSource(newSource);
592
        }
593

    
594
        //Rights
595
        result.rights = new HashSet<Rights>();
596
        for(Rights rights : getRights()) {
597
            result.addRights(rights);
598
        }
599

    
600

    
601
        //Credits
602
        result.credits = new ArrayList<Credit>();
603
        for(Credit credit : getCredits()) {
604
            result.addCredit(credit);
605
        }
606

    
607
        //no changes to: lsid, titleCache, protectedTitleCache
608

    
609
        //empty titleCache
610
        if (! protectedTitleCache){
611
            result.titleCache = null;
612
        }
613

    
614
        result.initListener();
615
        return result;
616
    }
617

    
618

    
619
}
(34-34/68)