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