Project

General

Profile

Download (21.3 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.Index;
24
import javax.persistence.Inheritance;
25
import javax.persistence.InheritanceType;
26
import javax.persistence.ManyToMany;
27
import javax.persistence.ManyToOne;
28
import javax.persistence.MapKeyJoinColumn;
29
import javax.persistence.OneToMany;
30
import javax.persistence.Table;
31
import javax.persistence.Transient;
32
import javax.validation.constraints.NotNull;
33
import javax.xml.bind.annotation.XmlAccessType;
34
import javax.xml.bind.annotation.XmlAccessorType;
35
import javax.xml.bind.annotation.XmlAttribute;
36
import javax.xml.bind.annotation.XmlElement;
37
import javax.xml.bind.annotation.XmlElementWrapper;
38
import javax.xml.bind.annotation.XmlIDREF;
39
import javax.xml.bind.annotation.XmlRootElement;
40
import javax.xml.bind.annotation.XmlSchemaType;
41
import javax.xml.bind.annotation.XmlType;
42
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
43

    
44
import org.apache.log4j.Logger;
45
import org.hibernate.annotations.Cascade;
46
import org.hibernate.annotations.CascadeType;
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
import org.hibernate.search.bridge.builtin.BooleanBridge;
57

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

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

    
113
    private static final long serialVersionUID = 6932680139334408031L;
114
    private static final Logger logger = Logger.getLogger(SpecimenOrObservationBase.class);
115

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

    
135

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

    
143

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

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

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

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

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

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

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

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

    
212
    @XmlAttribute(name = "publish")
213
    @Field(analyze = Analyze.NO)
214
    @FieldBridge(impl=BooleanBridge.class)
215
    private boolean publish = true;
216

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

    
231

    
232
    //if true identityCache will not be automatically generated/updated
233
    @XmlElement(name = "ProtectedIdentityCache")
234
    private boolean protectedIdentityCache;
235

    
236

    
237
//********************************** CONSTRUCTOR *********************************/
238

    
239
    //for hibernate use only
240
    @Deprecated
241
    protected SpecimenOrObservationBase(){
242
        super();
243
    }
244

    
245
    protected SpecimenOrObservationBase(SpecimenOrObservationType recordBasis) {
246
        super();
247
        if (recordBasis == null){ throw new IllegalArgumentException("RecordBasis must not be null");}
248
        this.recordBasis = recordBasis;
249
    }
250

    
251

    
252
    /**
253
     * Subclasses should implement setting the default cache strate
254
     */
255
    protected abstract void initDefaultCacheStrategy();
256

    
257

    
258
//************************* GETTER / SETTER ***********************/
259

    
260

    
261
    /**@see #recordBasis */
262
    public SpecimenOrObservationType getRecordBasis() {
263
        return recordBasis;
264
    }
265
    /**@see #recordBasis */
266
    public void setRecordBasis(SpecimenOrObservationType recordBasis) {
267
        this.recordBasis = recordBasis;
268
    }
269

    
270

    
271
    /**
272
     * @return the identityCache
273
     */
274
    public String getIdentityCache() {
275
        return identityCache;
276
    }
277
    /**
278
     * @param identityCache the identityCache to set
279
     */
280
    public void setIdentityCache(String identityCache) {
281
        this.identityCache = identityCache;
282
    }
283

    
284
    /**
285
     * @return the protectedIdentityCache
286
     */
287
    public boolean isProtectedIdentityCache() {
288
        return protectedIdentityCache;
289
    }
290
    /**
291
     * @param protectedIdentityCache the protectedIdentityCache to set
292
     */
293
    public void setProtectedIdentityCache(boolean protectedIdentityCache) {
294
        this.protectedIdentityCache = protectedIdentityCache;
295
    }
296

    
297
    /**@see #preferredStableUri */
298
    public URI getPreferredStableUri() {
299
        return preferredStableUri;
300
    }
301
    /**@see #preferredStableUri */
302
    public void setPreferredStableUri(URI preferredStableUri) {
303
        this.preferredStableUri = preferredStableUri;
304
    }
305

    
306

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

    
320
    /**
321
     * @see #isPublish()
322
     * @param publish
323
     */
324
    @Override
325
    public void setPublish(boolean publish) {
326
        this.publish = publish;
327
    }
328

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

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

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

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

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

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

    
417

    
418
    public Set<DerivationEvent> getDerivationEvents() {
419
        if(derivationEvents == null) {
420
            this.derivationEvents = new HashSet<>();
421
        }
422
        return this.derivationEvents;
423
    }
424

    
425
    public void addDerivationEvent(DerivationEvent derivationEvent) {
426
        if (! this.derivationEvents.contains(derivationEvent)){
427
            this.derivationEvents.add(derivationEvent);
428
            derivationEvent.addOriginal(this);
429
        }
430
    }
431

    
432
    public void removeDerivationEvent(DerivationEvent derivationEvent) {
433
        if (this.derivationEvents.contains(derivationEvent)){
434
            this.derivationEvents.remove(derivationEvent);
435
            derivationEvent.removeOriginal(this);
436
        }
437
    }
438

    
439
    public Set<DeterminationEvent> getDeterminations() {
440
        if(determinations == null) {
441
            this.determinations = new HashSet<>();
442
        }
443
        return this.determinations;
444
    }
445

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

    
451
    public void removeDetermination(DeterminationEvent determination) {
452
        // FIXME bidirectional integrity. Use protected Determination setter
453
        this.determinations.remove(determination);
454
    }
455

    
456
    public DefinedTerm getSex() {
457
        return sex;
458
    }
459

    
460
    public void setSex(DefinedTerm sex) {
461
        this.sex = sex;
462
    }
463

    
464
    public DefinedTerm getLifeStage() {
465
        return lifeStage;
466
    }
467

    
468
    public void setLifeStage(DefinedTerm lifeStage) {
469
        this.lifeStage = lifeStage;
470
    }
471

    
472

    
473
    /**
474
     * @see #kindOfUnit
475
     * @return
476
     */
477
    public DefinedTerm getKindOfUnit() {
478
        return kindOfUnit;
479
    }
480

    
481
    /**
482
     * @see #kindOfUnit
483
     * @param kindOfUnit
484
     */
485
    public void setKindOfUnit(DefinedTerm kindOfUnit) {
486
        this.kindOfUnit = kindOfUnit;
487
    }
488

    
489
    public String getIndividualCount() {
490
        return individualCount;
491
    }
492

    
493
    public void setIndividualCount(String individualCount) {
494
        this.individualCount = individualCount;
495
    }
496

    
497
    public Map<Language,LanguageString> getDefinition(){
498
        return this.definition;
499
    }
500

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

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

    
529

    
530
    public void removeDefinition(Language lang){
531
        this.definition.remove(lang);
532
    }
533

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

    
545

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

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

    
584

    
585

    
586
//******************** CLONE **********************************************/
587

    
588
    @Override
589
    public Object clone() throws CloneNotSupportedException {
590
        SpecimenOrObservationBase<S> result = (SpecimenOrObservationBase<S>)super.clone();
591

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

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

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

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

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

    
616
        //no changes to: individualCount
617
        return result;
618
    }
619
}
(11-11/14)