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