ref #9359 upgrade cdmlib to log4j 2
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / taxon / TaxonBase.java
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.taxon;
11
12 import java.lang.reflect.Method;
13 import java.util.List;
14
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;
30
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;
43
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;
67
68 /**
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".
76 * <P>
77 * This class corresponds to: <ul>
78 * <li> TaxonConcept according to the TDWG ontology
79 * <li> TaxonConcept according to the TCS
80 * </ul>
81 *
82 * @author m.doering
83 * @since 08-Nov-2007 13:06:56
84 */
85 @XmlAccessorType(XmlAccessType.FIELD)
86 @XmlType(name = "TaxonBase", propOrder = {
87 "name",
88 "secSource",
89 "doubtful",
90 "appendedPhrase",
91 "useNameCache",
92 "publish"
93 })
94 @Entity
95 @Audited
96 //@PreFilter("hasPermission(filterObject, 'edit')")
97 @Table(name="TaxonBase", indexes = { @Index(name = "taxonBaseTitleCacheIndex", columnList = "titleCache") })
98 @TaxonNameCannotBeAcceptedAndSynonym(groups = Level3.class)
99 @ClassBridges({
100 @ClassBridge(name="classInfo",
101 index = org.hibernate.search.annotations.Index.YES,
102 store = Store.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,
106 store = Store.YES,
107 impl = AcceptedTaxonBridge.class),
108 @ClassBridge(impl = eu.etaxonomy.cdm.hibernate.search.NomenclaturalSortOrderBrigde.class)
109 })
110 public abstract class TaxonBase<S extends ITaxonCacheStrategy>
111 extends IdentifiableEntity<S>
112 implements IPublishable, IIntextReferenceTarget{
113
114 private static final long serialVersionUID = -3589185949928938529L;
115 private static final Logger logger = LogManager.getLogger(TaxonBase.class);
116
117 public static final String ACC_TAXON_BRIDGE_PREFIX = "accTaxon";
118
119 private static Method methodTaxonNameAddTaxonBase;
120
121 static {
122 try {
123 methodTaxonNameAddTaxonBase = TaxonName.class.getDeclaredMethod("addTaxonBase", TaxonBase.class);
124 methodTaxonNameAddTaxonBase.setAccessible(true);
125 } catch (Exception e) {
126 logger.error(e);
127 for(StackTraceElement ste : e.getStackTrace()) {
128 logger.error(ste);
129 }
130 }
131 }
132
133 //The assignment to the Taxon or to the Synonym class is not definitive
134 @XmlAttribute(name = "isDoubtful")
135 private boolean doubtful;
136
137 @XmlElement(name = "Name")
138 @XmlIDREF
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;
145
146 //#9327
147 @XmlElement(name = "SecSource")
148 @XmlIDREF
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")
153 @IndexedEmbedded
154 private SecundumSource secSource;
155
156 @XmlElement(name = "AppendedPhrase")
157 private String appendedPhrase;
158
159 @XmlAttribute(name= "UseNameCache")
160 private boolean useNameCache = false;
161
162 @XmlAttribute(name = "publish")
163 @Field(analyze = Analyze.NO, store = Store.YES, bridge= @FieldBridge(impl=BooleanBridge.class))
164 private boolean publish = true;
165
166 // ************* CONSTRUCTORS *************/
167
168 /**
169 * Class constructor: creates a new empty (abstract) taxon.
170 *
171 * @see #TaxonBase(TaxonName, Reference)
172 */
173 protected TaxonBase(){
174 super();
175 }
176
177 /**
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}
180 * using it.
181 *
182 * @param taxonName the taxon name used
183 * @param sec the reference using the taxon name
184 * @see #TaxonBase()
185 */
186 protected TaxonBase(TaxonName taxonName, Reference sec, String secDetail){
187 super();
188 if (taxonName != null){
189 this.invokeSetMethod(methodTaxonNameAddTaxonBase, taxonName);
190 }
191 this.setSec(sec);
192 this.setSecMicroReference(secDetail);
193 }
194
195 @Override
196 protected void initDefaultCacheStrategy() {
197 this.cacheStrategy = (S)new TaxonBaseDefaultCacheStrategy();
198 }
199
200 //********* METHODS **************************************/
201
202 @Transient
203 public List<TaggedText> getTaggedTitle(){
204 return cacheStrategy().getTaggedTitle(this);
205 }
206
207 /**
208 * Returns the {@link TaxonName taxon name} used in <i>this</i> (abstract) taxon.
209 */
210 public TaxonName getName(){
211 return this.name;
212 }
213
214 public void setName(TaxonName name) {
215 if (this.name != null){
216 this.name.getTaxonBases().remove(this);
217 }
218 if(name != null) {
219 name.getTaxonBases().add(this);
220 }
221 this.name = name;
222 }
223
224 /**
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.
227 */
228 @Transient
229 public HomotypicalGroup getHomotypicGroup(){
230 if (this.getName() == null){
231 return null;
232 }else{
233 return this.getName().getHomotypicalGroup();
234 }
235 }
236
237 /**
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.
243 */
244 public boolean isDoubtful(){
245 return this.doubtful;
246 }
247 /**
248 * @see #isDoubtful()
249 */
250 public void setDoubtful(boolean doubtful){
251 this.doubtful = doubtful;
252 }
253
254 /**
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>.
260 */
261 @Override
262 public boolean isPublish() {
263 return publish;
264 }
265 @Override
266 public void setPublish(boolean publish) {
267 this.publish = publish;
268 }
269
270 //*************** sec source *******************/
271
272 public SecundumSource getSecSource(){
273 return this.secSource;
274 }
275
276 protected SecundumSource getSecSource(boolean createIfNotExist){
277 if (this.secSource == null && createIfNotExist){
278 setSecSource(SecundumSource.NewSecundumInstance(this));
279 }
280 return secSource;
281 }
282
283 public void setSecSource(SecundumSource secSource) throws IllegalArgumentException {
284 //check state
285 if (secSource != null && !OriginalSourceType.PrimaryTaxonomicSource.equals(secSource.getType())){
286 throw new IllegalArgumentException("Secundum source must be of type " + OriginalSourceType.PrimaryTaxonomicSource.getLabel());
287 }
288 this.secSource = secSource;
289 if (secSource != null && secSource.getSourcedTaxon() != this){
290 secSource.setSourcedTaxon(this);
291 }
292 }
293
294 @Transient
295 public Reference getSec(){
296 return this.secSource == null? null:this.secSource.getCitation();
297 }
298
299 @Transient
300 public void setSec(Reference secReference){
301 if (secReference == null && this.getSecSource()==null){
302 return;
303 }else{
304 getSecSource(true).setCitation(secReference);
305 }
306 }
307
308 /**
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.
315 */
316 @Transient
317 public String getSecMicroReference(){
318 return this.secSource == null? null: this.secSource.getCitationMicroReference();
319 }
320
321 /**
322 * @see #getSecMicroReference()
323 */
324 public void setSecMicroReference(String secMicroReference){
325 secMicroReference = isBlank(secMicroReference)? null : secMicroReference;
326 if (secMicroReference == null && this.getSecSource()==null){
327 return;
328 }else{
329 this.getSecSource(true).setCitationMicroReference(secMicroReference);
330 }
331 }
332
333 /**
334 * Checks if the source is completely empty and if empty removes it from the name.
335 */
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;
341 }
342 }
343
344 // *********************************************
345
346 /**
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
352 */
353 public String getAppendedPhrase() {
354 return appendedPhrase;
355 }
356
357 /**
358 * @param appendedPhrase the appendedPhrase to set
359 */
360 public void setAppendedPhrase(String appendedPhrase) {
361 this.appendedPhrase = appendedPhrase;
362 }
363
364 /**
365 * @return the useNameCache
366 */
367 public boolean isUseNameCache() {
368 return useNameCache;
369 }
370
371 /**
372 * @param useNameCache the useNameCache to set
373 */
374 public void setUseNameCache(boolean useNameCache) {
375 this.useNameCache = useNameCache;
376 }
377
378 /**
379 * Returns <code>true</code> if <code>this</code>
380 * taxon base is not part of any classification.
381 * False otherwise
382 * @return boolean
383 */
384 @Transient
385 public abstract boolean isOrphaned();
386
387
388 /**
389 * @return
390 */
391 @Transient
392 public Rank getNullSafeRank() {
393 return name == null ? null : name.getRank();
394 }
395
396 /**
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.
404 * <BR><BR>
405 * {@link https://dev.e-taxonomy.eu/redmine/issues/922}<BR>
406 * {@link https://dev.e-taxonomy.eu/redmine/issues/6311}
407 *
408 * @see ITaxonNameBase#compareToName(TaxonName)
409 * @see TaxonComparator
410 * @see TaxonNodeNaturalComparator
411 * @see TaxonNodeByNameComparator
412 * @see TaxonNodeByRankAndNameComparator
413 * @param otherTaxon
414 * @return the compareTo result similar to {@link Comparable#compareTo(Object)}
415 * @throws NullPointerException if otherTaxon is <code>null</code>
416 */
417 //TODO handling of protected titleCache
418 public int compareToTaxon(TaxonBase otherTaxon){
419
420 int result = 0;
421
422 if (otherTaxon == null) {
423 throw new NullPointerException("Cannot compare to null.");
424 }
425
426 otherTaxon = deproxy(otherTaxon);
427
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;
433 }
434 }else{
435 result = thisName.compareToName(otherName);
436 }
437
438 if (result == 0){
439 String otherReferenceTitleCache = "";
440 String thisReferenceTitleCache = "";
441
442 Reference otherRef = deproxy(otherTaxon.getSec());
443 if (otherRef != null) {
444 otherReferenceTitleCache = otherRef.getTitleCache();
445 }
446 Reference thisRef = deproxy(this.getSec());
447 if (thisRef != null) {
448 thisReferenceTitleCache = thisRef.getTitleCache();
449 }
450 if ((CdmUtils.isNotBlank(otherReferenceTitleCache) || CdmUtils.isNotBlank(thisReferenceTitleCache))) {
451 result = thisReferenceTitleCache.compareTo(otherReferenceTitleCache);
452 }
453 }
454
455 return result;
456 }
457
458 //*********************** CLONE ********************************************************/
459
460 /**
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.
463 *
464 * @see java.lang.Object#clone()
465 */
466 @Override
467 public TaxonBase<S> clone() {
468 TaxonBase<S> result;
469 try {
470 result = (TaxonBase<S>)super.clone();
471 if (this.getSecSource() != null){
472 result.setSecSource(this.getSecSource().clone());
473 result.getSecSource().setSourcedTaxon(result);
474 }
475 return result;
476 } catch (CloneNotSupportedException e) {
477 logger.warn("Object does not implement cloneable");
478 e.printStackTrace();
479 return null;
480 }
481 }
482 }