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