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.occurrence;
11

    
12
import java.net.URI;
13
import java.util.ArrayList;
14
import java.util.Collection;
15
import java.util.HashMap;
16
import java.util.HashSet;
17
import java.util.Map;
18
import java.util.Set;
19

    
20
import javax.persistence.Column;
21
import javax.persistence.Entity;
22
import javax.persistence.FetchType;
23
import javax.persistence.Inheritance;
24
import javax.persistence.InheritanceType;
25
import javax.persistence.ManyToMany;
26
import javax.persistence.ManyToOne;
27
import javax.persistence.MapKeyJoinColumn;
28
import javax.persistence.OneToMany;
29
import javax.persistence.Transient;
30
import javax.validation.constraints.NotNull;
31
import javax.xml.bind.annotation.XmlAccessType;
32
import javax.xml.bind.annotation.XmlAccessorType;
33
import javax.xml.bind.annotation.XmlAttribute;
34
import javax.xml.bind.annotation.XmlElement;
35
import javax.xml.bind.annotation.XmlElementWrapper;
36
import javax.xml.bind.annotation.XmlIDREF;
37
import javax.xml.bind.annotation.XmlRootElement;
38
import javax.xml.bind.annotation.XmlSchemaType;
39
import javax.xml.bind.annotation.XmlType;
40
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
41

    
42
import org.apache.log4j.Logger;
43
import org.hibernate.annotations.Cascade;
44
import org.hibernate.annotations.CascadeType;
45
import org.hibernate.annotations.Index;
46
import org.hibernate.annotations.Table;
47
import org.hibernate.annotations.Type;
48
import org.hibernate.envers.Audited;
49
import org.hibernate.search.annotations.Analyze;
50
import org.hibernate.search.annotations.Field;
51
import org.hibernate.search.annotations.FieldBridge;
52
import org.hibernate.search.annotations.Fields;
53
import org.hibernate.search.annotations.IndexedEmbedded;
54
import org.hibernate.search.annotations.SortableField;
55
import org.hibernate.search.annotations.Store;
56

    
57
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
58
import eu.etaxonomy.cdm.hibernate.search.StripHtmlBridge;
59
import eu.etaxonomy.cdm.jaxb.FormattedTextAdapter;
60
import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
61
import eu.etaxonomy.cdm.model.common.DefinedTerm;
62
import eu.etaxonomy.cdm.model.common.IIntextReferenceTarget;
63
import eu.etaxonomy.cdm.model.common.IMultiLanguageTextHolder;
64
import eu.etaxonomy.cdm.model.common.IPublishable;
65
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
66
import eu.etaxonomy.cdm.model.common.Language;
67
import eu.etaxonomy.cdm.model.common.LanguageString;
68
import eu.etaxonomy.cdm.model.common.MultilanguageText;
69
import eu.etaxonomy.cdm.model.common.TermType;
70
import eu.etaxonomy.cdm.model.description.DescriptionBase;
71
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
72
import eu.etaxonomy.cdm.model.description.IDescribable;
73
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
74
import eu.etaxonomy.cdm.model.description.TaxonDescription;
75
import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
76
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
77
import eu.etaxonomy.cdm.strategy.match.Match;
78
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
79
import eu.etaxonomy.cdm.strategy.match.MatchMode;
80

    
81
/**
82
 * type figures are observations with at least a figure object in media
83
 * @author m.doering
84
 * @since 08-Nov-2007 13:06:41
85
 */
86
@XmlAccessorType(XmlAccessType.FIELD)
87
@XmlType(name = "SpecimenOrObservationBase", propOrder = {
88
    "recordBasis",
89
    "identityCache",
90
    "protectedIdentityCache",
91
    "publish",
92
    "preferredStableUri",
93
    "sex",
94
    "lifeStage",
95
    "kindOfUnit",
96
    "individualCount",
97
    "definition",
98
    "descriptions",
99
    "determinations",
100
    "derivationEvents"
101
})
102
@XmlRootElement(name = "SpecimenOrObservationBase")
103
@Entity
104
@Audited
105
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
106
@Table(appliesTo="SpecimenOrObservationBase", indexes = { @Index(name = "specimenOrObservationBaseTitleCacheIndex", columnNames = { "titleCache" }),
107
        @Index(name = "specimenOrObservationBaseIdentityCacheIndex", columnNames = { "identityCache" }) })
108
public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCacheStrategy<?>>
109
                extends IdentifiableEntity<S>
110
                implements IMultiLanguageTextHolder, IIntextReferenceTarget, IDescribable<DescriptionBase<S>>, IPublishable  {
111
    private static final long serialVersionUID = 6932680139334408031L;
112
    private static final Logger logger = Logger.getLogger(SpecimenOrObservationBase.class);
113

    
114
    /**
115
     * An indication of what the unit record describes.
116
     *
117
     * NOTE: The name of the attribute was chosen against the common naming conventions of the CDM
118
     * as it is well known in common standards like ABCD and DarwinCore. According to CDM naming
119
     * conventions it would be specimenOrObservationType.
120
     *
121
     * @see ABCD: DataSets/DataSet/Units/Unit/RecordBasis
122
     * @see Darwin Core: http://wiki.tdwg.org/twiki/bin/view/DarwinCore/BasisOfRecord
123
     */
124
    @XmlAttribute(name ="RecordBasis")
125
    @Column(name="recordBasis")
126
    @NotNull
127
    @Type(type = "eu.etaxonomy.cdm.hibernate.EnumUserType",
128
        parameters = {@org.hibernate.annotations.Parameter(name  = "enumClass", value = "eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType")}
129
    )
130
    @Audited
131
    private SpecimenOrObservationType recordBasis;
132

    
133

    
134
    @XmlElementWrapper(name = "Descriptions")
135
    @XmlElement(name = "Description")
136
    @OneToMany(mappedBy="describedSpecimenOrObservation", fetch = FetchType.LAZY)
137
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
138
    @NotNull
139
    private Set<DescriptionBase<S>> descriptions = new HashSet<>();
140

    
141

    
142
    @XmlElementWrapper(name = "Determinations")
143
    @XmlElement(name = "Determination")
144
    @OneToMany(mappedBy="identifiedUnit", orphanRemoval=true)
145
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
146
    @IndexedEmbedded(depth = 2)
147
    @NotNull
148
    private Set<DeterminationEvent> determinations = new HashSet<>();
149

    
150
    @XmlElement(name = "Sex")
151
    @XmlIDREF
152
    @XmlSchemaType(name = "IDREF")
153
    @ManyToOne(fetch = FetchType.LAZY)
154
    private DefinedTerm sex;
155

    
156
    @XmlElement(name = "LifeStage")
157
    @XmlIDREF
158
    @XmlSchemaType(name = "IDREF")
159
    @ManyToOne(fetch = FetchType.LAZY)
160
    private DefinedTerm lifeStage;
161

    
162
    /**
163
     * Part(s) of organism or class of materials represented by this unit.
164
     * Example: fruits, seeds, tissue, gDNA, leaves
165
     *
166
     * @see ABCD: DataSets/DataSet/Units/Unit/KindOfUnit
167
     * @see TermType#KindOfUnit
168
     */
169
    @XmlElement(name = "KindOfUnit")
170
    @XmlIDREF
171
    @XmlSchemaType(name = "IDREF")
172
    @ManyToOne(fetch = FetchType.LAZY)
173
//    @IndexedEmbedded(depth=1)
174
    private DefinedTerm kindOfUnit;
175

    
176
    @XmlElement(name = "IndividualCount")
177
    @Field(analyze = Analyze.NO)
178
    private String individualCount;
179

    
180
    /**
181
     * The preferred stable identifier (URI) as discussed in
182
     * {@link  http://dev.e-taxonomy.eu/trac/ticket/5606}
183
     */
184
    @XmlElement(name = "PreferredStableUri")
185
    @Field(analyze = Analyze.NO)
186
    @Type(type="uriUserType")
187
    private URI preferredStableUri;
188

    
189
    // the verbatim description of this occurrence. Free text usable when no atomised data is available.
190
    // in conjunction with titleCache which serves as the "citation" string for this object
191
    @XmlElement(name = "Description")
192
    @XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
193
    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
194
    @MapKeyJoinColumn(name="definition_mapkey_id")
195
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE})
196
    @IndexedEmbedded
197
    @NotNull
198
    protected Map<Language,LanguageString> definition = new HashMap<>();
199

    
200
    // events that created derivedUnits from this unit
201
    @XmlElementWrapper(name = "DerivationEvents")
202
    @XmlElement(name = "DerivationEvent")
203
    @XmlIDREF
204
    @XmlSchemaType(name = "IDREF")
205
    @ManyToMany(fetch=FetchType.LAZY)
206
    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE})
207
    @NotNull
208
    protected Set<DerivationEvent> derivationEvents = new HashSet<DerivationEvent>();
209

    
210
    @XmlAttribute(name = "publish")
211
    private boolean publish = true;
212

    
213
    @XmlElement(name = "IdentityCache", required = false)
214
    @XmlJavaTypeAdapter(FormattedTextAdapter.class)
215
    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
216
//    @NotEmpty(groups = Level2.class) // implictly NotNull
217
    @Fields({
218
        @Field(store=Store.YES),
219
        //  If the field is only needed for sorting and nothing else, you may configure it as
220
        //  un-indexed and un-stored, thus avoid unnecessary index growth.
221
        @Field(name = "identityCache__sort", analyze = Analyze.NO, store=Store.NO, index = org.hibernate.search.annotations.Index.NO)
222
    })
223
    @SortableField(forField = "identityCache__sort")
224
    @FieldBridge(impl=StripHtmlBridge.class)
225
    private String identityCache;
226

    
227

    
228
    //if true identityCache will not be automatically generated/updated
229
    @XmlElement(name = "ProtectedIdentityCache")
230
    private boolean protectedIdentityCache;
231

    
232

    
233
//********************************** CONSTRUCTOR *********************************/
234

    
235
    //for hibernate use only
236
    @Deprecated
237
    protected SpecimenOrObservationBase(){
238
        super();
239
    }
240

    
241
    protected SpecimenOrObservationBase(SpecimenOrObservationType recordBasis) {
242
        super();
243
        if (recordBasis == null){ throw new IllegalArgumentException("RecordBasis must not be null");}
244
        this.recordBasis = recordBasis;
245
    }
246

    
247

    
248
    /**
249
     * Subclasses should implement setting the default cache strate
250
     */
251
    protected abstract void initDefaultCacheStrategy();
252

    
253

    
254
//************************* GETTER / SETTER ***********************/
255

    
256

    
257
    /**@see #recordBasis */
258
    public SpecimenOrObservationType getRecordBasis() {
259
        return recordBasis;
260
    }
261
    /**@see #recordBasis */
262
    public void setRecordBasis(SpecimenOrObservationType recordBasis) {
263
        this.recordBasis = recordBasis;
264
    }
265

    
266

    
267
    /**
268
     * @return the identityCache
269
     */
270
    public String getIdentityCache() {
271
        return identityCache;
272
    }
273
    /**
274
     * @param identityCache the identityCache to set
275
     */
276
    public void setIdentityCache(String identityCache) {
277
        this.identityCache = identityCache;
278
    }
279

    
280
    /**
281
     * @return the protectedIdentityCache
282
     */
283
    public boolean isProtectedIdentityCache() {
284
        return protectedIdentityCache;
285
    }
286
    /**
287
     * @param protectedIdentityCache the protectedIdentityCache to set
288
     */
289
    public void setProtectedIdentityCache(boolean protectedIdentityCache) {
290
        this.protectedIdentityCache = protectedIdentityCache;
291
    }
292

    
293
    /**@see #preferredStableUri */
294
    public URI getPreferredStableUri() {
295
        return preferredStableUri;
296
    }
297
    /**@see #preferredStableUri */
298
    public void setPreferredStableUri(URI preferredStableUri) {
299
        this.preferredStableUri = preferredStableUri;
300
    }
301

    
302

    
303
    /**
304
     * Returns the boolean value indicating if this specimen or observation should be withheld
305
     * (<code>publish=false</code>) or not (<code>publish=true</code>) during any publication
306
     * process to the general public.
307
     * This publish flag implementation is preliminary and may be replaced by a more general
308
     * implementation of READ rights in future.<BR>
309
     * The default value is <code>true</code>.
310
     */
311
    @Override
312
    public boolean isPublish() {
313
        return publish;
314
    }
315

    
316
    /**
317
     * @see #isPublish()
318
     * @param publish
319
     */
320
    @Override
321
    public void setPublish(boolean publish) {
322
        this.publish = publish;
323
    }
324

    
325
    /**
326
     * The descriptions this specimen or observation is part of.<BR>
327
     * A specimen can not only have it's own {@link SpecimenDescription specimen description }
328
     * but can also be part of a {@link TaxonDescription taxon description} or a
329
     * {@link TaxonNameDescription taxon name description}.<BR>
330
     * @see #getSpecimenDescriptions()
331
     * @return
332
     */
333
    @Override
334
    public Set<DescriptionBase<S>> getDescriptions() {
335
        if(descriptions == null) {
336
            this.descriptions = new HashSet<>();
337
        }
338
        return this.descriptions;
339
    }
340

    
341
    /**
342
     * Returns the {@link SpecimenDescription specimen descriptions} this specimen is part of.
343
     * @see #getDescriptions()
344
     * @return
345
     */
346
    @Transient
347
    public Set<SpecimenDescription> getSpecimenDescriptions() {
348
        return getSpecimenDescriptions(true);
349
    }
350

    
351
    /**
352
     * Returns the {@link SpecimenDescription specimen descriptions} this specimen is part of.
353
     * @see #getDescriptions()
354
     * @return
355
     */
356
    @Transient
357
    public Set<SpecimenDescription> getSpecimenDescriptions(boolean includeImageGallery) {
358
        Set<SpecimenDescription> specimenDescriptions = new HashSet<SpecimenDescription>();
359
        for (DescriptionBase descriptionBase : getDescriptions()){
360
            if (descriptionBase.isInstanceOf(SpecimenDescription.class)){
361
                if (includeImageGallery || descriptionBase.isImageGallery() == false){
362
                    specimenDescriptions.add(descriptionBase.deproxy(descriptionBase, SpecimenDescription.class));
363
                }
364

    
365
            }
366
        }
367
        return specimenDescriptions;
368
    }
369
    /**
370
     * Returns the {@link SpecimenDescription specimen descriptions} which act as an image gallery
371
     * and which this specimen is part of.
372
     * @see #getDescriptions()
373
     * @return
374
     */
375
    @Transient
376
    public Set<SpecimenDescription> getSpecimenDescriptionImageGallery() {
377
        Set<SpecimenDescription> specimenDescriptions = new HashSet<SpecimenDescription>();
378
        for (DescriptionBase descriptionBase : getDescriptions()){
379
            if (descriptionBase.isInstanceOf(SpecimenDescription.class)){
380
                if (descriptionBase.isImageGallery() == true){
381
                    specimenDescriptions.add(descriptionBase.deproxy(descriptionBase, SpecimenDescription.class));
382
                }
383
            }
384
        }
385
        return specimenDescriptions;
386
    }
387

    
388
    /**
389
     * Adds a new description to this specimen or observation
390
     * @param description
391
     */
392
    @Override
393
    public void addDescription(DescriptionBase description) {
394
        if (description.getDescribedSpecimenOrObservation() != null){
395
            description.getDescribedSpecimenOrObservation().removeDescription(description);
396
        }
397
        descriptions.add(description);
398
        description.setDescribedSpecimenOrObservation(this);
399
    }
400

    
401
    /**
402
     * Removes a specimen from a description (removes a description from this specimen)
403
     * @param description
404
     */
405
    @Override
406
    public void removeDescription(DescriptionBase description) {
407
        boolean existed = descriptions.remove(description);
408
        if (existed){
409
            description.setDescribedSpecimenOrObservation(null);
410
        }
411
    }
412

    
413

    
414
    public Set<DerivationEvent> getDerivationEvents() {
415
        if(derivationEvents == null) {
416
            this.derivationEvents = new HashSet<DerivationEvent>();
417
        }
418
        return this.derivationEvents;
419
    }
420

    
421
    public void addDerivationEvent(DerivationEvent derivationEvent) {
422
        if (! this.derivationEvents.contains(derivationEvent)){
423
            this.derivationEvents.add(derivationEvent);
424
            derivationEvent.addOriginal(this);
425
        }
426
    }
427

    
428
    public void removeDerivationEvent(DerivationEvent derivationEvent) {
429
        if (this.derivationEvents.contains(derivationEvent)){
430
            this.derivationEvents.remove(derivationEvent);
431
            derivationEvent.removeOriginal(this);
432
        }
433
    }
434

    
435
    public Set<DeterminationEvent> getDeterminations() {
436
        if(determinations == null) {
437
            this.determinations = new HashSet<DeterminationEvent>();
438
        }
439
        return this.determinations;
440
    }
441

    
442
    public void addDetermination(DeterminationEvent determination) {
443
        // FIXME bidirectional integrity. Use protected Determination setter
444
        this.determinations.add(determination);
445
    }
446

    
447
    public void removeDetermination(DeterminationEvent determination) {
448
        // FIXME bidirectional integrity. Use protected Determination setter
449
        this.determinations.remove(determination);
450
    }
451

    
452
    public DefinedTerm getSex() {
453
        return sex;
454
    }
455

    
456
    public void setSex(DefinedTerm sex) {
457
        this.sex = sex;
458
    }
459

    
460
    public DefinedTerm getLifeStage() {
461
        return lifeStage;
462
    }
463

    
464
    public void setLifeStage(DefinedTerm lifeStage) {
465
        this.lifeStage = lifeStage;
466
    }
467

    
468

    
469
    /**
470
     * @see #kindOfUnit
471
     * @return
472
     */
473
    public DefinedTerm getKindOfUnit() {
474
        return kindOfUnit;
475
    }
476

    
477
    /**
478
     * @see #kindOfUnit
479
     * @param kindOfUnit
480
     */
481
    public void setKindOfUnit(DefinedTerm kindOfUnit) {
482
        this.kindOfUnit = kindOfUnit;
483
    }
484

    
485
    public String getIndividualCount() {
486
        return individualCount;
487
    }
488

    
489
    public void setIndividualCount(String individualCount) {
490
        this.individualCount = individualCount;
491
    }
492

    
493
    public Map<Language,LanguageString> getDefinition(){
494
        return this.definition;
495
    }
496

    
497
    /**
498
     * adds the {@link LanguageString description} to the {@link MultilanguageText multilanguage text}
499
     * used to define <i>this</i> specimen or observation.
500
     *
501
     * @param description	the languageString in with the title string and the given language
502
     *
503
     * @see    	   		#getDefinition()
504
     * @see    	   		#putDefinition(Language, String)
505
     */
506
    public void putDefinition(LanguageString description){
507
        this.definition.put(description.getLanguage(),description);
508
    }
509

    
510
    /**
511
     * Creates a {@link LanguageString language string} based on the given text string
512
     * and the given {@link Language language} and adds it to the {@link MultilanguageText multilanguage text}
513
     * used to define <i>this</i> specimen or observation.
514
     *
515
     * @param language	the language in which the title string is formulated
516
     * @param text		the definition in a particular language
517
     *
518
     * @see    	   		#getDefinition()
519
     * @see    	   		#putDefinition(LanguageString)
520
     */
521
    public void putDefinition(Language language, String text){
522
        this.definition.put(language, LanguageString.NewInstance(text, language));
523
    }
524

    
525

    
526
    public void removeDefinition(Language lang){
527
        this.definition.remove(lang);
528
    }
529

    
530
    /**
531
     * for derived units get the single next higher parental/original unit.
532
     * If multiple original units exist throw error
533
     * @return
534
     */
535
    @Transient
536
    public SpecimenOrObservationBase getOriginalUnit(){
537
        logger.warn("GetOriginalUnit not yet implemented");
538
        return null;
539
    }
540

    
541

    
542
    public boolean hasCharacterData() {
543
        Set<DescriptionBase<S>> descriptions = this.getDescriptions();
544
        for (DescriptionBase<?> descriptionBase : descriptions) {
545
            if (descriptionBase.isInstanceOf(SpecimenDescription.class)) {
546
                SpecimenDescription specimenDescription = HibernateProxyHelper.deproxy(descriptionBase, SpecimenDescription.class);
547
                Set<DescriptionElementBase> elements = specimenDescription.getElements();
548
                for (DescriptionElementBase descriptionElementBase : elements) {
549
                    if (descriptionElementBase.isCharacterData()){
550
                        return true;
551
                    }
552
                }
553
            }
554
        }
555
        return false;
556
    }
557

    
558
    /**
559
     * Returns a list of all description items which
560
     * @return
561
     */
562
    @Transient
563
    public Collection<DescriptionElementBase> characterData() {
564
        Collection<DescriptionElementBase> states = new ArrayList<DescriptionElementBase>();
565
        Set<DescriptionBase<S>> descriptions = this.getDescriptions();
566
        for (DescriptionBase<?> descriptionBase : descriptions) {
567
            if (descriptionBase.isInstanceOf(SpecimenDescription.class)) {
568
                SpecimenDescription specimenDescription = HibernateProxyHelper.deproxy(descriptionBase, SpecimenDescription.class);
569
                Set<DescriptionElementBase> elements = specimenDescription.getElements();
570
                for (DescriptionElementBase descriptionElementBase : elements) {
571
                    if(descriptionElementBase.isCharacterData()){
572
                        states.add(descriptionElementBase);
573
                    }
574
                }
575
            }
576
        }
577
        return states;
578
    }
579

    
580

    
581

    
582
//******************** CLONE **********************************************/
583

    
584
    /* (non-Javadoc)
585
     * @see eu.etaxonomy.cdm.model.media.IdentifiableMediaEntity#clone()
586
     * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#clone()
587
     * @see java.lang.Object#clone()
588
     */
589
    @Override
590
    public Object clone() throws CloneNotSupportedException {
591
        SpecimenOrObservationBase<S> result = (SpecimenOrObservationBase<S>)super.clone();
592

    
593
        //defininion (description, languageString)
594
        result.definition = cloneLanguageString(this.definition);
595

    
596
        //sex
597
        result.setSex(this.sex);
598
        //life stage
599
        result.setLifeStage(this.lifeStage);
600

    
601
        //Descriptions
602
        for(DescriptionBase<S> description : this.descriptions) {
603
            result.addDescription(description);
604
        }
605

    
606
        //DeterminationEvent FIXME should clone() the determination
607
        // as the relationship is OneToMany
608
        for(DeterminationEvent determination : this.determinations) {
609
            result.addDetermination(determination);
610
        }
611

    
612
        //DerivationEvent
613
        for(DerivationEvent derivationEvent : this.derivationEvents) {
614
            result.addDerivationEvent(derivationEvent);
615
        }
616

    
617
        //no changes to: individualCount
618
        return result;
619
    }
620

    
621

    
622
}
(11-11/14)