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