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