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