fixing probelms related to bean initialization
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / taxon / TaxonNode.java
1 // $Id$
2 /**
3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10
11 package eu.etaxonomy.cdm.model.taxon;
12
13 import java.util.ArrayList;
14 import java.util.HashSet;
15 import java.util.List;
16 import java.util.Set;
17
18 import javax.persistence.Entity;
19 import javax.persistence.FetchType;
20 import javax.persistence.ManyToOne;
21 import javax.persistence.OneToMany;
22 import javax.persistence.OrderBy;
23 import javax.persistence.OrderColumn;
24 import javax.persistence.Transient;
25 import javax.validation.constraints.Size;
26 import javax.xml.bind.annotation.XmlAccessType;
27 import javax.xml.bind.annotation.XmlAccessorType;
28 import javax.xml.bind.annotation.XmlElement;
29 import javax.xml.bind.annotation.XmlElementWrapper;
30 import javax.xml.bind.annotation.XmlIDREF;
31 import javax.xml.bind.annotation.XmlRootElement;
32 import javax.xml.bind.annotation.XmlSchemaType;
33 import javax.xml.bind.annotation.XmlType;
34
35 import org.apache.log4j.Logger;
36 import org.hibernate.annotations.Cascade;
37 import org.hibernate.annotations.CascadeType;
38 import org.hibernate.envers.Audited;
39 import org.hibernate.search.annotations.ContainedIn;
40 import org.hibernate.search.annotations.Indexed;
41 import org.hibernate.search.annotations.IndexedEmbedded;
42
43 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
44 import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
45 import eu.etaxonomy.cdm.model.common.ITreeNode;
46 import eu.etaxonomy.cdm.model.reference.Reference;
47
48 /**
49 * @author a.mueller
50 * @created 31.03.2009
51 */
52 @XmlAccessorType(XmlAccessType.FIELD)
53 @XmlType(name = "TaxonNode", propOrder = {
54 "classification",
55 "taxon",
56 "parent",
57 "treeIndex",
58 "sortIndex",
59 "childNodes",
60 "referenceForParentChildRelation",
61 "microReferenceForParentChildRelation",
62 "countChildren",
63 "synonymToBeUsed"
64 })
65 @XmlRootElement(name = "TaxonNode")
66 @Entity
67 @Indexed(index = "eu.etaxonomy.cdm.model.taxon.TaxonNode")
68 @Audited
69 public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITreeNode<TaxonNode>, Cloneable{
70 private static final long serialVersionUID = -4743289894926587693L;
71 private static final Logger logger = Logger.getLogger(TaxonNode.class);
72
73 @XmlElement(name = "taxon")
74 @XmlIDREF
75 @XmlSchemaType(name = "IDREF")
76 @ManyToOne(fetch = FetchType.LAZY)
77 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
78 @ContainedIn
79 private Taxon taxon;
80
81
82 @XmlElement(name = "parent")
83 @XmlIDREF
84 @XmlSchemaType(name = "IDREF")
85 @ManyToOne(fetch = FetchType.LAZY)
86 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
87 private TaxonNode parent;
88
89
90 @XmlElement(name = "treeIndex")
91 @Size(max=255)
92 private String treeIndex;
93
94
95 @XmlElement(name = "classification")
96 @XmlIDREF
97 @XmlSchemaType(name = "IDREF")
98 @ManyToOne(fetch = FetchType.LAZY)
99 @Cascade({CascadeType.SAVE_UPDATE})
100 // TODO @NotNull // avoids creating a UNIQUE key for this field
101 @IndexedEmbedded
102 private Classification classification;
103
104 @XmlElementWrapper(name = "childNodes")
105 @XmlElement(name = "childNode")
106 @XmlIDREF
107 @XmlSchemaType(name = "IDREF")
108 //see https://dev.e-taxonomy.eu/trac/ticket/3722
109 @OrderColumn(name="sortIndex")
110 @OrderBy("sortIndex")
111 @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
112 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
113 private List<TaxonNode> childNodes = new ArrayList<TaxonNode>();
114
115 //see https://dev.e-taxonomy.eu/trac/ticket/3722
116 private Integer sortIndex = -1;
117
118 @XmlElement(name = "reference")
119 @XmlIDREF
120 @XmlSchemaType(name = "IDREF")
121 @ManyToOne(fetch = FetchType.LAZY)
122 @Cascade({CascadeType.SAVE_UPDATE})
123 private Reference<?> referenceForParentChildRelation;
124
125 @XmlElement(name = "microReference")
126 private String microReferenceForParentChildRelation;
127
128 @XmlElement(name = "countChildren")
129 private int countChildren;
130
131 // private Taxon originalConcept;
132 // //or
133 @XmlElement(name = "synonymToBeUsed")
134 @XmlIDREF
135 @XmlSchemaType(name = "IDREF")
136 @ManyToOne(fetch = FetchType.LAZY)
137 @Cascade({CascadeType.SAVE_UPDATE})
138 private Synonym synonymToBeUsed;
139
140
141 protected TaxonNode(){
142 super();
143 }
144
145 /**
146 * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
147 * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
148 * @param taxon
149 * @param classification
150 * @deprecated setting of classification is handled in the addTaxonNode() method,
151 * use TaxonNode(taxon) instead
152 */
153 @Deprecated
154 protected TaxonNode (Taxon taxon, Classification classification){
155 this(taxon);
156 setClassification(classification);
157 }
158
159 /**
160 * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
161 * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
162 *
163 * @param taxon
164 */
165 protected TaxonNode(Taxon taxon){
166 setTaxon(taxon);
167 }
168
169
170
171 //************************ METHODS **************************/
172
173 @Override
174 public TaxonNode addChildTaxon(Taxon taxon, Reference citation, String microCitation) {
175 return addChildTaxon(taxon, this.childNodes.size(), citation, microCitation);
176
177 }
178
179
180 @Override
181 public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
182 if (this.getClassification().isTaxonInTree(taxon)){
183 throw new IllegalArgumentException(String.format("Taxon may not be in a taxonomic view twice: %s", taxon.getTitleCache()));
184 }
185
186 return addChildNode(new TaxonNode(taxon), index, citation, microCitation);
187 }
188
189 /**
190 * Moves a taxon node to a new parent. Descendents of the node are moved as well
191 *
192 * @param childNode the taxon node to be moved to the new parent
193 * @return the child node in the state of having a new parent
194 */
195 @Override
196 public TaxonNode addChildNode(TaxonNode childNode, Reference reference, String microReference){
197
198 addChildNode(childNode, childNodes.size(), reference, microReference);
199 return childNode;
200 }
201
202 /**
203 * Inserts the given taxon node in the list of children of <i>this</i> taxon node
204 * at the given (index + 1) position. If the given index is out of bounds
205 * an exception will arise.<BR>
206 * Due to bidirectionality this method must also assign <i>this</i> taxon node
207 * as the parent of the given child.
208 *
209 * @param child the taxon node to be added
210 * @param index the integer indicating the position at which the child
211 * should be added
212 * @see #getChildNodes()
213 * @see #addChildNode(TaxonNode, Reference, String, Synonym)
214 * @see #deleteChildNode(TaxonNode)
215 * @see #deleteChildNode(int)
216 */
217 @Override
218 public TaxonNode addChildNode(TaxonNode child, int index, Reference reference, String microReference){
219 if (index < 0 || index > childNodes.size() + 1){
220 throw new IndexOutOfBoundsException("Wrong index: " + index);
221 }
222 // check if this node is a descendant of the childNode
223 if(child.getParentTreeNode() != this && child.isAncestor(this)){
224 throw new IllegalAncestryException("New parent node is a descendant of the node to be moved.");
225 }
226
227
228 // if (child.getParent() != null){
229 // child.getParent().deleteChildNode(child);
230 // }
231
232 child.setParentTreeNode(this, index);
233
234 //TODO workaround (see sortIndex doc) => not really required anymore here, as it is done in child.setParentTreeNode already
235 for(int i = 0; i < childNodes.size(); i++){
236 childNodes.get(i).sortIndex = i;
237 }
238 child.sortIndex = index;
239
240 //TODO workaround (see sortIndex doc)
241 for(int i = 0; i < childNodes.size(); i++){
242 childNodes.get(i).sortIndex = i;
243 }
244 child.sortIndex = index;
245
246 child.setReference(reference);
247 child.setMicroReference(microReference);
248
249 return child;
250
251 }
252
253 /**
254 * Sets this nodes classification. Updates classification of child nodes recursively
255 *
256 * If the former and the actual tree are equal() this method does nothing
257 *
258 * @param newTree
259 */
260 @Transient
261 private void setClassificationRecursively(Classification newTree) {
262 if(! newTree.equals(this.getClassification())){
263 this.setClassification(newTree);
264 for(TaxonNode childNode : this.getChildNodes()){
265 childNode.setClassificationRecursively(newTree);
266 }
267 }
268 }
269
270 @Override
271 public boolean deleteChildNode(TaxonNode node) {
272 boolean result = removeChildNode(node);
273 Taxon taxon = node.getTaxon();
274 node.setTaxon(null);
275 taxon.removeTaxonNode(node);
276
277
278 ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
279 for(TaxonNode childNode : childNodes){
280 node.deleteChildNode(childNode);
281 }
282
283 // // two iterations because of ConcurrentModificationErrors
284 // Set<TaxonNode> removeNodes = new HashSet<TaxonNode>();
285 // for (TaxonNode grandChildNode : node.getChildNodes()) {
286 // removeNodes.add(grandChildNode);
287 // }
288 // for (TaxonNode childNode : removeNodes) {
289 // childNode.deleteChildNode(node);
290 // }
291
292 return result;
293 }
294
295 /* (non-Javadoc)
296 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#removeChildNode(eu.etaxonomy.cdm.model.taxon.TaxonNode)
297 */
298 public boolean deleteChildNode(TaxonNode node, boolean deleteChildren) {
299 boolean result = removeChildNode(node);
300 Taxon taxon = node.getTaxon();
301 node.setTaxon(null);
302 taxon.removeTaxonNode(node);
303 if (deleteChildren){
304 ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
305 for(TaxonNode childNode : childNodes){
306 node.deleteChildNode(childNode, deleteChildren);
307 }
308 }
309
310 // // two iterations because of ConcurrentModificationErrors
311 // Set<TaxonNode> removeNodes = new HashSet<TaxonNode>();
312 // for (TaxonNode grandChildNode : node.getChildNodes()) {
313 // removeNodes.add(grandChildNode);
314 // }
315 // for (TaxonNode childNode : removeNodes) {
316 // childNode.deleteChildNode(node);
317 // }
318
319 return result;
320 }
321
322 /**
323 * Removes the child node from this node. Sets the parent and the classification of the child
324 * node to null
325 *
326 * @param childNode
327 * @return
328 */
329 protected boolean removeChildNode(TaxonNode childNode){
330 boolean result = true;
331
332 if(childNode == null){
333 throw new IllegalArgumentException("TaxonNode may not be null");
334 }
335 int index = childNodes.indexOf(childNode);
336 if (index >= 0){
337 removeChild(index);
338 } else {
339 result = false;
340 }
341
342
343 // OLD
344 // if(HibernateProxyHelper.deproxy(childNode.getParent(), TaxonNode.class) != this){
345 // throw new IllegalArgumentException("TaxonNode must be a child of this node");
346 // }
347 //
348 // result = childNodes.remove(childNode);
349 // this.countChildren--;
350 // if (this.countChildren < 0){
351 // throw new IllegalStateException("Children count must not be negative ");
352 // }
353 // childNode.setParent(null);
354 // childNode.setClassification(null);
355
356 return result;
357 }
358
359 /**
360 * Removes the child node placed at the given (index + 1) position
361 * from the list of {@link #getChildNodes() children} of <i>this</i> taxon node.
362 * Sets the parent and the classification of the child
363 * node to null.
364 * If the given index is out of bounds no child will be removed.
365 *
366 * @param index the integer indicating the position of the taxon node to
367 * be removed
368 * @see #getChildNodes()
369 * @see #addChildNode(TaxonNode, Reference, String)
370 * @see #addChildNode(TaxonNode, int, Reference, String)
371 * @see #deleteChildNode(TaxonNode)
372 */
373 public void removeChild(int index){
374
375 TaxonNode child = childNodes.get(index);
376 child = HibernateProxyHelper.deproxy(child, TaxonNode.class); //strange that this is required, but otherwise child.getParent() returns null for some lazy-loaded items.
377
378 if (child != null){
379
380 TaxonNode parent = HibernateProxyHelper.deproxy(child.getParent(), TaxonNode.class);
381 TaxonNode thisNode = HibernateProxyHelper.deproxy(this, TaxonNode.class);
382 if(parent != null && parent != thisNode){
383 throw new IllegalArgumentException("Child TaxonNode (id:" + child.getId() +") must be a child of this (id:" + thisNode.getId() + " node. Sortindex is: " + index + ", parent-id:" + parent.getId());
384 }else if (parent == null){
385 throw new IllegalStateException("Parent of child is null in TaxonNode.removeChild(int). This should not happen.");
386 }
387 childNodes.remove(index);
388 this.countChildren = childNodes.size();
389 child.setParent(null);
390 //TODO workaround (see sortIndex doc)
391 for(int i = 0; i < countChildren; i++){
392 TaxonNode childAt = childNodes.get(i);
393 childAt.sortIndex = i;
394 }
395 child.sortIndex = null;
396 child.setClassification(null);
397 }
398 }
399
400
401 /**
402 * Remove this taxonNode From its taxonomic parent
403 *
404 * @return true on success
405 */
406 public boolean delete(){
407 if(isTopmostNode()){
408 return classification.deleteChildNode(this);
409 }else{
410 return getParent().deleteChildNode(this);
411 }
412 }
413
414 /**
415 * Remove this taxonNode From its taxonomic parent
416 *
417 * @return true on success
418 */
419 public boolean delete(boolean deleteChildren){
420 if(isTopmostNode()){
421 return classification.deleteChildNode(this, deleteChildren);
422 }else{
423 return getParent().deleteChildNode(this, deleteChildren);
424 }
425 }
426
427 //*********** GETTER / SETTER ***********************************/
428
429 @Override
430 public String treeIndex() {
431 return treeIndex;
432 }
433
434 @Override
435 @Deprecated //for CDM lib internal use only, may be removed in future versions
436 public void setTreeIndex(String treeIndex) {
437 this.treeIndex = treeIndex;
438 }
439
440
441 @Override
442 @Deprecated //for CDM lib internal use only, may be removed in future versions
443 public int treeId() {
444 return this.classification.getId();
445 }
446
447 public Taxon getTaxon() {
448 return taxon;
449 }
450 protected void setTaxon(Taxon taxon) {
451 this.taxon = taxon;
452 if (taxon != null){
453 taxon.addTaxonNode(this);
454 }
455 }
456 @Transient
457 public ITaxonTreeNode getParentTreeNode() {
458 if(isTopmostNode()){
459 return getClassification();
460 }
461 return parent;
462 }
463
464 @Override
465 public TaxonNode getParent(){
466 return parent;
467 }
468
469 /**
470 * Sets the parent of this taxon node.
471 *
472 * In most cases you would want to call setParentTreeNode(ITreeNode) which
473 * handles updating of the bidirectional relationship
474 *
475 * @param parent
476 *
477 * @see setParentTreeNode(ITreeNode)
478 */
479 protected void setParent(ITaxonTreeNode parent) {
480 if(parent instanceof Classification){
481 this.parent = null;
482 return;
483 }
484 this.parent = (TaxonNode) parent;
485 }
486
487 /**
488 * Sets the parent of this taxon node to the given parent. Cleans up references to
489 * old parents and sets the classification to the new parents classification
490 *
491 * @param parent
492 */
493 @Transient
494 protected void setParentTreeNode(ITaxonTreeNode parent, int index){
495 // remove ourselves from the old parent
496 ITaxonTreeNode formerParent = this.getParentTreeNode();
497
498 //special case, child already exists for same parent
499 if (formerParent != null && formerParent.equals(parent)){
500 int currentIndex = formerParent.getChildNodes().indexOf(this);
501 if (currentIndex != -1 && currentIndex < index){
502 index--;
503 }
504 }
505 //remove old parent
506 if(formerParent instanceof TaxonNode){ //child was a child itself
507 ((TaxonNode) formerParent).removeChildNode(this);
508 } else if((formerParent instanceof Classification) && ! formerParent.equals(parent)){ //child was root in old tree
509 ((Classification) formerParent).removeChildNode(this);
510 }
511
512 // set the new parent
513 setParent(parent);
514
515 // set the classification to the parents classification
516 Classification classification = (parent instanceof Classification) ? (Classification) parent : ((TaxonNode) parent).getClassification();
517 setClassificationRecursively(classification);
518
519 // add this node to the parent child nodes
520 List<TaxonNode> parentChildren = parent.getChildNodes();
521 if (parentChildren.contains(this)){
522 //avoid duplicates
523 if (parentChildren.indexOf(this) < index){
524 index = index -1;
525 }
526 parentChildren.remove(this);
527 parentChildren.add(index, this);
528 }else{
529 parentChildren.add(index, this);
530 }
531
532 //TODO workaround (see sortIndex doc)
533 //TODO check if it is correct to use the parentChildren here
534 for(int i = 0; i < parentChildren.size(); i++){
535 parentChildren.get(i).sortIndex = i;
536 }
537 // this.sortIndex = index;
538
539 // update the children count
540 if(parent instanceof TaxonNode){
541 TaxonNode parentTaxonNode = (TaxonNode) parent;
542 parentTaxonNode.setCountChildren(parent.getChildNodes().size());
543 }
544 }
545
546 public Classification getClassification() {
547 return classification;
548 }
549 /**
550 * THIS METHOD SHOULD NOT BE CALLED!
551 * invisible part of the bidirectional relationship, for public use TaxonomicView.addRoot() or TaxonNode.addChild()
552 * @param classification
553 */
554 protected void setClassification(Classification classification) {
555 this.classification = classification;
556 }
557
558 @Override
559 public List<TaxonNode> getChildNodes() {
560 return childNodes;
561 }
562
563 /**
564 * Returns a set containing this node and all nodes that are descendants of this node
565 *
566 * @return
567 */
568 protected Set<TaxonNode> getDescendants(){
569 Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
570
571 nodeSet.add(this);
572
573 for(TaxonNode childNode : getChildNodes()){
574 nodeSet.addAll(childNode.getDescendants());
575 }
576
577 return nodeSet;
578 }
579
580 /**
581 * Returns a set containing a clone of this node and of all nodes that are descendants of this node
582 *
583 * @return
584 */
585 protected TaxonNode cloneDescendants(){
586
587 TaxonNode clone = (TaxonNode)this.clone();
588 TaxonNode childClone;
589
590 for(TaxonNode childNode : getChildNodes()){
591 childClone = (TaxonNode) childNode.clone();
592 for (TaxonNode childChild:childNode.getChildNodes()){
593 childClone.addChildNode(childChild.cloneDescendants(), childChild.getReference(), childChild.getMicroReference());
594 }
595 clone.addChildNode(childClone, childNode.getReference(), childNode.getMicroReference());
596
597
598 //childClone.addChildNode(childNode.cloneDescendants());
599 }
600 return clone;
601 }
602
603 /**
604 * Returns a
605 *
606 * @return
607 */
608 protected Set<TaxonNode> getAncestors(){
609 Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
610 nodeSet.add(this);
611 if(this.getParent() != null){
612 nodeSet.addAll(this.getParent().getAncestors());
613 }
614 return nodeSet;
615 }
616
617
618 @Override
619 public Reference getReference() {
620 return referenceForParentChildRelation;
621 }
622
623 /* (non-Javadoc)
624 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#setReference(eu.etaxonomy.cdm.model.reference.Reference)
625 */
626 public void setReference(Reference reference) {
627 this.referenceForParentChildRelation = reference;
628 }
629
630
631 @Override
632 public String getMicroReference() {
633 return microReferenceForParentChildRelation;
634 }
635
636 /* (non-Javadoc)
637 * @see eu.etaxonomy.cdm.model.taxon.ITreeNode#setMicroReference(java.lang.String)
638 */
639 public void setMicroReference(String microReference) {
640 this.microReferenceForParentChildRelation = microReference;
641 }
642
643 /**
644 * @return the count of children this taxon node has
645 */
646 public int getCountChildren() {
647 return countChildren;
648 }
649
650 /**
651 * @param countChildren
652 */
653 protected void setCountChildren(int countChildren) {
654 this.countChildren = countChildren;
655 }
656
657 public Synonym getSynonymToBeUsed() {
658 return synonymToBeUsed;
659 }
660 public void setSynonymToBeUsed(Synonym synonymToBeUsed) {
661 this.synonymToBeUsed = synonymToBeUsed;
662 }
663
664 /**
665 * Whether this TaxonNode is a direct child of the classification TreeNode
666 * @return
667 */
668 @Transient
669 public boolean isTopmostNode(){
670 return parent == null;
671 }
672
673 /**
674 * Whether this TaxonNode is a descendant of the given TaxonNode
675 *
676 * Caution: use this method with care on big branches. -> performance and memory hungry
677 *
678 * Protip: Try solving your problem with the isAscendant method which traverses the tree in the
679 * other direction (up). It will always result in a rather small set of consecutive parents beeing
680 * generated.
681 *
682 * TODO implement more efficiently without generating the set of descendants first
683 *
684 * @param possibleParent
685 * @return true if this is a descendant
686 */
687 @Transient
688 public boolean isDescendant(TaxonNode possibleParent){
689 return possibleParent.getDescendants().contains(this);
690 }
691
692 /**
693 * Whether this TaxonNode is an ascendant of the given TaxonNode
694 *
695 *
696 * @param possibleChild
697 * @return true if there are ascendants
698 */
699 @Transient
700 public boolean isAncestor(TaxonNode possibleChild){
701 return possibleChild.getAncestors().contains(this);
702 }
703
704 /**
705 * Whether this taxon has child nodes
706 *
707 * @return true if the taxonNode has childNodes
708 */
709 @Transient
710 @Override
711 public boolean hasChildNodes(){
712 return childNodes.size() > 0;
713 }
714
715 //*********************** CLONE ********************************************************/
716 /**
717 * Clones <i>this</i> taxon node. This is a shortcut that enables to create
718 * a new instance that differs only slightly from <i>this</i> taxon node by
719 * modifying only some of the attributes.<BR><BR>
720 * The child nodes are not copied.<BR>
721 * The taxon and parent are the same as for the original taxon node. <BR>
722 *
723 * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
724 * @see java.lang.Object#clone()
725 */
726 @Override
727 public Object clone() {
728 TaxonNode result;
729 try{
730 result = (TaxonNode)super.clone();
731 result.getTaxon().addTaxonNode(result);
732 result.childNodes = new ArrayList<TaxonNode>();
733 result.countChildren = 0;
734
735 return result;
736 }catch (CloneNotSupportedException e) {
737 logger.warn("Object does not implement cloneable");
738 e.printStackTrace();
739 return null;
740 }
741 }
742
743
744
745 }