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