ref #3560 implement publish filter for TaxonNodeDao.listChildrenOf and .countChildrenOf
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / ClassificationServiceImpl.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.api.service;
11
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.Comparator;
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.TreeMap;
23 import java.util.UUID;
24
25 import javax.persistence.EntityNotFoundException;
26
27 import org.apache.commons.collections.CollectionUtils;
28 import org.apache.commons.lang.StringUtils;
29 import org.apache.log4j.Logger;
30 import org.springframework.beans.factory.annotation.Autowired;
31 import org.springframework.stereotype.Service;
32 import org.springframework.transaction.annotation.Transactional;
33
34 import eu.etaxonomy.cdm.api.service.config.CreateHierarchyForClassificationConfigurator;
35 import eu.etaxonomy.cdm.api.service.config.NodeDeletionConfigurator.ChildHandling;
36 import eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator;
37 import eu.etaxonomy.cdm.api.service.dto.EntityDTO;
38 import eu.etaxonomy.cdm.api.service.dto.GroupedTaxonDTO;
39 import eu.etaxonomy.cdm.api.service.dto.MarkedEntityDTO;
40 import eu.etaxonomy.cdm.api.service.dto.TaxonInContextDTO;
41 import eu.etaxonomy.cdm.api.service.pager.Pager;
42 import eu.etaxonomy.cdm.api.service.pager.PagerUtils;
43 import eu.etaxonomy.cdm.api.service.pager.impl.AbstractPagerImpl;
44 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
45 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
46 import eu.etaxonomy.cdm.hibernate.HHH_9751_Util;
47 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
48 import eu.etaxonomy.cdm.model.common.CdmBase;
49 import eu.etaxonomy.cdm.model.common.DefinedTermBase;
50 import eu.etaxonomy.cdm.model.common.ITreeNode;
51 import eu.etaxonomy.cdm.model.common.MarkerType;
52 import eu.etaxonomy.cdm.model.common.TreeIndex;
53 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
54 import eu.etaxonomy.cdm.model.description.TaxonDescription;
55 import eu.etaxonomy.cdm.model.media.Media;
56 import eu.etaxonomy.cdm.model.media.MediaRepresentation;
57 import eu.etaxonomy.cdm.model.media.MediaUtils;
58 import eu.etaxonomy.cdm.model.name.INonViralName;
59 import eu.etaxonomy.cdm.model.name.Rank;
60 import eu.etaxonomy.cdm.model.name.TaxonName;
61 import eu.etaxonomy.cdm.model.reference.Reference;
62 import eu.etaxonomy.cdm.model.taxon.Classification;
63 import eu.etaxonomy.cdm.model.taxon.ITaxonNodeComparator;
64 import eu.etaxonomy.cdm.model.taxon.ITaxonTreeNode;
65 import eu.etaxonomy.cdm.model.taxon.Synonym;
66 import eu.etaxonomy.cdm.model.taxon.Taxon;
67 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
68 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
69 import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
70 import eu.etaxonomy.cdm.persistence.dao.common.IDefinedTermDao;
71 import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;
72 import eu.etaxonomy.cdm.persistence.dao.taxon.IClassificationDao;
73 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
74 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeDao;
75 import eu.etaxonomy.cdm.persistence.dto.ClassificationLookupDTO;
76 import eu.etaxonomy.cdm.persistence.dto.TaxonNodeDto;
77 import eu.etaxonomy.cdm.persistence.dto.TaxonStatus;
78 import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
79 import eu.etaxonomy.cdm.persistence.query.OrderHint;
80 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
81 import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;
82
83 /**
84 * @author n.hoffmann
85 * @since Sep 21, 2009
86 */
87 @Service
88 @Transactional(readOnly = true)
89 public class ClassificationServiceImpl extends IdentifiableServiceBase<Classification, IClassificationDao>
90 implements IClassificationService {
91 private static final Logger logger = Logger.getLogger(ClassificationServiceImpl.class);
92
93 @Autowired
94 private ITaxonNodeDao taxonNodeDao;
95
96 @Autowired
97 private ITaxonDao taxonDao;
98
99 @Autowired
100 private ITaxonNodeService taxonNodeService;
101
102 @Autowired
103 private IDefinedTermDao termDao;
104
105 @Autowired
106 private IBeanInitializer defaultBeanInitializer;
107
108 @Override
109 @Autowired
110 protected void setDao(IClassificationDao dao) {
111 this.dao = dao;
112 }
113
114 private Comparator<? super TaxonNode> taxonNodeComparator;
115
116 @Autowired
117 public void setTaxonNodeComparator(ITaxonNodeComparator<? super TaxonNode> taxonNodeComparator){
118 this.taxonNodeComparator = (Comparator<? super TaxonNode>) taxonNodeComparator;
119 }
120
121 @Override
122 public TaxonNode loadTaxonNodeByTaxon(Taxon taxon, UUID classificationUuid, List<String> propertyPaths){
123 Classification tree = dao.load(classificationUuid);
124 TaxonNode node = tree.getNode(taxon);
125
126 return loadTaxonNode(node.getUuid(), propertyPaths);
127 }
128
129 @Override
130 @Deprecated // use loadTaxonNode(UUID, List<String>) instead
131 public TaxonNode loadTaxonNode(TaxonNode taxonNode, List<String> propertyPaths){
132 return taxonNodeDao.load(taxonNode.getUuid(), propertyPaths);
133 }
134
135 public TaxonNode loadTaxonNode(UUID taxonNodeUuid, List<String> propertyPaths){
136 return taxonNodeDao.load(taxonNodeUuid, propertyPaths);
137 }
138
139 @Override
140 @Transactional(readOnly = false)
141 public UpdateResult cloneClassification(UUID classificationUuid,
142 String name, Reference sec, TaxonRelationshipType relationshipType) {
143 UpdateResult result = new UpdateResult();
144 Classification classification = load(classificationUuid);
145 Classification clone = Classification.NewInstance(name);
146 clone.setReference(sec);
147
148 //clone taxa and taxon nodes
149 List<TaxonNode> childNodes = classification.getRootNode().getChildNodes();
150 for (TaxonNode taxonNode : childNodes) {
151 addChildTaxa(taxonNode, null, clone, relationshipType);
152 }
153 dao.saveOrUpdate(clone);
154 result.setCdmEntity(clone);
155 return result;
156 }
157
158 private void addChildTaxa(TaxonNode originalParentNode, TaxonNode cloneParentNode, Classification classification, TaxonRelationshipType relationshipType){
159 Reference reference = classification.getReference();
160 Taxon cloneTaxon = (Taxon) HibernateProxyHelper.deproxy(originalParentNode.getTaxon(), Taxon.class).clone();
161 cloneTaxon.setSec(reference);
162 String microReference = null;
163 List<TaxonNode> originalChildNodes = originalParentNode.getChildNodes();
164 HHH_9751_Util.removeAllNull(originalChildNodes);
165
166 //add relation between taxa
167 if (relationshipType != null){
168 cloneTaxon.addTaxonRelation(originalParentNode.getTaxon(), relationshipType, reference, microReference);
169 }
170
171 TaxonNode cloneChildNode = null;
172 //add taxon node to either parent node or classification (no parent node)
173 if(cloneParentNode==null){
174 cloneChildNode = classification.addChildTaxon(cloneTaxon, reference, microReference);
175 }
176 else{
177 cloneChildNode = cloneParentNode.addChildTaxon(cloneTaxon, reference, microReference);
178 }
179 taxonNodeDao.saveOrUpdate(cloneChildNode);
180 //add children
181 for (TaxonNode originalChildNode : originalChildNodes) {
182 addChildTaxa(originalChildNode, cloneChildNode, classification, relationshipType);
183 }
184 }
185
186 @Override
187 public List<TaxonNode> listRankSpecificRootNodes(Classification classification, Rank rank, Integer pageSize,
188 Integer pageIndex, List<String> propertyPaths) {
189 return pageRankSpecificRootNodes(classification, rank, pageSize, pageIndex, propertyPaths).getRecords();
190 }
191
192 @Override
193 public Pager<TaxonNode> pageRankSpecificRootNodes(Classification classification, Rank rank, Integer pageSize,
194 Integer pageIndex, List<String> propertyPaths) {
195 long[] numberOfResults = dao.countRankSpecificRootNodes(classification, rank);
196 long totalNumberOfResults = numberOfResults[0] + (numberOfResults.length > 1 ? numberOfResults[1] : 0);
197
198 List<TaxonNode> results = new ArrayList<TaxonNode>();
199
200 if (AbstractPagerImpl.hasResultsInRange(totalNumberOfResults, pageIndex, pageSize)) { // no point checking again
201 Integer limit = PagerUtils.limitFor(pageSize);
202 Integer start = PagerUtils.startFor(pageSize, pageIndex);
203
204 Integer remainingLimit = limit;
205 int[] queryIndexes = rank == null ? new int[]{0} : new int[]{0,1};
206
207 for(int queryIndex: queryIndexes) {
208 if(start != null && start > numberOfResults[queryIndex]) {
209 // start in next query with new start value
210 start = start - (int)numberOfResults[queryIndex];
211 continue;
212 }
213
214 List<TaxonNode> perQueryResults = dao.listRankSpecificRootNodes(classification, rank, remainingLimit, start, propertyPaths, queryIndex);
215 results.addAll(perQueryResults);
216 if(remainingLimit != null ){
217 remainingLimit = remainingLimit - results.size();
218 if(remainingLimit <= 0) {
219 // no need to run further queries if first query returned enough items!
220 break;
221 }
222 // start at with fist item of next query to fetch the remaining items
223 start = 0;
224 }
225 }
226 }
227 // long start_t = System.currentTimeMillis();
228 Collections.sort(results, taxonNodeComparator); // TODO is ordering during the hibernate query in the dao possible?
229 // System.err.println("service.pageRankSpecificRootNodes() - Collections.sort(results, taxonNodeComparator) " + (System.currentTimeMillis() - start_t));
230 return new DefaultPagerImpl<TaxonNode>(pageIndex, (int) totalNumberOfResults, pageSize, results);
231
232 }
233
234 /**
235 * @implements {@link IClassificationService#loadTreeBranch(TaxonNode, Rank, List)
236 * @see eu.etaxonomy.cdm.api.service.ITaxonService#loadTreeBranchTo(eu.etaxonomy.cdm.model.taxon.TaxonNode, eu.etaxonomy.cdm.model.name.Rank, java.util.List)
237 * FIXME Candidate for harmonization
238 * move to classification service
239 */
240 @Override
241 public List<TaxonNode> loadTreeBranch(TaxonNode taxonNode, Rank baseRank, List<String> propertyPaths){
242
243 TaxonNode thisNode = taxonNodeDao.load(taxonNode.getUuid(), propertyPaths);
244 if(baseRank != null){
245 baseRank = (Rank) termDao.load(baseRank.getUuid());
246 }
247 List<TaxonNode> pathToRoot = new ArrayList<TaxonNode>();
248 pathToRoot.add(thisNode);
249
250 while(!thisNode.isTopmostNode()){
251 //TODO why do we need to deproxy here?
252 // without this thisNode.getParent() will return NULL in
253 // some cases (environment dependend?) even if the parent exits
254 TaxonNode parentNode = CdmBase.deproxy(thisNode, TaxonNode.class).getParent();
255
256 if(parentNode == null){
257 throw new NullPointerException("taxonNode " + thisNode + " must have a parent since it is not top most");
258 }
259 if(parentNode.getTaxon() == null){
260 throw new NullPointerException("The taxon associated with taxonNode " + parentNode + " is NULL");
261 }
262 if(parentNode.getTaxon().getName() == null){
263 throw new NullPointerException("The name of the taxon associated with taxonNode " + parentNode + " is NULL");
264 }
265
266 Rank parentNodeRank = parentNode.getTaxon().getName() == null ? null : parentNode.getTaxon().getName().getRank();
267 // stop if the next parent is higher than the baseRank
268 if(baseRank != null && parentNodeRank != null && baseRank.isLower(parentNodeRank)){
269 break;
270 }
271
272 pathToRoot.add(parentNode);
273 thisNode = parentNode;
274 }
275
276 // initialize and invert order of nodes in list
277 defaultBeanInitializer.initializeAll(pathToRoot, propertyPaths);
278 Collections.reverse(pathToRoot);
279
280 return pathToRoot;
281 }
282
283 @Override
284 public List<TaxonNode> loadTreeBranchToTaxon(Taxon taxon, Classification classification, Rank baseRank, List<String> propertyPaths){
285 Classification tree = dao.load(classification.getUuid());
286 taxon = (Taxon) taxonDao.load(taxon.getUuid());
287 TaxonNode node = tree.getNode(taxon);
288 if(node == null){
289 logger.warn("The specified taxon is not found in the given tree.");
290 return null;
291 }
292 return loadTreeBranch(node, baseRank, propertyPaths);
293 }
294
295
296 @Override
297 public List<TaxonNode> loadChildNodesOfTaxonNode(TaxonNode taxonNode,
298 List<String> propertyPaths) {
299 taxonNode = taxonNodeDao.load(taxonNode.getUuid());
300 List<TaxonNode> childNodes = new ArrayList<TaxonNode>(taxonNode.getChildNodes());
301 defaultBeanInitializer.initializeAll(childNodes, propertyPaths);
302 Collections.sort(childNodes, taxonNodeComparator);
303 return childNodes;
304 }
305
306 @Override
307 public List<TaxonNode> listChildNodesOfTaxon(UUID taxonUuid, UUID classificationUuid, Integer pageSize,
308 Integer pageIndex, List<String> propertyPaths){
309
310 Classification classification = dao.load(classificationUuid);
311 Taxon taxon = (Taxon) taxonDao.load(taxonUuid);
312
313 List<TaxonNode> results = dao.listChildrenOf(taxon, classification, pageSize, pageIndex, propertyPaths);
314 Collections.sort(results, taxonNodeComparator); // FIXME this is only a HACK, order during the hibernate query in the dao
315 return results;
316 }
317
318 @Override
319 public Pager<TaxonNode> pageSiblingsOfTaxon(UUID taxonUuid, UUID classificationUuid, Integer pageSize,
320 Integer pageIndex, List<String> propertyPaths){
321
322 Classification classification = dao.load(classificationUuid);
323 Taxon taxon = (Taxon) taxonDao.load(taxonUuid);
324
325 long numberOfResults = dao.countSiblingsOf(taxon, classification);
326
327 List<TaxonNode> results;
328 if(PagerUtils.hasResultsInRange(numberOfResults, pageIndex, pageSize)) {
329 results = dao.listSiblingsOf(taxon, classification, pageSize, pageIndex, propertyPaths);
330 Collections.sort(results, taxonNodeComparator); // FIXME this is only a HACK, order during the hibernate query in the dao
331 } else {
332 results = new ArrayList<>();
333 }
334
335 return new DefaultPagerImpl<TaxonNode>(pageIndex, numberOfResults, pageSize, results);
336 }
337
338 @Override
339 public List<TaxonNode> listSiblingsOfTaxon(UUID taxonUuid, UUID classificationUuid, Integer pageSize,
340 Integer pageIndex, List<String> propertyPaths){
341
342 Pager<TaxonNode> pager = pageSiblingsOfTaxon(taxonUuid, classificationUuid, pageSize, pageIndex, propertyPaths);
343 return pager.getRecords();
344 }
345
346 @Override
347 public TaxonNode getTaxonNodeByUuid(UUID uuid) {
348 return taxonNodeDao.findByUuid(uuid);
349 }
350
351 @Override
352 public ITaxonTreeNode getTreeNodeByUuid(UUID uuid){
353 ITaxonTreeNode treeNode = taxonNodeDao.findByUuid(uuid);
354 if(treeNode == null){
355 treeNode = dao.findByUuid(uuid);
356 }
357
358 return treeNode;
359 }
360
361 @Override
362 public TaxonNode getRootNode(UUID classificationUuid){
363 return dao.getRootNode(classificationUuid);
364 }
365
366 @Override
367 public List<Classification> listClassifications(Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
368 return dao.list(limit, start, orderHints, propertyPaths);
369 }
370
371 @Override
372 public UUID removeTaxonNode(TaxonNode taxonNode) {
373 return taxonNodeDao.delete(taxonNode);
374 }
375 @Override
376 public UUID removeTreeNode(ITaxonTreeNode treeNode) {
377 if(treeNode instanceof Classification){
378 return dao.delete((Classification) treeNode);
379 }else if(treeNode instanceof TaxonNode){
380 return taxonNodeDao.delete((TaxonNode)treeNode);
381 }
382 return null;
383 }
384 @Override
385 public UUID saveTaxonNode(TaxonNode taxonNode) {
386 return taxonNodeDao.save(taxonNode).getUuid();
387 }
388
389 @Override
390 public Map<UUID, TaxonNode> saveTaxonNodeAll(
391 Collection<TaxonNode> taxonNodeCollection) {
392 return taxonNodeDao.saveAll(taxonNodeCollection);
393 }
394
395 @Override
396 public UUID saveClassification(Classification classification) {
397
398 taxonNodeDao.saveOrUpdateAll(classification.getAllNodes());
399 UUID result =dao.saveOrUpdate(classification);
400 return result;
401 }
402
403 @Override
404 public UUID saveTreeNode(ITaxonTreeNode treeNode) {
405 if(treeNode instanceof Classification){
406 return dao.save((Classification) treeNode).getUuid();
407 }else if(treeNode instanceof TaxonNode){
408 return taxonNodeDao.save((TaxonNode)treeNode).getUuid();
409 }
410 return null;
411 }
412
413 @Override
414 public List<TaxonNode> getAllNodes(){
415 return taxonNodeDao.list(null,null);
416 }
417
418 @Override
419 public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(UUID classificationUuid, Integer limit, String pattern, boolean searchForClassifications) {
420 return taxonNodeDao.getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(dao.load(classificationUuid), limit, pattern, searchForClassifications);
421 }
422
423 @Override
424 public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(Classification classification, Integer limit, String pattern, boolean searchForClassifications) {
425 return taxonNodeDao.getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classification, limit, pattern, searchForClassifications);
426 }
427
428 @Override
429 public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(UUID classificationUuid, boolean searchForClassifications ) {
430 return taxonNodeDao.getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(dao.load(classificationUuid), null, null, searchForClassifications);
431 }
432
433 @Override
434 public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(Classification classification, boolean searchForClassifications ) {
435 return taxonNodeDao.getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classification, null, null, searchForClassifications);
436 }
437
438 @Override
439 public List<UuidAndTitleCache<Classification>> getUuidAndTitleCache(Integer limit, String pattern) {
440 return dao.getUuidAndTitleCache(limit, pattern);
441 }
442
443 @Override
444 public Map<UUID, List<MediaRepresentation>> getAllMediaForChildNodes(
445 TaxonNode taxonNode, List<String> propertyPaths, int size,
446 int height, int widthOrDuration, String[] mimeTypes) {
447
448 TreeMap<UUID, List<MediaRepresentation>> result = new TreeMap<UUID, List<MediaRepresentation>>();
449 List<Media> taxonMedia = new ArrayList<Media>();
450 List<MediaRepresentation> mediaRepresentations = new ArrayList<MediaRepresentation>();
451
452 //add all media of the children to the result map
453 if (taxonNode != null){
454
455 List<TaxonNode> nodes = new ArrayList<TaxonNode>();
456
457 nodes.add(loadTaxonNode(taxonNode, propertyPaths));
458 nodes.addAll(loadChildNodesOfTaxonNode(taxonNode, propertyPaths));
459
460 if (nodes != null){
461 for(TaxonNode node : nodes){
462 Taxon taxon = node.getTaxon();
463 for (TaxonDescription taxonDescription: taxon.getDescriptions()){
464 for (DescriptionElementBase descriptionElement: taxonDescription.getElements()){
465 for(Media media : descriptionElement.getMedia()){
466 taxonMedia.add(media);
467
468 //find the best matching representation
469 mediaRepresentations.add(MediaUtils.findBestMatchingRepresentation(media,null, size, height, widthOrDuration, mimeTypes));
470
471 }
472 }
473 }
474 result.put(taxon.getUuid(), mediaRepresentations);
475
476 }
477 }
478
479 }
480
481 return result;
482
483 }
484
485
486 @Override
487 @Transactional(readOnly = false)
488 public void updateTitleCache(Class<? extends Classification> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<Classification> cacheStrategy, IProgressMonitor monitor) {
489 if (clazz == null){
490 clazz = Classification.class;
491 }
492 super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
493 }
494
495 /**
496 *
497 * @param allNodesOfClassification
498 * @return null - if allNodesOfClassification is empty <br>
499 */
500
501 private Map<String, List<TaxonNode>> getSortedGenusList(Collection<TaxonNode> allNodesOfClassification){
502
503 if(allNodesOfClassification == null || allNodesOfClassification.isEmpty()){
504 return null;
505 }
506 Map<String, List<TaxonNode>> sortedGenusMap = new HashMap<>();
507 for(TaxonNode node:allNodesOfClassification){
508 final TaxonNode tn = node;
509 Taxon taxon = node.getTaxon();
510 INonViralName name = taxon.getName();
511 String genusOrUninomial = name.getGenusOrUninomial();
512 //if rank unknown split string and take first word
513 if(genusOrUninomial == null){
514 String titleCache = taxon.getTitleCache();
515 String[] split = titleCache.split("\\s+");
516 for(String s:split){
517 genusOrUninomial = s;
518 break;
519 }
520 }
521 //if node has children
522
523 //retrieve list from map if not create List
524 if(sortedGenusMap.containsKey(genusOrUninomial)){
525 List<TaxonNode> list = sortedGenusMap.get(genusOrUninomial);
526 list.add(node);
527 sortedGenusMap.put(genusOrUninomial, list);
528 }else{
529 //create List for genus
530 List<TaxonNode> list = new ArrayList<TaxonNode>();
531 list.add(node);
532 sortedGenusMap.put(genusOrUninomial, list);
533 }
534 }
535 return sortedGenusMap;
536 }
537
538 /**
539 *
540 * creates new Classification and parent TaxonNodes at genus level
541 *
542 *
543 * @param map GenusMap which holds a name (Genus) and all the same Taxa as a list
544 * @param classification you want to improve the hierarchy (will not be modified)
545 * @param configurator to change certain settings, if null then standard settings will be taken
546 * @return new classification with parentNodes for each entry in the map
547 */
548 @SuppressWarnings({ "unchecked" })
549 @Transactional(readOnly = false)
550 @Override
551 public UpdateResult createHierarchyInClassification(Classification classification, CreateHierarchyForClassificationConfigurator configurator){
552 UpdateResult result = new UpdateResult();
553 classification = dao.findByUuid(classification.getUuid());
554 Map<String, List<TaxonNode>> map = getSortedGenusList(classification.getAllNodes());
555
556 final String APPENDIX = "repaired";
557 String titleCache = StringUtils.isBlank(classification.getTitleCache()) ? " " : classification.getTitleCache() ;
558 //TODO classification clone???
559 Classification newClassification = Classification.NewInstance(titleCache +" "+ APPENDIX);
560 newClassification.setReference(classification.getReference());
561
562 for(Map.Entry<String, List<TaxonNode>> entry:map.entrySet()){
563 String genus = entry.getKey();
564 List<TaxonNode> listOfTaxonNodes = entry.getValue();
565 TaxonNode parentNode = null;
566 //Search for genus in list
567 for(TaxonNode tNode:listOfTaxonNodes){
568 //take that taxonNode as parent and remove from list with all it possible children
569 //FIXME NPE for name
570 TaxonName name = tNode.getTaxon().getName();
571 if(name.getNameCache().equalsIgnoreCase(genus)){
572 TaxonNode clone = (TaxonNode) tNode.clone();
573 if(!tNode.hasChildNodes()){
574 //FIXME remove classification
575 // parentNode = newClassification.addChildNode(clone, 0, classification.getCitation(), classification.getMicroReference());
576 parentNode = newClassification.addChildNode(clone, 0, clone.getReference(), clone.getMicroReference());
577 //remove taxonNode from list because just added to classification
578 result.addUpdatedObject(tNode);
579 listOfTaxonNodes.remove(tNode);
580 }else{
581 //get all childNodes
582 //save prior Hierarchy and remove them from the list
583 List<TaxonNode> copyAllChildrenToTaxonNode = copyAllChildrenToTaxonNode(tNode, clone, result);
584 // parentNode = newClassification.addChildNode(clone, 0, classification.getCitation(), classification.getMicroReference());
585 //FIXME remove classification
586 parentNode = newClassification.addChildNode(clone, 0, clone.getReference(), clone.getMicroReference());
587 //remove taxonNode from list because just added to classification
588 result.addUpdatedObject(tNode);
589 listOfTaxonNodes.remove(tNode);
590 if(copyAllChildrenToTaxonNode != null){
591 listOfTaxonNodes = (List<TaxonNode>) CollectionUtils.removeAll(listOfTaxonNodes, copyAllChildrenToTaxonNode);
592 }
593 }
594 break;
595 }
596 }
597 if(parentNode == null){
598 //if no match found in list, create parentNode
599 NonViralNameParserImpl parser = NonViralNameParserImpl.NewInstance();
600 TaxonName TaxonName = (TaxonName)parser.parseFullName(genus);
601 //TODO Sec via configurator
602 Taxon taxon = Taxon.NewInstance(TaxonName, null);
603 parentNode = newClassification.addChildTaxon(taxon, 0, null, null);
604 result.addUpdatedObject(parentNode);
605 }
606 //iterate over the rest of the list
607 for(TaxonNode tn : listOfTaxonNodes){
608 //if TaxonNode has a parent and this is not the classification then skip it
609 //and add to new classification via the parentNode as children of it
610 //this should assures to keep the already existing hierarchy
611 //FIXME: Assert is not rootnode --> entrypoint is not classification in future but rather rootNode
612
613 if(!tn.isTopmostNode()){
614 continue; //skip to next taxonNode
615 }
616
617 TaxonNode clone = (TaxonNode) tn.clone();
618 //FIXME: citation from node
619 //TODO: addchildNode without citation and references
620 // TaxonNode taxonNode = parentNode.addChildNode(clone, classification.getCitation(), classification.getMicroReference());
621 TaxonNode taxonNode = parentNode.addChildNode(clone, clone.getReference(), clone.getMicroReference());
622 result.addUnChangedObject(clone);
623 if(tn.hasChildNodes()){
624 //save hierarchy in new classification
625 List<TaxonNode> copyAllChildrenToTaxonNode = copyAllChildrenToTaxonNode(tn, taxonNode, result);
626 if(copyAllChildrenToTaxonNode != null){
627 listOfTaxonNodes = (List<TaxonNode>) CollectionUtils.removeAll(listOfTaxonNodes, copyAllChildrenToTaxonNode);
628 }
629 }
630 }
631 }
632 dao.saveOrUpdate(newClassification);
633 result.setCdmEntity(newClassification);
634 return result;
635 }
636
637 /**
638 *
639 * recursive method to get all childnodes of taxonNode in classification.
640 *
641 * @param classification just for References and Citation, can be null
642 * @param copyFromNode TaxonNode with Children
643 * @param copyToNode TaxonNode which will receive the children
644 * @return List of ChildNode which has been added. If node has no children returns null
645 */
646 private List<TaxonNode> copyAllChildrenToTaxonNode(TaxonNode copyFromNode, TaxonNode copyToNode, UpdateResult result) {
647 List<TaxonNode> childNodes;
648 if(!copyFromNode.hasChildNodes()){
649 return null;
650 }else{
651 childNodes = copyFromNode.getChildNodes();
652 }
653 for(TaxonNode childNode:childNodes){
654 TaxonNode clone = (TaxonNode) childNode.clone();
655 result.addUnChangedObject(clone);
656 if(childNode.hasChildNodes()){
657 copyAllChildrenToTaxonNode(childNode, clone, result);
658 }
659 //FIXME: citation from node instead of classification
660 // copyToNode.addChildNode(clone,classification.getCitation(), classification.getMicroReference());
661 copyToNode.addChildNode(clone, clone.getReference(), clone.getMicroReference());
662 }
663 return childNodes;
664 }
665
666 /**
667 * {@inheritDoc}
668 */
669 @Override
670 public ClassificationLookupDTO classificationLookup(Classification classification) {
671 return dao.classificationLookup(classification);
672 }
673
674
675 @Override
676 @Transactional
677 public DeleteResult delete(UUID classificationUuid, TaxonDeletionConfigurator config){
678 DeleteResult result = new DeleteResult();
679 Classification classification = dao.findByUuid(classificationUuid);
680 if (classification == null){
681 result.addException(new IllegalArgumentException("The classification does not exist in database."));
682 result.setAbort();
683 return result;
684 }
685 if (!classification.hasChildNodes()){
686 dao.delete(classification);
687 result.addDeletedObject(classification);
688 return result;
689 }
690 if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)){
691 // TaxonNode root = classification.getRootNode();
692 // result.includeResult(taxonNodeService.deleteTaxonNode(HibernateProxyHelper.deproxy(root), config));
693 // result.addDeletedObject(classification);
694 dao.delete(classification);
695 result.addDeletedObject(classification);
696 return result;
697 }
698
699
700 return result;
701 }
702
703 @Override
704 public List<GroupedTaxonDTO> groupTaxaByHigherTaxon(List<UUID> originalTaxonUuids, UUID classificationUuid, Rank minRank, Rank maxRank){
705 List<GroupedTaxonDTO> result = new ArrayList<>();
706
707 //get treeindex for each taxonUUID
708 Map<UUID, TreeIndex> taxonIdTreeIndexMap = dao.treeIndexForTaxonUuids(classificationUuid, originalTaxonUuids);
709
710 //build treeindex list (or tree)
711 //TODO make it work with TreeIndex or move there
712 List<String> treeIndexClosureStr = new ArrayList<>();
713 for (TreeIndex treeIndex : taxonIdTreeIndexMap.values()){
714 String[] splits = treeIndex.toString().substring(1).split(ITreeNode.separator);
715 String currentIndex = ITreeNode.separator;
716 for (String split : splits){
717 if (split.equals("")){
718 continue;
719 }
720 currentIndex += split + ITreeNode.separator;
721 if (!treeIndexClosureStr.contains(currentIndex) && !split.startsWith(ITreeNode.treePrefix)){
722 treeIndexClosureStr.add(currentIndex);
723 }
724 }
725 }
726
727 //get rank sortindex for all parent taxa with sortindex <= minRank and sortIndex >= maxRank (if available)
728 Integer minRankOrderIndex = minRank == null ? null : minRank.getOrderIndex();
729 Integer maxRankOrderIndex = maxRank == null ? null : maxRank.getOrderIndex();
730 List<TreeIndex> treeIndexClosure = TreeIndex.NewListInstance(treeIndexClosureStr);
731
732 Map<TreeIndex, Integer> treeIndexSortIndexMapTmp = taxonNodeDao.rankOrderIndexForTreeIndex(treeIndexClosure, minRankOrderIndex, maxRankOrderIndex);
733
734 //remove all treeindex with "exists child in above map(and child.sortindex > xxx)
735 List<TreeIndex> treeIndexList = TreeIndex.sort(treeIndexSortIndexMapTmp.keySet());
736
737 Map<TreeIndex, Integer> treeIndexSortIndexMap = new HashMap<>();
738 TreeIndex lastTreeIndex = null;
739 for (TreeIndex treeIndex : treeIndexList){
740 if (lastTreeIndex != null && lastTreeIndex.hasChild(treeIndex)){
741 treeIndexSortIndexMap.remove(lastTreeIndex);
742 }
743 treeIndexSortIndexMap.put(treeIndex, treeIndexSortIndexMapTmp.get(treeIndex));
744 lastTreeIndex = treeIndex;
745 }
746
747 //get taxonID for treeIndexes
748 Map<TreeIndex, UuidAndTitleCache<?>> treeIndexTaxonIdMap = taxonNodeDao.taxonUuidsForTreeIndexes(treeIndexSortIndexMap.keySet());
749
750 //fill result list
751 for (UUID originalTaxonUuid : originalTaxonUuids){
752 GroupedTaxonDTO item = new GroupedTaxonDTO();
753 result.add(item);
754 item.setTaxonUuid(originalTaxonUuid);
755 TreeIndex groupTreeIndex = taxonIdTreeIndexMap.get(originalTaxonUuid);
756 String groupIndexX = TreeIndex.toString(groupTreeIndex);
757 while (groupTreeIndex != null){
758 if (treeIndexTaxonIdMap.get(groupTreeIndex) != null){
759 UuidAndTitleCache<?> uuidAndLabel = treeIndexTaxonIdMap.get(groupTreeIndex);
760 item.setGroupTaxonUuid(uuidAndLabel.getUuid());
761 item.setGroupTaxonName(uuidAndLabel.getTitleCache());
762 break;
763 }else{
764 groupTreeIndex = groupTreeIndex.parent();
765 // int index = groupIndex.substring(0, groupIndex.length()-1).lastIndexOf(ITreeNode.separator);
766 // groupIndex = index < 0 ? null : groupIndex.substring(0, index+1);
767 }
768 }
769 }
770
771 return result;
772 }
773
774 /**
775 * {@inheritDoc}
776 */
777 @Override
778 public List<GroupedTaxonDTO> groupTaxaByMarkedParents(List<UUID> originalTaxonUuids, UUID classificationUuid,
779 MarkerType markerType, Boolean flag) {
780
781 List<GroupedTaxonDTO> result = new ArrayList<>();
782
783 //get treeindex for each taxonUUID
784 Map<UUID, TreeIndex> taxonIdTreeIndexMap = dao.treeIndexForTaxonUuids(classificationUuid, originalTaxonUuids);
785
786 //get all marked tree indexes
787 Set<TreeIndex> markedTreeIndexes = dao.getMarkedTreeIndexes(markerType, flag);
788
789
790 Map<TreeIndex, TreeIndex> groupedMap = TreeIndex.group(markedTreeIndexes, taxonIdTreeIndexMap.values());
791 Set<TreeIndex> notNullGroups = new HashSet<>(groupedMap.values());
792 notNullGroups.remove(null);
793
794 //get taxonInfo for treeIndexes
795 Map<TreeIndex, UuidAndTitleCache<?>> treeIndexTaxonIdMap = taxonNodeDao.taxonUuidsForTreeIndexes(notNullGroups);
796
797 //fill result list
798 for (UUID originalTaxonUuid : originalTaxonUuids){
799 GroupedTaxonDTO item = new GroupedTaxonDTO();
800 result.add(item);
801 item.setTaxonUuid(originalTaxonUuid);
802
803 TreeIndex toBeGroupedTreeIndex = taxonIdTreeIndexMap.get(originalTaxonUuid);
804 TreeIndex groupTreeIndex = groupedMap.get(toBeGroupedTreeIndex);
805 UuidAndTitleCache<?> uuidAndLabel = treeIndexTaxonIdMap.get(groupTreeIndex);
806 if (uuidAndLabel != null){
807 item.setGroupTaxonUuid(uuidAndLabel.getUuid());
808 item.setGroupTaxonName(uuidAndLabel.getTitleCache());
809 }
810 }
811
812 return result;
813 }
814
815 /**
816 * {@inheritDoc}
817 */
818 @Override
819 public UUID getTaxonNodeUuidByTaxonUuid(UUID classificationUuid, UUID taxonUuid) {
820 Map<UUID, UUID> map = dao.getTaxonNodeUuidByTaxonUuid(classificationUuid, Arrays.asList(taxonUuid));
821 UUID taxonNodeUuid = map.get(taxonUuid);
822 return taxonNodeUuid;
823 }
824
825 /**
826 * {@inheritDoc}
827 */
828 @Override
829 public TaxonInContextDTO getTaxonInContext(UUID classificationUuid, UUID taxonBaseUuid,
830 Boolean doChildren, Boolean doSynonyms, boolean includeUnpublished, List<UUID> ancestorMarkers,
831 NodeSortMode sortMode) {
832 TaxonInContextDTO result = new TaxonInContextDTO();
833
834 TaxonBase<?> taxonBase = taxonDao.load(taxonBaseUuid);
835 if (taxonBase == null){
836 throw new EntityNotFoundException("Taxon with uuid " + taxonBaseUuid + " not found in datasource");
837 }
838 boolean isSynonym = false;
839 Taxon acceptedTaxon;
840 if (taxonBase.isInstanceOf(Synonym.class)){
841 isSynonym = true;
842 Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
843 acceptedTaxon = synonym.getAcceptedTaxon();
844 if (acceptedTaxon == null) {
845 throw new EntityNotFoundException("Accepted taxon not found for synonym" );
846 }
847 TaxonStatus taxonStatus = TaxonStatus.Synonym;
848 if (synonym.getName()!= null && acceptedTaxon.getName() != null
849 && synonym.getName().getHomotypicalGroup().equals(acceptedTaxon.getName().getHomotypicalGroup())){
850 taxonStatus = TaxonStatus.SynonymObjective;
851 }
852 result.setTaxonStatus(taxonStatus);
853
854 }else{
855 acceptedTaxon = CdmBase.deproxy(taxonBase, Taxon.class);
856 result.setTaxonStatus(TaxonStatus.Accepted);
857 }
858 UUID acceptedTaxonUuid = acceptedTaxon.getUuid();
859
860 UUID taxonNodeUuid = getTaxonNodeUuidByTaxonUuid(classificationUuid, acceptedTaxonUuid);
861 if (taxonNodeUuid == null) {
862 throw new EntityNotFoundException("Taxon not found in classficiation with uuid " + classificationUuid + ". Either classification does not exist or does not contain taxon/synonym with uuid " + taxonBaseUuid );
863 }
864 result.setTaxonNodeUuid(taxonNodeUuid);
865
866 //TODO make it a dao call
867 Taxon parentTaxon = getParentTaxon(classificationUuid, acceptedTaxon);
868 if (parentTaxon != null){
869 result.setParentTaxonUuid(parentTaxon.getUuid());
870 result.setParentTaxonLabel(parentTaxon.getTitleCache());
871 if (parentTaxon.getName() != null){
872 result.setParentNameLabel(parentTaxon.getName().getTitleCache());
873 }
874 }
875
876
877 result.setTaxonUuid(taxonBaseUuid);
878 result.setClassificationUuid(classificationUuid);
879 if (taxonBase.getSec() != null){
880 result.setSecundumUuid(taxonBase.getSec().getUuid());
881 result.setSecundumLabel(taxonBase.getSec().getTitleCache());
882 }
883 result.setTaxonLabel(taxonBase.getTitleCache());
884
885 TaxonName name = taxonBase.getName();
886 result.setNameUuid(name.getUuid());
887 result.setNameLabel(name.getTitleCache());
888 result.setNameWithoutAuthor(name.getNameCache());
889 result.setGenusOrUninomial(name.getGenusOrUninomial());
890 result.setInfraGenericEpithet(name.getInfraGenericEpithet());
891 result.setSpeciesEpithet(name.getSpecificEpithet());
892 result.setInfraSpecificEpithet(name.getInfraSpecificEpithet());
893
894 result.setAuthorship(name.getAuthorshipCache());
895
896 Rank rank = name.getRank();
897 if (rank != null){
898 result.setRankUuid(rank.getUuid());
899 String rankLabel = rank.getAbbreviation();
900 if (StringUtils.isBlank(rankLabel)){
901 rankLabel = rank.getLabel();
902 }
903 result.setRankLabel(rankLabel);
904 }
905
906 boolean recursive = false;
907 Integer pageSize = null;
908 Integer pageIndex = null;
909 Pager<TaxonNodeDto> children = taxonNodeService.pageChildNodesDTOs(taxonNodeUuid, recursive, includeUnpublished, doSynonyms,
910 sortMode, pageSize, pageIndex);
911
912 //children
913 if(! isSynonym) {
914 for (TaxonNodeDto childDto : children.getRecords()){
915 if (doChildren && childDto.getStatus().equals(TaxonStatus.Accepted)){
916 EntityDTO<Taxon> child = new EntityDTO<Taxon>(childDto.getTaxonUuid(), childDto.getTitleCache());
917 result.addChild(child);
918 }else if (doSynonyms && childDto.getStatus().isSynonym()){
919 EntityDTO<Synonym> child = new EntityDTO<Synonym>(childDto.getTaxonUuid(), childDto.getTitleCache());
920 result.addSynonym(child);
921 }
922 }
923 }else{
924 result.setAcceptedTaxonUuid(acceptedTaxonUuid);
925 String nameTitel = acceptedTaxon.getName() == null ? null : acceptedTaxon.getName().getTitleCache();
926 result.setAcceptedTaxonLabel(acceptedTaxon.getTitleCache());
927 result.setAcceptedNameLabel(nameTitel);
928 }
929
930 //marked ancestors
931 if (ancestorMarkers != null && !ancestorMarkers.isEmpty()){
932 List<DefinedTermBase> markerTypesTerms = termDao.list(ancestorMarkers, pageSize, null, null, null);
933 List<MarkerType> markerTypes = new ArrayList<>();
934 for (DefinedTermBase<?> term : markerTypesTerms){
935 if (term.isInstanceOf(MarkerType.class)){
936 markerTypes.add(CdmBase.deproxy(term, MarkerType.class));
937 }
938 }
939 if (! markerTypes.isEmpty()){
940 TaxonNode node = taxonNodeDao.findByUuid(taxonNodeUuid);
941 handleAncestorsForMarkersRecursive(result, markerTypes, node);
942 }
943 }
944
945 return result;
946 }
947
948 /**
949 * @param classificationUuid
950 * @param acceptedTaxon
951 * @return
952 */
953 private Taxon getParentTaxon(UUID classificationUuid, Taxon acceptedTaxon) {
954 if (classificationUuid == null){
955 return null;
956 }
957 TaxonNode parent = null;
958 for (TaxonNode node : acceptedTaxon.getTaxonNodes()){
959 if (classificationUuid.equals(node.getClassification().getUuid())){
960 parent = node.getParent();
961 }
962 }
963 if (parent != null){
964 return parent.getTaxon();
965 }
966 return null;
967 }
968
969 /**
970 * @param result
971 * @param markerTypes
972 * @param node
973 */
974 private void handleAncestorsForMarkersRecursive(TaxonInContextDTO result, List<MarkerType> markerTypes, TaxonNode node) {
975 for (MarkerType type : markerTypes){
976 Taxon taxon = node.getTaxon();
977 if (taxon != null && taxon.hasMarker(type, true)){
978 String label = taxon.getName() == null? taxon.getTitleCache() : taxon.getName().getTitleCache();
979 MarkedEntityDTO<Taxon> dto = new MarkedEntityDTO<>(type, true, taxon.getUuid(), label);
980 result.addMarkedAncestor(dto);
981 }
982 }
983 TaxonNode parentNode = node.getParent();
984 if (parentNode != null){
985 handleAncestorsForMarkersRecursive(result, markerTypes, parentNode);
986 }
987 }
988
989 /**
990 * {@inheritDoc}
991 */
992 @Override
993 public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(
994 Classification classification) {
995 return getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classification, false);
996 }
997
998 /**
999 * {@inheritDoc}
1000 */
1001 @Override
1002 public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(
1003 UUID classificationUuid) {
1004 return getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classificationUuid, false);
1005 }
1006
1007 /**
1008 * {@inheritDoc}
1009 */
1010 @Override
1011 public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(
1012 UUID classificationUuid, Integer limit, String pattern) {
1013 return getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classificationUuid, limit, pattern, false);
1014 }
1015
1016 /**
1017 * {@inheritDoc}
1018 */
1019 @Override
1020 public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(
1021 Classification classification, Integer limit, String pattern) {
1022 return getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classification, limit, pattern, false);
1023 }
1024
1025
1026 }