2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
10 package eu
.etaxonomy
.cdm
.model
.taxon
;
12 import java
.lang
.reflect
.Method
;
13 import java
.util
.List
;
15 import javax
.persistence
.Entity
;
16 import javax
.persistence
.FetchType
;
17 import javax
.persistence
.Index
;
18 import javax
.persistence
.ManyToOne
;
19 import javax
.persistence
.OneToOne
;
20 import javax
.persistence
.Table
;
21 import javax
.persistence
.Transient
;
22 import javax
.validation
.constraints
.NotNull
;
23 import javax
.xml
.bind
.annotation
.XmlAccessType
;
24 import javax
.xml
.bind
.annotation
.XmlAccessorType
;
25 import javax
.xml
.bind
.annotation
.XmlAttribute
;
26 import javax
.xml
.bind
.annotation
.XmlElement
;
27 import javax
.xml
.bind
.annotation
.XmlIDREF
;
28 import javax
.xml
.bind
.annotation
.XmlSchemaType
;
29 import javax
.xml
.bind
.annotation
.XmlType
;
31 import org
.apache
.logging
.log4j
.LogManager
;import org
.apache
.logging
.log4j
.Logger
;
32 import org
.hibernate
.annotations
.Cascade
;
33 import org
.hibernate
.annotations
.CascadeType
;
34 import org
.hibernate
.envers
.Audited
;
35 import org
.hibernate
.search
.annotations
.Analyze
;
36 import org
.hibernate
.search
.annotations
.ClassBridge
;
37 import org
.hibernate
.search
.annotations
.ClassBridges
;
38 import org
.hibernate
.search
.annotations
.Field
;
39 import org
.hibernate
.search
.annotations
.FieldBridge
;
40 import org
.hibernate
.search
.annotations
.IndexedEmbedded
;
41 import org
.hibernate
.search
.annotations
.Store
;
42 import org
.hibernate
.search
.bridge
.builtin
.BooleanBridge
;
44 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
45 import eu
.etaxonomy
.cdm
.compare
.taxon
.TaxonComparator
;
46 import eu
.etaxonomy
.cdm
.compare
.taxon
.TaxonNodeByNameComparator
;
47 import eu
.etaxonomy
.cdm
.compare
.taxon
.TaxonNodeByRankAndNameComparator
;
48 import eu
.etaxonomy
.cdm
.compare
.taxon
.TaxonNodeNaturalComparator
;
49 import eu
.etaxonomy
.cdm
.hibernate
.search
.AcceptedTaxonBridge
;
50 import eu
.etaxonomy
.cdm
.hibernate
.search
.ClassInfoBridge
;
51 import eu
.etaxonomy
.cdm
.model
.common
.IIntextReferenceTarget
;
52 import eu
.etaxonomy
.cdm
.model
.common
.IPublishable
;
53 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
54 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
55 import eu
.etaxonomy
.cdm
.model
.name
.ITaxonNameBase
;
56 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
57 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
58 import eu
.etaxonomy
.cdm
.model
.reference
.OriginalSourceType
;
59 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
60 import eu
.etaxonomy
.cdm
.strategy
.cache
.TaggedText
;
61 import eu
.etaxonomy
.cdm
.strategy
.cache
.name
.CacheUpdate
;
62 import eu
.etaxonomy
.cdm
.strategy
.cache
.taxon
.ITaxonCacheStrategy
;
63 import eu
.etaxonomy
.cdm
.strategy
.cache
.taxon
.TaxonBaseDefaultCacheStrategy
;
64 import eu
.etaxonomy
.cdm
.validation
.Level2
;
65 import eu
.etaxonomy
.cdm
.validation
.Level3
;
66 import eu
.etaxonomy
.cdm
.validation
.annotation
.TaxonNameCannotBeAcceptedAndSynonym
;
69 * The upmost (abstract) class for the use of a {@link eu.etaxonomy.cdm.model.name.TaxonName taxon name} in a {@link eu.etaxonomy.cdm.model.reference.Reference reference}
70 * or within a taxonomic view/treatment either as a {@link Taxon taxon}
71 * ("accepted" respectively "correct" name) or as a (junior) {@link Synonym synonym}.
72 * Within a taxonomic view/treatment or a reference a taxon name can be used
73 * only in one of both described meanings. The reference using the taxon name
74 * is generally cited with "sec." (secundum, sensu). For instance:
75 * "<i>Juncus longirostris</i> Kuvaev sec. Kirschner, J. et al. 2002".
77 * This class corresponds to: <ul>
78 * <li> TaxonConcept according to the TDWG ontology
79 * <li> TaxonConcept according to the TCS
83 * @since 08-Nov-2007 13:06:56
85 @XmlAccessorType(XmlAccessType
.FIELD
)
86 @XmlType(name
= "TaxonBase", propOrder
= {
96 //@PreFilter("hasPermission(filterObject, 'edit')")
97 @Table(name
="TaxonBase", indexes
= { @Index(name
= "taxonBaseTitleCacheIndex", columnList
= "titleCache") })
98 @TaxonNameCannotBeAcceptedAndSynonym(groups
= Level3
.class)
100 @ClassBridge(name
="classInfo",
101 index
= org
.hibernate
.search
.annotations
.Index
.YES
,
103 impl
= ClassInfoBridge
.class),
104 @ClassBridge(name
=AcceptedTaxonBridge
.ACC_TAXON
, // TODO rename to acceptedTaxon, since we are usually not using abbreviations for field names, see also ACC_TAXON_BRIDGE_PREFIX
105 index
= org
.hibernate
.search
.annotations
.Index
.YES
,
107 impl
= AcceptedTaxonBridge
.class),
108 @ClassBridge(impl
= eu
.etaxonomy
.cdm
.hibernate
.search
.NomenclaturalSortOrderBrigde
.class)
110 public abstract class TaxonBase
<S
extends ITaxonCacheStrategy
>
111 extends IdentifiableEntity
<S
>
112 implements IPublishable
, IIntextReferenceTarget
{
114 private static final long serialVersionUID
= -3589185949928938529L;
115 private static final Logger logger
= LogManager
.getLogger(TaxonBase
.class);
117 public static final String ACC_TAXON_BRIDGE_PREFIX
= "accTaxon";
119 private static Method methodTaxonNameAddTaxonBase
;
123 methodTaxonNameAddTaxonBase
= TaxonName
.class.getDeclaredMethod("addTaxonBase", TaxonBase
.class);
124 methodTaxonNameAddTaxonBase
.setAccessible(true);
125 } catch (Exception e
) {
127 for(StackTraceElement ste
: e
.getStackTrace()) {
133 //The assignment to the Taxon or to the Synonym class is not definitive
134 @XmlAttribute(name
= "isDoubtful")
135 private boolean doubtful
;
137 @XmlElement(name
= "Name")
139 @XmlSchemaType(name
= "IDREF")
140 @ManyToOne(fetch
= FetchType
.LAZY
)
141 @IndexedEmbedded(includeEmbeddedObjectId
=true)
142 @Cascade({CascadeType
.SAVE_UPDATE
,CascadeType
.MERGE
})
143 @NotNull(groups
= Level2
.class)
144 private TaxonName name
;
147 @XmlElement(name
= "SecSource")
149 @XmlSchemaType(name
= "IDREF")
150 @OneToOne(fetch
= FetchType
.LAZY
, orphanRemoval
=true, mappedBy
="sourcedTaxon")
151 @Cascade({CascadeType
.SAVE_UPDATE
,CascadeType
.MERGE
,CascadeType
.DELETE
})
152 @CacheUpdate(noUpdate
="titleCache")
154 private SecundumSource secSource
;
156 @XmlElement(name
= "AppendedPhrase")
157 private String appendedPhrase
;
159 @XmlAttribute(name
= "UseNameCache")
160 private boolean useNameCache
= false;
162 @XmlAttribute(name
= "publish")
163 @Field(analyze
= Analyze
.NO
, store
= Store
.YES
, bridge
= @FieldBridge(impl
=BooleanBridge
.class))
164 private boolean publish
= true;
166 // ************* CONSTRUCTORS *************/
169 * Class constructor: creates a new empty (abstract) taxon.
171 * @see #TaxonBase(TaxonName, Reference)
173 protected TaxonBase(){
178 * Class constructor: creates a new (abstract) taxon with the
179 * {@link eu.etaxonomy.cdm.model.name.TaxonName taxon name} used and the {@link eu.etaxonomy.cdm.model.reference.Reference reference}
182 * @param taxonName the taxon name used
183 * @param sec the reference using the taxon name
186 protected TaxonBase(TaxonName taxonName
, Reference sec
, String secDetail
){
188 if (taxonName
!= null){
189 this.invokeSetMethod(methodTaxonNameAddTaxonBase
, taxonName
);
192 this.setSecMicroReference(secDetail
);
196 protected void initDefaultCacheStrategy() {
197 this.cacheStrategy
= (S
)new TaxonBaseDefaultCacheStrategy();
200 //********* METHODS **************************************/
203 public List
<TaggedText
> getTaggedTitle(){
204 return cacheStrategy().getTaggedTitle(this);
208 * Returns the {@link TaxonName taxon name} used in <i>this</i> (abstract) taxon.
210 public TaxonName
getName(){
214 public void setName(TaxonName name
) {
215 if (this.name
!= null){
216 this.name
.getTaxonBases().remove(this);
219 name
.getTaxonBases().add(this);
225 * Returns the {@link eu.etaxonomy.cdm.model.name.HomotypicalGroup homotypical group} of the
226 * {@link eu.etaxonomy.cdm.model.name.TaxonName taxon name} used in <i>this</i> (abstract) taxon.
229 public HomotypicalGroup
getHomotypicGroup(){
230 if (this.getName() == null){
233 return this.getName().getHomotypicalGroup();
238 * Returns the boolean value indicating whether the assignment of <i>this</i>
239 * (abstract) taxon to the {@link Taxon Taxon} or to the {@link Synonym Synonym} class
240 * is definitive (false) or not (true). If this flag is set the use of <i>this</i> (abstract)
241 * taxon as an "accepted/correct" name or as a (junior) "synonym" might
242 * still change in the course of taxonomical working process.
244 public boolean isDoubtful(){
245 return this.doubtful
;
250 public void setDoubtful(boolean doubtful
){
251 this.doubtful
= doubtful
;
255 * Returns the boolean value indicating if this taxon should be withheld (<code>publish=false</code>) or not
256 * (<code>publish=true</code>) during any publication process to the general public.
257 * This publish flag implementation is preliminary and may be replaced by a more general
258 * implementation of READ rights in future.<BR>
259 * The default value is <code>true</code>.
262 public boolean isPublish() {
266 public void setPublish(boolean publish
) {
267 this.publish
= publish
;
270 //*************** sec source *******************/
272 public SecundumSource
getSecSource(){
273 return this.secSource
;
276 protected SecundumSource
getSecSource(boolean createIfNotExist
){
277 if (this.secSource
== null && createIfNotExist
){
278 setSecSource(SecundumSource
.NewSecundumInstance(this));
283 public void setSecSource(SecundumSource secSource
) throws IllegalArgumentException
{
285 if (secSource
!= null && !OriginalSourceType
.PrimaryTaxonomicSource
.equals(secSource
.getType())){
286 throw new IllegalArgumentException("Secundum source must be of type " + OriginalSourceType
.PrimaryTaxonomicSource
.getLabel());
288 this.secSource
= secSource
;
289 if (secSource
!= null && secSource
.getSourcedTaxon() != this){
290 secSource
.setSourcedTaxon(this);
295 public Reference
getSec(){
296 return this.secSource
== null?
null:this.secSource
.getCitation();
300 public void setSec(Reference secReference
){
301 if (secReference
== null && this.getSecSource()==null){
304 getSecSource(true).setCitation(secReference
);
309 * Returns the details string of the {@link #getSec() sec reference} assigned
310 * to <i>this</i> taxon base. The details describe the exact localisation within
311 * the publication used as sec reference. These are mostly
312 * (implicitly) pages but can also be figures or tables or any other
313 * element of a publication. A sec micro reference (details)
314 * requires the existence of a sec reference.
317 public String
getSecMicroReference(){
318 return this.secSource
== null?
null: this.secSource
.getCitationMicroReference();
322 * @see #getSecMicroReference()
324 public void setSecMicroReference(String secMicroReference
){
325 secMicroReference
= isBlank(secMicroReference
)?
null : secMicroReference
;
326 if (secMicroReference
== null && this.getSecSource()==null){
329 this.getSecSource(true).setCitationMicroReference(secMicroReference
);
334 * Checks if the source is completely empty and if empty removes it from the name.
336 //TODO maybe this should be moved to a hibernate listener, but the listener solution may
337 //work only for sec single sources as they are the only which are bidirectional
338 protected void checkNullSource() {
339 if (this.secSource
!= null && this.secSource
.checkEmpty(true)){
340 this.secSource
= null;
344 // *********************************************
347 * An appended phrase is a phrase that is added to the {@link eu.etaxonomy.cdm.model.name.TaxonName taxon name}
348 * 's title cache to be used just in this taxon. E.g. the phrase "sensu latu" may be added
349 * to the name to describe this taxon more precisely.
350 * If {@link #isUseNameCache()}
351 * @return the appendedPhrase
353 public String
getAppendedPhrase() {
354 return appendedPhrase
;
358 * @param appendedPhrase the appendedPhrase to set
360 public void setAppendedPhrase(String appendedPhrase
) {
361 this.appendedPhrase
= appendedPhrase
;
365 * @return the useNameCache
367 public boolean isUseNameCache() {
372 * @param useNameCache the useNameCache to set
374 public void setUseNameCache(boolean useNameCache
) {
375 this.useNameCache
= useNameCache
;
379 * Returns <code>true</code> if <code>this</code>
380 * taxon base is not part of any classification.
385 public abstract boolean isOrphaned();
392 public Rank
getNullSafeRank() {
393 return name
== null ?
null : name
.getRank();
397 * This method compares 2 taxa on it's name titles and caches.
398 * If both are equal it compares on the secundum titleCache as well.
399 * It is not fully clear/defined how this method relates to
400 * explicit comparators like {@link TaxonComparator}. The later
401 * currently uses this method.
402 * Historically it was a compareTo method in {@link IdentifiableEntity}
403 * but did not fulfill the {@link Comparable} contract.
405 * {@link https://dev.e-taxonomy.eu/redmine/issues/922}<BR>
406 * {@link https://dev.e-taxonomy.eu/redmine/issues/6311}
408 * @see ITaxonNameBase#compareToName(TaxonName)
409 * @see TaxonComparator
410 * @see TaxonNodeNaturalComparator
411 * @see TaxonNodeByNameComparator
412 * @see TaxonNodeByRankAndNameComparator
414 * @return the compareTo result similar to {@link Comparable#compareTo(Object)}
415 * @throws NullPointerException if otherTaxon is <code>null</code>
417 //TODO handling of protected titleCache
418 public int compareToTaxon(TaxonBase otherTaxon
){
422 if (otherTaxon
== null) {
423 throw new NullPointerException("Cannot compare to null.");
426 otherTaxon
= deproxy(otherTaxon
);
428 TaxonName otherName
= deproxy(otherTaxon
.getName());
429 ITaxonNameBase thisName
= this.getName();
430 if ((otherName
== null || thisName
== null)){
431 if (otherName
!= thisName
){
432 result
= thisName
== null ?
-1 : 1;
435 result
= thisName
.compareToName(otherName
);
439 String otherReferenceTitleCache
= "";
440 String thisReferenceTitleCache
= "";
442 Reference otherRef
= deproxy(otherTaxon
.getSec());
443 if (otherRef
!= null) {
444 otherReferenceTitleCache
= otherRef
.getTitleCache();
446 Reference thisRef
= deproxy(this.getSec());
447 if (thisRef
!= null) {
448 thisReferenceTitleCache
= thisRef
.getTitleCache();
450 if ((CdmUtils
.isNotBlank(otherReferenceTitleCache
) || CdmUtils
.isNotBlank(thisReferenceTitleCache
))) {
451 result
= thisReferenceTitleCache
.compareTo(otherReferenceTitleCache
);
458 //*********************** CLONE ********************************************************/
461 * Clones <i>this</i> taxon. This is a shortcut that enables to create
462 * a new instance with exactly the same taxon name and sec reference.
464 * @see java.lang.Object#clone()
467 public TaxonBase
<S
> clone() {
470 result
= (TaxonBase
<S
>)super.clone();
471 if (this.getSecSource() != null){
472 result
.setSecSource(this.getSecSource().clone());
473 result
.getSecSource().setSourcedTaxon(result
);
476 } catch (CloneNotSupportedException e
) {
477 logger
.warn("Object does not implement cloneable");