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