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