cleanup
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / TaxonNodeServiceImpl.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.HashSet;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.UUID;
22 import java.util.stream.Collectors;
23
24 import org.apache.logging.log4j.LogManager;
25 import org.apache.logging.log4j.Logger;
26 import org.springframework.beans.factory.annotation.Autowired;
27 import org.springframework.security.core.Authentication;
28 import org.springframework.stereotype.Service;
29 import org.springframework.transaction.annotation.Transactional;
30 import org.springframework.transaction.interceptor.TransactionAspectSupport;
31
32 import eu.etaxonomy.cdm.api.service.UpdateResult.Status;
33 import eu.etaxonomy.cdm.api.service.config.ForSubtreeConfiguratorBase;
34 import eu.etaxonomy.cdm.api.service.config.NodeDeletionConfigurator.ChildHandling;
35 import eu.etaxonomy.cdm.api.service.config.PublishForSubtreeConfigurator;
36 import eu.etaxonomy.cdm.api.service.config.SecundumForSubtreeConfigurator;
37 import eu.etaxonomy.cdm.api.service.config.SubtreeCloneConfigurator;
38 import eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator;
39 import eu.etaxonomy.cdm.api.service.config.TaxonNodeDeletionConfigurator;
40 import eu.etaxonomy.cdm.api.service.dto.CdmEntityIdentifier;
41 import eu.etaxonomy.cdm.api.service.dto.CreateTaxonDTO;
42 import eu.etaxonomy.cdm.api.service.dto.TaxonDistributionDTO;
43 import eu.etaxonomy.cdm.api.service.pager.Pager;
44 import eu.etaxonomy.cdm.api.service.pager.PagerUtils;
45 import eu.etaxonomy.cdm.api.service.pager.impl.AbstractPagerImpl;
46 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
47 import eu.etaxonomy.cdm.common.monitor.DefaultProgressMonitor;
48 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
49 import eu.etaxonomy.cdm.common.monitor.SubProgressMonitor;
50 import eu.etaxonomy.cdm.compare.taxon.HomotypicGroupTaxonComparator;
51 import eu.etaxonomy.cdm.compare.taxon.TaxonNodeSortMode;
52 import eu.etaxonomy.cdm.filter.TaxonNodeFilter;
53 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
54 import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
55 import eu.etaxonomy.cdm.model.common.CdmBase;
56 import eu.etaxonomy.cdm.model.common.Language;
57 import eu.etaxonomy.cdm.model.common.LanguageString;
58 import eu.etaxonomy.cdm.model.common.TreeIndex;
59 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
60 import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
61 import eu.etaxonomy.cdm.model.description.DescriptiveDataSet;
62 import eu.etaxonomy.cdm.model.description.TaxonDescription;
63 import eu.etaxonomy.cdm.model.metadata.SecReferenceHandlingEnum;
64 import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
65 import eu.etaxonomy.cdm.model.name.HybridRelationship;
66 import eu.etaxonomy.cdm.model.name.Rank;
67 import eu.etaxonomy.cdm.model.name.TaxonName;
68 import eu.etaxonomy.cdm.model.permission.Operation;
69 import eu.etaxonomy.cdm.model.reference.NamedSource;
70 import eu.etaxonomy.cdm.model.reference.Reference;
71 import eu.etaxonomy.cdm.model.taxon.Classification;
72 import eu.etaxonomy.cdm.model.taxon.ITaxonTreeNode;
73 import eu.etaxonomy.cdm.model.taxon.Synonym;
74 import eu.etaxonomy.cdm.model.taxon.SynonymType;
75 import eu.etaxonomy.cdm.model.taxon.Taxon;
76 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
77 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
78 import eu.etaxonomy.cdm.model.taxon.TaxonNodeAgentRelation;
79 import eu.etaxonomy.cdm.model.taxon.TaxonNodeStatus;
80 import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
81 import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
82 import eu.etaxonomy.cdm.model.term.DefinedTerm;
83 import eu.etaxonomy.cdm.persistence.dao.common.Restriction;
84 import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;
85 import eu.etaxonomy.cdm.persistence.dao.name.IHomotypicalGroupDao;
86 import eu.etaxonomy.cdm.persistence.dao.reference.IOriginalSourceDao;
87 import eu.etaxonomy.cdm.persistence.dao.reference.IReferenceDao;
88 import eu.etaxonomy.cdm.persistence.dao.taxon.IClassificationDao;
89 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeDao;
90 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeFilterDao;
91 import eu.etaxonomy.cdm.persistence.dto.HomotypicGroupDto;
92 import eu.etaxonomy.cdm.persistence.dto.TaxonNodeDto;
93 import eu.etaxonomy.cdm.persistence.permission.ICdmPermissionEvaluator;
94 import eu.etaxonomy.cdm.persistence.query.OrderHint;
95
96 /**
97 * @author n.hoffmann
98 * @since Apr 9, 2010
99 */
100 @Service
101 @Transactional(readOnly = true)
102 public class TaxonNodeServiceImpl
103 extends AnnotatableServiceBase<TaxonNode, ITaxonNodeDao>
104 implements ITaxonNodeService{
105
106 private static final Logger logger = LogManager.getLogger();
107
108 @Autowired
109 private IBeanInitializer defaultBeanInitializer;
110
111 @Autowired
112 private ITaxonService taxonService;
113
114 // @Autowired
115 // private IReferenceService referenceService;
116
117 @Autowired
118 private IDescriptiveDataSetService dataSetService;
119
120 @Autowired
121 private IAgentService agentService;
122
123 @Autowired
124 private INameService nameService;
125
126 @Autowired
127 private IOriginalSourceDao sourceDao;
128
129 @Autowired
130 private ITaxonNodeFilterDao nodeFilterDao;
131
132 @Autowired
133 private IReferenceDao referenceDao;
134
135 @Autowired
136 private IClassificationDao classificationDao;
137
138 @Autowired
139 private IHomotypicalGroupDao homotypicalGroupDao;
140
141 @Autowired
142 IProgressMonitorService progressMonitorService;
143
144 @Autowired
145 private ICdmPermissionEvaluator permissionEvaluator;
146
147 @Override
148 public List<TaxonNode> loadChildNodesOfTaxonNode(TaxonNode taxonNode,
149 List<String> propertyPaths, boolean recursive, boolean includeUnpublished,
150 TaxonNodeSortMode sortMode) {
151
152 taxonNode = load(taxonNode.getUuid());
153 List<TaxonNode> childNodes;
154 if (recursive == true){
155 Comparator<TaxonNode> comparator = sortMode == null? null : sortMode.comparator();
156 childNodes = dao.listChildrenOf(taxonNode, null, null, recursive, includeUnpublished, propertyPaths, comparator);
157 }else if (includeUnpublished){
158 childNodes = new ArrayList<>(taxonNode.getChildNodes());
159 }else{
160 childNodes = new ArrayList<>();
161 for (TaxonNode node:taxonNode.getChildNodes()){
162 if (node.getTaxon().isPublish()){
163 childNodes.add(node);
164 }
165 }
166 }
167
168 if (recursive == false && sortMode != null){
169 Comparator<TaxonNode> comparator = sortMode.comparator();
170 Collections.sort(childNodes, comparator);
171 }
172 defaultBeanInitializer.initializeAll(childNodes, propertyPaths);
173 return childNodes;
174 }
175
176 @Override
177 public List<TaxonNode> listChildrenOf(TaxonNode node, Integer pageSize, Integer pageIndex,
178 boolean recursive, boolean includeUnpublished, List<String> propertyPaths){
179 return dao.listChildrenOf(node, pageSize, pageIndex, recursive, includeUnpublished, propertyPaths, null);
180 }
181
182 @Override
183 public TaxonNodeDto getParentUuidAndTitleCache(ITaxonTreeNode child) {
184 UUID uuid = child.getUuid();
185 int id = child.getId();
186 TaxonNodeDto uuidAndTitleCache = new TaxonNodeDto(uuid, id, null);
187 return getParentUuidAndTitleCache(uuidAndTitleCache);
188 }
189
190 @Override
191 public TaxonNodeDto getParentUuidAndTitleCache(TaxonNodeDto child) {
192 return dao.getParentUuidAndTitleCache(child);
193 }
194
195 @Override
196 public List<TaxonNodeDto> listChildNodesAsTaxonNodeDto(TaxonNodeDto parent) {
197 return dao.listChildNodesAsTaxonNodeDto(parent);
198 }
199
200 @Override
201 public List<TaxonNodeDto> getUuidAndTitleCache(Integer limit, String pattern, UUID classificationUuid) {
202 return dao.getUuidAndTitleCache(limit, pattern, classificationUuid, true);
203 }
204
205 @Override
206 public List<TaxonNodeDto> listChildNodesAsTaxonNodeDto(ITaxonTreeNode parent) {
207 List<String> propertyPaths = new ArrayList<>();
208 propertyPaths.add("parent");
209 parent = dao.load(parent.getId(), propertyPaths);
210 TaxonNodeDto uuidAndTitleCache = new TaxonNodeDto(parent);
211 return listChildNodesAsTaxonNodeDto(uuidAndTitleCache);
212 }
213
214 @Override
215 public List<TaxonNodeDto> taxonNodeDtoParentRank(Classification classification, Rank rank, TaxonName name) {
216 return dao.getParentTaxonNodeDtoForRank(classification, rank, name);
217 }
218
219 @Override
220 public List<TaxonNodeDto> taxonNodeDtoParentRank(Classification classification, Rank rank, TaxonBase<?> taxonBase) {
221 return dao.getParentTaxonNodeDtoForRank(classification, rank, taxonBase);
222 }
223
224 @Override
225 public Pager<TaxonNodeDto> pageChildNodesDTOs(UUID taxonNodeUuid, boolean recursive, boolean includeUnpublished,
226 boolean doSynonyms, TaxonNodeSortMode sortMode,
227 Integer pageSize, Integer pageIndex) {
228
229 TaxonNode parentNode = dao.load(taxonNodeUuid);
230
231 List<CdmBase> allRecords = new ArrayList<>();
232
233 //acceptedTaxa
234 List<TaxonNode> childNodes = loadChildNodesOfTaxonNode(parentNode, null, recursive, includeUnpublished, sortMode);
235 allRecords.addAll(childNodes);
236
237 //add synonyms if pager is not yet full synonyms
238 if (doSynonyms){
239 List<Synonym> synList = new ArrayList<>(parentNode.getTaxon().getSynonyms());
240 Collections.sort(synList, new HomotypicGroupTaxonComparator(null));
241 //TODO: test sorting
242
243 allRecords.addAll(synList);
244 }
245
246 List<TaxonNodeDto> dtos = new ArrayList<>(pageSize==null?25:pageSize);
247 long totalCount = Long.valueOf(allRecords.size());
248
249 TaxonName parentName = null;
250
251 for(CdmBase item : PagerUtils.pageList(allRecords, pageIndex, pageSize)) {
252 if (item.isInstanceOf(TaxonNode.class)){
253 dtos.add(new TaxonNodeDto(CdmBase.deproxy(item, TaxonNode.class)));
254 }else if (item.isInstanceOf(Synonym.class)){
255 Synonym synonym = CdmBase.deproxy(item, Synonym.class);
256 parentName = parentName == null? parentNode.getTaxon().getName(): parentName;
257 boolean isHomotypic = synonym.getName().isHomotypic(parentName);
258 dtos.add(new TaxonNodeDto(synonym, isHomotypic));
259 }
260 }
261 return new DefaultPagerImpl<>(pageIndex, totalCount, pageSize , dtos);
262 }
263
264 @Override
265 public TaxonNodeDto parentDto(UUID taxonNodeUuid) {
266 if (taxonNodeUuid == null){
267 return null;
268 }
269 TaxonNode taxonNode = dao.load(taxonNodeUuid);
270 if(taxonNode.getParent() != null) {
271 return new TaxonNodeDto(taxonNode.getParent());
272 }
273 return null;
274 }
275
276 @Override
277 public TaxonNodeDto dto(UUID taxonNodeUuid) {
278 if (taxonNodeUuid == null){
279 return null;
280 }
281 return dao.getTaxonNodeDto(taxonNodeUuid);
282 }
283
284 @Override
285 public TaxonNodeDto dto(UUID taxonUuid, UUID classificationUuid) {
286 if (taxonUuid == null){
287 return null;
288 }
289 List<TaxonNodeDto> taxonNodes = dao.getTaxonNodeForTaxonInClassificationDto(taxonUuid, classificationUuid);
290 if (!taxonNodes.isEmpty()){
291 return taxonNodes.get(0);
292 }
293 return null;
294 }
295
296 @Override
297 @Autowired
298 protected void setDao(ITaxonNodeDao dao) {
299 this.dao = dao;
300 }
301
302 @Override
303 @Transactional(readOnly = false)
304 public DeleteResult makeTaxonNodeASynonymOfAnotherTaxonNode(TaxonNode oldTaxonNode, TaxonNode newAcceptedTaxonNode,
305 SynonymType synonymType, Reference citation, String microReference, SecReferenceHandlingEnum secHandling, boolean setNameInSource) {
306
307 // TODO at the moment this method only moves synonym-, concept relations and descriptions to the new accepted taxon
308 // in a future version we also want to move cdm data like annotations, marker, so., but we will need a policy for that
309 if (oldTaxonNode == null || newAcceptedTaxonNode == null || oldTaxonNode.getTaxon().getName() == null){
310 throw new IllegalArgumentException("A mandatory parameter was null.");
311 }
312
313 if(oldTaxonNode.equals(newAcceptedTaxonNode)){
314 throw new IllegalArgumentException("Taxon can not be made synonym of its own.");
315 }
316
317 Classification classification = oldTaxonNode.getClassification();
318 Taxon oldTaxon = HibernateProxyHelper.deproxy(oldTaxonNode.getTaxon());
319 Taxon newAcceptedTaxon = (Taxon)this.taxonService.find(newAcceptedTaxonNode.getTaxon().getUuid());
320 newAcceptedTaxon = HibernateProxyHelper.deproxy(newAcceptedTaxon);
321 // Move oldTaxon to newTaxon
322 //TaxonName synonymName = oldTaxon.getName();
323 TaxonName newSynonymName = CdmBase.deproxy(oldTaxon.getName());
324 HomotypicalGroup group = CdmBase.deproxy(newSynonymName.getHomotypicalGroup());
325 if (synonymType == null){
326 if (newSynonymName.isHomotypic(newAcceptedTaxon.getName())){
327 synonymType = SynonymType.HOMOTYPIC_SYNONYM_OF;
328 }else{
329 synonymType = SynonymType.HETEROTYPIC_SYNONYM_OF;
330 }
331 }
332
333 //set homotypic group
334 TaxonName newAcceptedTaxonName = HibernateProxyHelper.deproxy(newAcceptedTaxon.getName(), TaxonName.class);
335 newAcceptedTaxon.setName(newAcceptedTaxonName);
336 Reference secNewAccepted = newAcceptedTaxon.getSec();
337 Reference secOldAccepted = oldTaxon.getSec();
338 boolean uuidsEqual = (secNewAccepted != null && secOldAccepted != null && secNewAccepted.equals(secOldAccepted)) || (secNewAccepted == null && secOldAccepted == null);
339 Reference newSec = citation;
340 //keep when same only warns in ui, the sec still
341 if (secHandling != null && secHandling.equals(SecReferenceHandlingEnum.KeepOrWarn) ){
342 newSec = oldTaxon.getSec();
343 }
344 if (secHandling != null && secHandling.equals(SecReferenceHandlingEnum.AlwaysDelete)){
345 newSec = null;
346 }
347
348 Synonym newSyn = newAcceptedTaxon.addSynonymName(newSynonymName, newSec, microReference, synonymType);
349 if (newSec == null){
350 newSyn.setSec(newSec);
351 }
352 newSyn.setPublish(oldTaxon.isPublish());
353
354 // Move Synonyms to new Taxon
355 // From ticket 3163 we can move taxon with accepted name having homotypic synonyms
356 List<Synonym> synonymsInHomotypicalGroup = null;
357
358 //the synonyms of the homotypical group of the old taxon
359 if (synonymType.equals(SynonymType.HOMOTYPIC_SYNONYM_OF)){
360 synonymsInHomotypicalGroup = oldTaxon.getSynonymsInGroup(group);
361 }
362
363 Set<Synonym> syns = new HashSet<>(oldTaxon.getSynonyms());
364 for(Synonym synonym : syns){
365 SynonymType srt;
366 if(synonym.getHomotypicGroup()!= null
367 && synonym.getHomotypicGroup().equals(newAcceptedTaxonName.getHomotypicalGroup())) {
368 srt = SynonymType.HOMOTYPIC_SYNONYM_OF;
369 } else if(synonym.getType() != null && synonym.getType().equals(SynonymType.HOMOTYPIC_SYNONYM_OF)) {
370 if (synonymType.equals(SynonymType.HOMOTYPIC_SYNONYM_OF)){
371 srt = SynonymType.HOMOTYPIC_SYNONYM_OF;
372 } else{
373 srt = SynonymType.HETEROTYPIC_SYNONYM_OF;
374 }
375 } else {
376 if (synonymsInHomotypicalGroup != null && synonymsInHomotypicalGroup.contains(synonym)){
377 srt = SynonymType.HOMOTYPIC_SYNONYM_OF;
378 }else{
379 srt = synonym.getType();
380 }
381 }
382 if (secHandling != null && !secHandling.equals(SecReferenceHandlingEnum.KeepOrWarn)){
383 synonym.setSec(newSec);
384 }
385 newAcceptedTaxon.addSynonym(synonym, srt);
386 }
387
388 // CHILD NODES
389 if(oldTaxonNode.getChildNodes() != null && oldTaxonNode.getChildNodes().size() != 0){
390 List<TaxonNode> childNodes = new ArrayList<>();
391 for (TaxonNode childNode : oldTaxonNode.getChildNodes()){
392 childNodes.add(childNode);
393 }
394 for(TaxonNode childNode :childNodes){
395 newAcceptedTaxonNode.addChildNode(childNode, childNode.getReference(), childNode.getMicroReference()); // childNode.getSynonymToBeUsed()
396 }
397 }
398
399 //Move Taxon RelationShips to new Taxon
400 for(TaxonRelationship taxonRelationship : oldTaxon.getTaxonRelations()){
401 Taxon fromTaxon = HibernateProxyHelper.deproxy(taxonRelationship.getFromTaxon());
402 Taxon toTaxon = HibernateProxyHelper.deproxy(taxonRelationship.getToTaxon());
403 if (fromTaxon == oldTaxon){
404 newAcceptedTaxon.addTaxonRelation(taxonRelationship.getToTaxon(), taxonRelationship.getType(),
405 taxonRelationship.getCitation(), taxonRelationship.getCitationMicroReference());
406
407 }else if(toTaxon == oldTaxon){
408 fromTaxon.addTaxonRelation(newAcceptedTaxon, taxonRelationship.getType(),
409 taxonRelationship.getCitation(), taxonRelationship.getCitationMicroReference());
410 taxonService.saveOrUpdate(fromTaxon);
411
412 }else{
413 logger.warn("Taxon is not part of its own Taxonrelationship");
414 }
415 // Remove old relationships
416
417 fromTaxon.removeTaxonRelation(taxonRelationship);
418 toTaxon.removeTaxonRelation(taxonRelationship);
419 taxonRelationship.setToTaxon(null);
420 taxonRelationship.setFromTaxon(null);
421 }
422
423 //Move descriptions to new taxon
424 List<TaxonDescription> descriptions = new ArrayList<TaxonDescription>( oldTaxon.getDescriptions()); //to avoid concurrent modification errors (newAcceptedTaxon.addDescription() modifies also oldtaxon.descritpions())
425 for(TaxonDescription description : descriptions){
426 String message = "Description copied from former accepted taxon: %s (Old title: %s)";
427 message = String.format(message, oldTaxon.getTitleCache(), description.getTitleCache());
428 description.setTitleCache(message, true);
429 if (setNameInSource) {
430 for (DescriptionElementBase element: description.getElements()){
431 for (DescriptionElementSource source: element.getSources()){
432 if (source.getNameUsedInSource() == null){
433 source.setNameUsedInSource(newSynonymName);
434 }
435 }
436 }
437 }
438 //oldTaxon.removeDescription(description, false);
439 newAcceptedTaxon.addDescription(description);
440 }
441 oldTaxon.clearDescriptions();
442
443 taxonService.saveOrUpdate(newAcceptedTaxon);
444
445 taxonService.saveOrUpdate(oldTaxon);
446 taxonService.getSession().flush();
447
448 TaxonDeletionConfigurator conf = new TaxonDeletionConfigurator();
449 conf.setDeleteSynonymsIfPossible(false);
450 conf.setDeleteNameIfPossible(false);
451 DeleteResult taxonDeleteResult = taxonService.isDeletable(oldTaxon.getUuid(), conf);
452
453 DeleteResult result;
454 if (taxonDeleteResult.isOk()){
455 result = taxonService.deleteTaxon(oldTaxon.getUuid(), conf, classification.getUuid());
456 }else{
457 TaxonNodeDeletionConfigurator config = new TaxonNodeDeletionConfigurator();
458 config.setDeleteElement(false);
459 conf.setTaxonNodeConfig(config);
460 result = deleteTaxonNode(oldTaxonNode, conf);
461 result.getRelatedObjects().addAll(taxonDeleteResult.getRelatedObjects()); //we want to know what causes that the taxon can not be deleted
462 result.getExceptions().addAll(taxonDeleteResult.getExceptions()); //same for the exceptions
463 }
464
465 result.addUpdatedObject(newAcceptedTaxon);
466
467 //oldTaxonNode.delete();
468 return result;
469 }
470
471 @Override
472 @Transactional(readOnly = false)
473 public DeleteResult makeTaxonNodeSynonymsOfAnotherTaxonNode( Set<UUID> oldTaxonNodeUuids,
474 UUID newAcceptedTaxonNodeUUIDs,
475 SynonymType synonymType,
476 UUID citation,
477 String microReference,
478 SecReferenceHandlingEnum secHandling,
479 boolean setNameInSource) {
480 DeleteResult result = new DeleteResult();
481 for (UUID nodeUuid: oldTaxonNodeUuids) {
482 result.includeResult(makeTaxonNodeASynonymOfAnotherTaxonNode(nodeUuid, newAcceptedTaxonNodeUUIDs, synonymType, citation, microReference, secHandling, setNameInSource));
483 }
484 return result;
485 }
486
487 @Override
488 @Transactional(readOnly = false)
489 public DeleteResult makeTaxonNodeASynonymOfAnotherTaxonNode(UUID oldTaxonNodeUuid,
490 UUID newAcceptedTaxonNodeUUID,
491 SynonymType synonymType,
492 UUID citationUuid,
493 String microReference,
494 SecReferenceHandlingEnum secHandling,
495 boolean setNameInSource) {
496
497 TaxonNode oldTaxonNode = dao.load(oldTaxonNodeUuid);
498 TaxonNode oldTaxonParentNode = oldTaxonNode.getParent();
499 TaxonNode newTaxonNode = dao.load(newAcceptedTaxonNodeUUID);
500 Reference citation = referenceDao.load(citationUuid);
501
502 switch (secHandling){
503 case AlwaysDelete:
504 citation = null;
505 break;
506 case UseNewParentSec:
507 citation = newTaxonNode.getTaxon() != null? newTaxonNode.getTaxon().getSec(): null;
508 break;
509 case KeepOrWarn:
510
511 Reference synSec = oldTaxonNode.getTaxon().getSec();
512 if (synSec != null ){
513 citation = CdmBase.deproxy(synSec);
514 }
515 break;
516 case KeepOrSelect:
517
518 break;
519 default:
520 break;
521 }
522
523 DeleteResult result = makeTaxonNodeASynonymOfAnotherTaxonNode(oldTaxonNode,
524 newTaxonNode,
525 synonymType,
526 citation,
527 microReference,
528 secHandling, setNameInSource);
529
530 result.addUpdatedCdmId(CdmEntityIdentifier.NewInstance(oldTaxonParentNode));
531 result.addUpdatedCdmId(CdmEntityIdentifier.NewInstance(newTaxonNode));
532 result.setCdmEntity(oldTaxonParentNode);
533 return result;
534 }
535
536 @Override
537 @Transactional(readOnly = false)
538 public DeleteResult deleteTaxonNodes(List<TaxonNode> list, TaxonDeletionConfigurator config) {
539
540 if (config == null){
541 config = new TaxonDeletionConfigurator();
542 }
543 DeleteResult result = new DeleteResult();
544 Classification classification = null;
545 List<TaxonNode> taxonNodes = new ArrayList<>(list);
546
547 for (TaxonNode treeNode:taxonNodes){
548 if (treeNode != null){
549
550 TaxonNode taxonNode;
551 taxonNode = CdmBase.deproxy(treeNode);
552 TaxonNode parent = taxonNode.getParent();
553 //check whether the node has children or the children are already deleted
554 if(taxonNode.hasChildNodes()) {
555 List<TaxonNode> children = new ArrayList<> ();
556 List<TaxonNode> childNodesList = taxonNode.getChildNodes();
557 children.addAll(childNodesList);
558 //To avoid NPE when child is also in list of taxonNodes, remove it from the list
559 Iterator<TaxonNode> it = taxonNodes.iterator();
560 for (TaxonNode child: children) {
561 while (it.hasNext()) {
562 if (it.next().equals(child)) {
563 it.remove();
564 }
565 }
566 }
567 int compare = config.getTaxonNodeConfig().getChildHandling().compareTo(ChildHandling.DELETE);
568 boolean childHandling = (compare == 0)? true: false;
569 if (childHandling){
570 boolean changeDeleteTaxon = false;
571 if (!config.getTaxonNodeConfig().isDeleteTaxon()){
572 config.getTaxonNodeConfig().setDeleteTaxon(true);
573 changeDeleteTaxon = true;
574 }
575 DeleteResult resultNodes = deleteTaxonNodes(children, config);
576 if (!resultNodes.isOk()){
577 result.addExceptions(resultNodes.getExceptions());
578 result.setStatus(resultNodes.getStatus());
579 }
580 if (changeDeleteTaxon){
581 config.getTaxonNodeConfig().setDeleteTaxon(false);
582 }
583
584 } else {
585 //move the children to the parent
586
587 for (TaxonNode child: childNodesList){
588 parent.addChildNode(child, child.getReference(), child.getMicroReference());
589 }
590 }
591 }
592
593 classification = taxonNode.getClassification();
594
595 if (classification.getRootNode().equals(taxonNode)){
596 classification.removeRootNode();
597 classification = null;
598 }else if (classification.getChildNodes().contains(taxonNode)){
599 Taxon taxon = taxonNode.getTaxon();
600 classification.deleteChildNode(taxonNode);
601
602 //node is rootNode
603 if (taxon != null){
604
605 if (config.getTaxonNodeConfig().isDeleteTaxon()){
606 taxonService.saveOrUpdate(taxon);
607 saveOrUpdate(taxonNode);
608
609 TaxonDeletionConfigurator configNew = new TaxonDeletionConfigurator();
610 configNew.setClassificationUuid(classification.getUuid());
611 DeleteResult resultTaxon = taxonService.deleteTaxon(taxon.getUuid(), configNew, classification.getUuid());
612 if (!resultTaxon.isOk()){
613 result.addExceptions(resultTaxon.getExceptions());
614 result.setStatus(resultTaxon.getStatus());
615 }
616
617 }
618 }
619 classification = null;
620
621 } else {
622 //classification = null;
623 Taxon taxon = taxonNode.getTaxon();
624 taxon = CdmBase.deproxy(taxon);
625 if (taxon != null){
626 taxon.removeTaxonNode(taxonNode);
627 if (config.getTaxonNodeConfig().isDeleteTaxon()){
628 TaxonDeletionConfigurator configNew = new TaxonDeletionConfigurator();
629 saveOrUpdate(taxonNode);
630 taxonService.saveOrUpdate(taxon);
631 DeleteResult resultTaxon = taxonService.deleteTaxon(taxon.getUuid(), configNew, classification.getUuid());
632
633 if (!resultTaxon.isOk()){
634 result.addExceptions(resultTaxon.getExceptions());
635 result.setStatus(resultTaxon.getStatus());
636 }
637 }
638 }
639
640 }
641
642 result.addUpdatedObject(parent);
643 if(result.getCdmEntity() == null){
644 result.setCdmEntity(taxonNode);
645 }
646 UUID uuid = dao.delete(taxonNode);
647 logger.debug("Deleted node " +uuid.toString());
648
649 }
650 }
651 /*if (classification != null){
652 result.addUpdatedObject(classification);
653 DeleteResult resultClassification = classService.delete(classification);
654 if (!resultClassification.isOk()){
655 result.addExceptions(resultClassification.getExceptions());
656 result.setStatus(resultClassification.getStatus());
657 }
658 }*/
659 return result;
660 }
661
662
663 @Override
664 @Transactional(readOnly = false)
665 public DeleteResult deleteTaxonNodes(Collection<UUID> nodeUuids, TaxonDeletionConfigurator config) {
666 List<TaxonNode> nodes = new ArrayList<>();
667 for(UUID nodeUuid : nodeUuids) {
668 nodes.add(dao.load(nodeUuid));
669 }
670 return deleteTaxonNodes(nodes, config);
671 }
672
673
674 @Override
675 @Transactional(readOnly = false)
676 public DeleteResult deleteTaxonNode(UUID nodeUUID, TaxonDeletionConfigurator config) {
677
678 TaxonNode node = CdmBase.deproxy(dao.load(nodeUUID));
679 return deleteTaxonNode(node, config);
680 }
681
682 @Override
683 @Transactional(readOnly = false)
684 public DeleteResult deleteTaxonNode(TaxonNode node, TaxonDeletionConfigurator config) {
685 DeleteResult result = new DeleteResult();
686 if (node == null){
687 result.setAbort();
688 result.addException(new Exception("The TaxonNode was already deleted."));
689 return result;
690 }
691 Taxon taxon = null;
692 try{
693 taxon = HibernateProxyHelper.deproxy(node.getTaxon());
694 }catch(NullPointerException e){
695 result.setAbort();
696 result.addException(new Exception("The Taxon was already deleted."));
697 }
698
699 TaxonNode parent = HibernateProxyHelper.deproxy(node.getParent(), TaxonNode.class);
700 if (config == null){
701 config = new TaxonDeletionConfigurator();
702 }
703
704 if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.MOVE_TO_PARENT)){
705 Object[] children = node.getChildNodes().toArray();
706 TaxonNode childNode;
707 for (Object child: children){
708 childNode = (TaxonNode) child;
709 parent.addChildNode(childNode, childNode.getReference(), childNode.getMicroReference());
710 }
711 }else{
712 DeleteResult tmpResult = deleteTaxonNodes(node.getChildNodes(), config);
713 result.includeResult(tmpResult);
714 }
715
716 //remove node from DescriptiveDataSet
717 commonService.getReferencingObjects(node).stream()
718 .filter(obj->obj instanceof DescriptiveDataSet)
719 .forEach(dataSet->{
720 ((DescriptiveDataSet)dataSet).removeTaxonSubtree(node);
721 dataSetService.saveOrUpdate((DescriptiveDataSet) dataSet);
722 });
723
724 if (taxon != null){
725 if (config.getTaxonNodeConfig().isDeleteTaxon() && (config.isDeleteInAllClassifications() || taxon.getTaxonNodes().size() == 1)){
726 result = taxonService.deleteTaxon(taxon.getUuid(), config, node.getClassification().getUuid());
727 result.addUpdatedObject(parent);
728 if (result.isOk()){
729 return result;
730 }
731 } else {
732 result.addUpdatedObject(taxon);
733 }
734 }
735 result.setCdmEntity(node);
736 boolean success = true;
737 if (taxon != null){
738 success = taxon.removeTaxonNode(node);
739 taxonService.saveOrUpdate(taxon);
740 }
741 dao.saveOrUpdate(parent);
742
743 result.addUpdatedObject(parent);
744
745 if (success){
746 result.setStatus(Status.OK);
747 if (parent != null){
748 parent = HibernateProxyHelper.deproxy(parent, TaxonNode.class);
749 int index = parent.getChildNodes().indexOf(node);
750 if (index > -1){
751 parent.removeChild(index);
752 }
753 }
754 if (!dao.delete(node, config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)).equals(null)){
755 result.getUpdatedObjects().remove(node);
756 result.addDeletedObject(node);
757 return result;
758 } else {
759 result.setError();
760 return result;
761 }
762 }else{
763 if (dao.findByUuid(node.getUuid()) != null){
764 result.setError();
765 result.addException(new Exception("The node can not be removed from the taxon."));
766 }
767 return result;
768 }
769 }
770
771
772 @Override
773 public List<TaxonNode> listAllNodesForClassification(Classification classification, Integer start, Integer end) {
774 return dao.getTaxonOfAcceptedTaxaByClassification(classification, start, end);
775 }
776
777 @Override
778 public int countAllNodesForClassification(Classification classification) {
779 return dao.countTaxonOfAcceptedTaxaByClassification(classification);
780 }
781
782 @Override
783 @Transactional
784 public UpdateResult moveTaxonNode(UUID taxonNodeUuid, UUID targetNodeUuid, int movingType, SecReferenceHandlingEnum secHandling, UUID secUuid){
785 TaxonNode taxonNode = HibernateProxyHelper.deproxy(dao.load(taxonNodeUuid));
786 TaxonNode targetNode = HibernateProxyHelper.deproxy(dao.load(targetNodeUuid));
787 Reference sec = null;
788 if (secUuid != null){
789 sec = HibernateProxyHelper.deproxy(referenceDao.load(secUuid));
790 }
791 UpdateResult result = moveTaxonNode(taxonNode, targetNode, movingType, secHandling, sec);
792 return result;
793 }
794
795 @Override
796 @Transactional
797 public UpdateResult moveTaxonNode(TaxonNode taxonNode, TaxonNode newParent, int movingType, SecReferenceHandlingEnum secHandling, Reference sec){
798 UpdateResult result = new UpdateResult();
799
800 TaxonNode parentParent = HibernateProxyHelper.deproxy(newParent.getParent());
801 Integer sortIndex = -1;
802 if (movingType == 0){
803 sortIndex = 0;
804 }else if (movingType == 1){
805 sortIndex = newParent.getSortIndex();
806 newParent = parentParent;
807 } else if (movingType == 2){
808 sortIndex = newParent.getSortIndex() +1;
809 newParent = parentParent;
810 } else{
811 result.setAbort();
812 result.addException(new Exception("The moving type "+ movingType +" is not supported."));
813 }
814
815 if (secHandling.equals(SecReferenceHandlingEnum.AlwaysSelect) || (secHandling.equals(SecReferenceHandlingEnum.KeepOrSelect) && sec != null)){
816 if (taxonNode.getTaxon() != null){
817 taxonNode.getTaxon().setSec(sec);
818 }
819 }else if (secHandling.equals(SecReferenceHandlingEnum.AlwaysDelete)){
820 if (taxonNode.getTaxon() != null){
821 taxonNode.getTaxon().setSec(null);
822 }
823 }else if (secHandling.equals(SecReferenceHandlingEnum.UseNewParentSec)){
824 if (taxonNode.getTaxon() != null && newParent.getTaxon()!= null){
825 taxonNode.getTaxon().setSec(newParent.getTaxon().getSec());
826 }
827 }
828
829 taxonNode = newParent.addChildNode(taxonNode, sortIndex, taxonNode.getReference(), taxonNode.getMicroReference());
830 result.addUpdatedObject(taxonNode);
831
832 return result;
833 }
834
835 @Override
836 @Transactional
837 public UpdateResult moveTaxonNodes(Set<UUID> taxonNodeUuids, UUID newParentNodeUuid, int movingType, SecReferenceHandlingEnum secHandling, UUID secUuid, IProgressMonitor monitor){
838
839 if (monitor == null){
840 monitor = DefaultProgressMonitor.NewInstance();
841 }
842 UpdateResult result = new UpdateResult();
843 List<String> taxonNodePropertyPath = new ArrayList<>();
844 taxonNodePropertyPath.add("taxon.secSource.*");
845 taxonNodePropertyPath.add("parent.taxon.secSource.*");
846 TaxonNode targetNode = dao.load(newParentNodeUuid, taxonNodePropertyPath);
847 List<TaxonNode> nodes = dao.list(taxonNodeUuids, null, null, null, null);
848 Reference sec = referenceDao.load(secUuid);
849
850 monitor.beginTask("Move Taxonnodes", nodes.size()*2);
851 monitor.subTask("move taxon nodes");
852 for (TaxonNode node: nodes){
853 if (!monitor.isCanceled()){
854 if (!nodes.contains(node.getParent())){
855 result.includeResult(moveTaxonNode(node, targetNode, movingType, secHandling, sec));
856 }
857 monitor.worked(1);
858 }else{
859 monitor.done();
860 result.setAbort();
861 break;
862 }
863 }
864 if (!monitor.isCanceled()){
865 monitor.subTask("saving and reindex");
866 dao.saveOrUpdateAll(nodes);
867 }else{
868 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
869 }
870
871 monitor.done();
872 return result;
873 }
874
875 @Override
876 public Pager<TaxonNodeAgentRelation> pageTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid,
877 UUID agentUuid, UUID rankUuid, UUID relTypeUuid, Integer pageSize, Integer pageIndex, List<String> propertyPaths) {
878
879 List<TaxonNodeAgentRelation> records = null;
880
881 long count = dao.countTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid);
882 if(PagerUtils.hasResultsInRange(count, pageIndex, pageSize)) {
883 records = dao.listTaxonNodeAgentRelations(taxonUuid, classificationUuid,
884 agentUuid, rankUuid, relTypeUuid, PagerUtils.startFor(pageSize, pageIndex), PagerUtils.limitFor(pageSize), propertyPaths);
885 }
886
887 Pager<TaxonNodeAgentRelation> pager = new DefaultPagerImpl<>(pageIndex, count, pageSize, records);
888 return pager;
889 }
890
891 @Override
892 @Transactional
893 public UpdateResult createNewTaxonNode(UUID parentNodeUuid, CreateTaxonDTO taxonDto,
894 NamedSource source, String microref,
895 TaxonNodeStatus status, Map<Language,LanguageString> statusNote){
896
897 UpdateResult result = new UpdateResult();
898 TaxonNode child = null;
899 TaxonNode parent = null;
900 try{
901 TaxonName name = null;
902 Taxon taxon = null;
903 if (taxonDto.getTaxonUuid() != null){
904 taxon = (Taxon) taxonService.load(taxonDto.getTaxonUuid());
905 if (taxon == null){
906 throw new RuntimeException("Taxon for not found for id " + taxonDto.getTaxonUuid());
907 }
908 }else{
909 if (taxonDto.getNameUuid() != null){
910 name = nameService.load(taxonDto.getNameUuid());
911 if (name == null){
912 throw new RuntimeException("Taxon name not found for id " + taxonDto.getTaxonUuid());
913 }
914 } else {
915 UpdateResult tmpResult = nameService.parseName(taxonDto.getTaxonNameString(),
916 taxonDto.getCode(), taxonDto.getPreferredRank(), true);
917 result.addUpdatedObjects(tmpResult.getUpdatedObjects());
918 name = (TaxonName)tmpResult.getCdmEntity();
919 }
920 Reference sec = null;
921 if (taxonDto.getSecUuid() != null ){
922 sec = referenceDao.load(taxonDto.getSecUuid());
923 }
924 if (name != null && !name.isPersited()){
925 for (HybridRelationship rel : name.getHybridChildRelations()){
926 if (!rel.getHybridName().isPersited()) {
927 nameService.save(rel.getHybridName());
928 }
929 if (!rel.getParentName().isPersited()) {
930 nameService.save(rel.getParentName());
931 }
932 }
933 }
934 taxon = Taxon.NewInstance(name, sec);
935 taxon.setPublish(taxonDto.isPublish());
936 }
937
938 parent = dao.load(parentNodeUuid);
939 if (source != null){
940 if (source.isPersited()){
941 source = (NamedSource) sourceDao.load(source.getUuid());
942 }
943 if (source.getCitation() != null){
944 source.setCitation(referenceDao.load(source.getCitation().getUuid()));
945 }
946 if (source.getNameUsedInSource() !=null){
947 source.setNameUsedInSource(nameService.load(source.getNameUsedInSource().getUuid()));
948 }
949 }
950
951 child = parent.addChildTaxon(taxon, source);
952 child.setStatus(status);
953
954 if (statusNote != null){
955 child.getStatusNote().putAll(statusNote);
956 }
957
958 }catch(Exception e){
959 result.addException(e);
960 result.setError();
961 return result;
962 }
963 child = dao.save(child);
964
965 result.addUpdatedObject(parent);
966 if (child != null){
967 result.setCdmEntity(child);
968 }
969 return result;
970 }
971
972 @Override
973 @Transactional
974 public UpdateResult addTaxonNodeAgentRelation(UUID taxonNodeUUID, UUID agentUUID, DefinedTerm relationshipType){
975 UpdateResult result = new UpdateResult();
976 TaxonNode node = dao.load(taxonNodeUUID);
977 TeamOrPersonBase<?> agent = (TeamOrPersonBase<?>) agentService.load(agentUUID);
978 node.addAgentRelation(relationshipType, agent);
979 try{
980 dao.merge(node, true);
981 }catch (Exception e){
982 result.setError();
983 result.addException(e);
984 }
985 result.setCdmEntity(node);
986 return result;
987 }
988
989 @Override
990 @Transactional(readOnly=false)
991 public UpdateResult setSecundumForSubtree(SecundumForSubtreeConfigurator config) {
992 UpdateResult result = new UpdateResult();
993 IProgressMonitor monitor = config.getMonitor();
994
995 if (monitor == null){
996 monitor = DefaultProgressMonitor.NewInstance();
997 }
998 monitor.beginTask("Update secundum reference for subtree", 100);
999 monitor.subTask("Check start conditions");
1000
1001 if (config.getSubtreeUuid() == null){
1002 result.setError();
1003 result.addException(new NullPointerException("No subtree given"));
1004 monitor.done();
1005 return result;
1006 }
1007 monitor.worked(1);
1008 TaxonNode subTree = load(config.getSubtreeUuid());
1009 if (subTree == null){
1010 result.setError();
1011 result.addException(new NullPointerException("Subtree does not exist"));
1012 monitor.done();
1013 return result;
1014 }
1015 monitor.worked(1);
1016
1017 Reference newSec = null;
1018 if (config.getNewSecundum() != null){
1019 newSec = referenceDao.load(config.getNewSecundum().getUuid());
1020 if (newSec == null){
1021 result.setError();
1022 result.addException(new NullPointerException("New secundum reference does not exist"));
1023 monitor.done();
1024 return result;
1025 }
1026 }
1027 monitor.worked(1);
1028
1029 monitor.subTask("Count records");
1030 try {
1031 boolean includeRelatedTaxa = config.isIncludeProParteSynonyms() || config.isIncludeMisapplications();
1032
1033 TreeIndex subTreeIndex = TreeIndex.NewInstance(subTree.treeIndex());
1034 int count = config.isIncludeAcceptedTaxa() ? dao.countSecundumForSubtreeAcceptedTaxa(subTreeIndex, newSec, config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail()):0;
1035 monitor.worked(2);
1036 count += config.isIncludeSynonyms() ? dao.countSecundumForSubtreeSynonyms(subTreeIndex, newSec, config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail()) :0;
1037 monitor.worked(3);
1038 count += includeRelatedTaxa ? dao.countSecundumForSubtreeRelations(subTreeIndex, newSec, config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail()):0;
1039 monitor.worked(2);
1040 if (monitor.isCanceled()){
1041 return result;
1042 }
1043
1044 SubProgressMonitor subMonitor = SubProgressMonitor.NewStarted(monitor, 90, "Updating secundum for subtree", count * 2); //*2 1 tick for update and 1 tick for commit
1045 //Reference ref = config.getNewSecundum();
1046 if (config.isIncludeAcceptedTaxa()){
1047 monitor.subTask("Update Accepted Taxa");
1048 Set<CdmBase> updatedTaxa = dao.setSecundumForSubtreeAcceptedTaxa(subTreeIndex, newSec,
1049 config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail(), subMonitor);
1050 result.addUpdatedObjects(updatedTaxa);
1051 if (monitor.isCanceled()){
1052 return result;
1053 }
1054 }
1055 if (config.isIncludeSynonyms()){
1056 monitor.subTask("Update Synonyms");
1057 Set<CdmBase> updatedSynonyms = dao.setSecundumForSubtreeSynonyms(subTreeIndex, newSec,
1058 config.isOverwriteExisting(), config.isIncludeSharedTaxa() , config.isEmptySecundumDetail(), subMonitor);
1059 result.addUpdatedObjects(updatedSynonyms);
1060 if (monitor.isCanceled()){
1061 return result;
1062 }
1063 }
1064 if (includeRelatedTaxa){
1065 monitor.subTask("Update Related Taxa");
1066 Set<UUID> relationTypes = getRelationTypesForSubtree(config);
1067 Set<CdmBase> updatedRels = dao.setSecundumForSubtreeRelations(subTreeIndex, newSec,
1068 relationTypes, config.isOverwriteExisting(), config.isIncludeSharedTaxa() , config.isEmptySecundumDetail(), subMonitor);
1069 result.addUpdatedObjects(updatedRels);
1070 if (monitor.isCanceled()){
1071 return result;
1072 }
1073 }
1074 } catch (Exception e) {
1075 result.setError();
1076 result.addException(e);
1077 }
1078 monitor.done();
1079 return result;
1080 }
1081
1082 @Override
1083 @Transactional(readOnly=false)
1084 public UpdateResult setPublishForSubtree(PublishForSubtreeConfigurator config){
1085 UpdateResult result = new UpdateResult();
1086 IProgressMonitor monitor = config.getMonitor();
1087 if (monitor == null){
1088 monitor = DefaultProgressMonitor.NewInstance();
1089 }
1090 monitor.beginTask("Update publish flag for subtree", 100);
1091 monitor.subTask("Check start conditions");
1092
1093 if (config.getSubtreeUuid() == null){
1094 result.setError();
1095 result.addException(new NullPointerException("No subtree given"));
1096 monitor.done();
1097 return result;
1098 }
1099 monitor.worked(1);
1100
1101 TaxonNode subTree = find(config.getSubtreeUuid());
1102 if (subTree == null){
1103 result.setError();
1104 result.addException(new NullPointerException("Subtree does not exist"));
1105 monitor.done();
1106 return result;
1107 }
1108 monitor.worked(1);
1109
1110 monitor.subTask("Count records");
1111 boolean includeAcceptedTaxa = config.isIncludeAcceptedTaxa();
1112 boolean publish = config.isPublish();
1113 boolean includeSynonyms = config.isIncludeSynonyms();
1114 boolean includeSharedTaxa = config.isIncludeSharedTaxa();
1115 boolean includeHybrids = config.isIncludeHybrids();
1116 boolean includeRelatedTaxa = config.isIncludeProParteSynonyms() || config.isIncludeMisapplications();
1117 try {
1118 TreeIndex subTreeIndex = TreeIndex.NewInstance(subTree.treeIndex());
1119 int count = includeAcceptedTaxa ? dao.countPublishForSubtreeAcceptedTaxa(subTreeIndex, publish, includeSharedTaxa, includeHybrids):0;
1120 monitor.worked(3);
1121 count += includeSynonyms ? dao.countPublishForSubtreeSynonyms(subTreeIndex, publish, includeSharedTaxa, includeHybrids):0;
1122 monitor.worked(3);
1123 count += includeRelatedTaxa ? dao.countPublishForSubtreeRelatedTaxa(subTreeIndex, publish, includeSharedTaxa, includeHybrids):0;
1124 monitor.worked(2);
1125 if (monitor.isCanceled()){
1126 return result;
1127 }
1128
1129 SubProgressMonitor subMonitor = SubProgressMonitor.NewStarted(monitor, 90, "Updating secundum for subtree", count);
1130 if (includeAcceptedTaxa){
1131 monitor.subTask("Update Accepted Taxa");
1132 @SuppressWarnings("rawtypes")
1133 Set<TaxonBase> updatedTaxa = dao.setPublishForSubtreeAcceptedTaxa(subTreeIndex,
1134 publish, includeSharedTaxa, includeHybrids, subMonitor);
1135 result.addUpdatedObjects(updatedTaxa);
1136 if (monitor.isCanceled()){
1137 return result;
1138 }
1139 }
1140 if (includeSynonyms){
1141 monitor.subTask("Update Synonyms");
1142 @SuppressWarnings("rawtypes")
1143 Set<TaxonBase> updatedSynonyms = dao.setPublishForSubtreeSynonyms(subTreeIndex,
1144 publish, includeSharedTaxa, includeHybrids, subMonitor);
1145 result.addUpdatedObjects(updatedSynonyms);
1146 if (monitor.isCanceled()){
1147 return result;
1148 }
1149 }
1150 if (includeRelatedTaxa){
1151 monitor.subTask("Update Related Taxa");
1152 Set<UUID> relationTypes = getRelationTypesForSubtree(config);
1153 if (config.isIncludeMisapplications()){
1154 relationTypes.addAll(TaxonRelationshipType.misappliedNameUuids());
1155 }
1156 if (config.isIncludeProParteSynonyms()){
1157 relationTypes.addAll(TaxonRelationshipType.proParteOrPartialSynonymUuids());
1158 }
1159 @SuppressWarnings("rawtypes")
1160 Set<TaxonBase> updatedTaxa = dao.setPublishForSubtreeRelatedTaxa(subTreeIndex, publish,
1161 relationTypes, includeSharedTaxa, includeHybrids, subMonitor);
1162 result.addUpdatedObjects(updatedTaxa);
1163 if (monitor.isCanceled()){
1164 return result;
1165 }
1166 }
1167 } catch (Exception e) {
1168 result.setError();
1169 result.addException(e);
1170 }
1171
1172 monitor.done();
1173 return result;
1174 }
1175
1176 private Set<UUID> getRelationTypesForSubtree(ForSubtreeConfiguratorBase config) {
1177 Set<UUID> relationTypes = new HashSet<>();
1178 if (config.isIncludeMisapplications()){
1179 relationTypes.addAll(TaxonRelationshipType.misappliedNameUuids());
1180 }
1181 if (config.isIncludeProParteSynonyms()){
1182 relationTypes.addAll(TaxonRelationshipType.proParteOrPartialSynonymUuids());
1183 }
1184 return relationTypes;
1185 }
1186
1187 @Override
1188 public long count(TaxonNodeFilter filter){
1189 return nodeFilterDao.count(filter);
1190 }
1191
1192 @Override
1193 public List<UUID> uuidList(TaxonNodeFilter filter){
1194 return nodeFilterDao.listUuids(filter);
1195 }
1196
1197 @Override
1198 public List<Integer> idList(TaxonNodeFilter filter){
1199 return nodeFilterDao.idList(filter);
1200 }
1201
1202 @Override
1203 public TaxonNodeDto findCommonParentDto(Collection<TaxonNodeDto> nodes) {
1204 TaxonNodeDto commonParent = null;
1205 List<String> treePath = null;
1206 for (TaxonNodeDto nodeDto : nodes) {
1207 if (nodeDto == null){
1208 continue;
1209 }
1210 String nodeTreeIndex = nodeDto.getTreeIndex();
1211 nodeTreeIndex = nodeTreeIndex.replaceFirst("#", "");
1212 String[] split = nodeTreeIndex.split("#");
1213 if(treePath == null){
1214 treePath = Arrays.asList(split);
1215 }
1216 else{
1217 List<String> match = new ArrayList<>();
1218 for(int i=0;i<treePath.size();i++){
1219 if(i>=split.length){
1220 //current tree index is shorter so break
1221 break;
1222 }
1223 else if(split[i].equals(treePath.get(i))){
1224 //match found
1225 match.add(treePath.get(i));
1226 }
1227 else{
1228 //first mismatch found
1229 break;
1230 }
1231 }
1232 treePath = match;
1233 if(treePath.isEmpty()){
1234 //no common parent found for at least two nodes
1235 //-> they belong to a different classification
1236 break;
1237 }
1238 }
1239 }
1240 if(treePath!=null && !treePath.isEmpty()) {
1241 //get last index
1242 int nodeId = Integer.parseInt(treePath.get(treePath.size()-1));
1243 TaxonNode taxonNode = dao.load(nodeId, null);
1244 commonParent = new TaxonNodeDto(taxonNode);
1245 }
1246 return commonParent;
1247 }
1248
1249 @Override
1250 public List<TaxonDistributionDTO> getTaxonDistributionDTO(List<UUID> nodeUuids, List<String> propertyPaths,
1251 Authentication authentication, boolean openChildren, TaxonNodeSortMode sortMode){
1252
1253 nodeUuids = nodeUuids.stream().distinct().collect(Collectors.toList());
1254 List<TaxonNode> nodes = new ArrayList<>();
1255
1256 List<TaxonNode> parentNodes = load(nodeUuids, propertyPaths);
1257 if (sortMode != null){
1258 parentNodes.sort(sortMode.comparator());
1259 }
1260 if (openChildren){
1261 //TODO we could remove nodes which are children of other nodes in parentNodes list here as they are duplicates
1262 for (TaxonNode node: parentNodes){
1263 if (node == null || nodes.contains(node)){
1264 continue;
1265 }
1266 nodes.add(node);
1267 List<TaxonNode> children = new ArrayList<>();
1268 children.addAll(loadChildNodesOfTaxonNode(node,
1269 propertyPaths, true, true, sortMode));
1270 for (TaxonNode child: children){
1271 if (!nodes.contains(child)){
1272 nodes.add(child);
1273 }
1274 }
1275 }
1276 }else{
1277 nodes.addAll(nodes);
1278 }
1279
1280 List<TaxonDistributionDTO> result = new ArrayList<>();
1281 boolean hasPermission = false;
1282 for(TaxonNode node: nodes){
1283 if (authentication != null ) {
1284 hasPermission = permissionEvaluator.hasPermission(authentication, node, Operation.UPDATE);
1285 }else {
1286 hasPermission = true;
1287 }
1288 if (node.getTaxon() != null && hasPermission){
1289 try{
1290 TaxonDistributionDTO dto = new TaxonDistributionDTO(node);
1291 result.add(dto);
1292 }catch(Exception e){
1293 logger.error(e.getMessage(), e);
1294 }
1295 }
1296 }
1297 return result;
1298 }
1299
1300 @Override
1301 public <S extends TaxonNode> Pager<S> page(Class<S> clazz, List<Restriction<?>> restrictions, Integer pageSize,
1302 Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths) {
1303 return page(clazz, restrictions, pageSize, pageIndex, orderHints, propertyPaths, INCLUDE_UNPUBLISHED);
1304 }
1305
1306 @Override
1307 public <S extends TaxonNode> Pager<S> page(Class<S> clazz, List<Restriction<?>> restrictions, Integer pageSize,
1308 Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths, boolean includeUnpublished) {
1309
1310 List<S> records;
1311 long resultSize = dao.count(clazz, restrictions);
1312 if(AbstractPagerImpl.hasResultsInRange(resultSize, pageIndex, pageSize)){
1313 records = dao.list(clazz, restrictions, pageSize, pageIndex, orderHints, propertyPaths, includeUnpublished);
1314 } else {
1315 records = new ArrayList<>();
1316 }
1317 Pager<S> pager = new DefaultPagerImpl<>(pageIndex, resultSize, pageSize, records);
1318 return pager;
1319 }
1320
1321 @Override
1322 public List<TaxonDistributionDTO> getTaxonDistributionDTO(List<UUID> nodeUuids,
1323 List<String> propertyPaths, boolean openChildren) {
1324 return getTaxonDistributionDTO(nodeUuids, propertyPaths, null, openChildren, null);
1325 }
1326
1327 @Override
1328 @Transactional(readOnly = false)
1329 public UpdateResult cloneSubtree(SubtreeCloneConfigurator config) {
1330 UpdateResult result = new UpdateResult();
1331
1332 if (config.getSubTreeUuids().isEmpty()){
1333 return result;
1334 }
1335
1336 //TODO error handling
1337 Reference taxonSecundum = config.isReuseTaxa() || config.isReuseTaxonSecundum() || config.getTaxonSecundumUuid() == null ?
1338 null : referenceDao.findByUuid(config.getTaxonSecundumUuid());
1339 config.setTaxonSecundum(taxonSecundum);
1340
1341 Reference parentChildReference = config.isReuseParentChildReference() || config.getParentChildReferenceUuid() == null ?
1342 null : referenceDao.findByUuid(config.getParentChildReferenceUuid());
1343 config.setParentChildReference(parentChildReference);
1344
1345 Reference taxonRelationshipReference = config.getRelationTypeToOldTaxon() == null ?
1346 null : referenceDao.findByUuid(config.getRelationshipReferenceUuid());
1347 config.setRelationshipReference(taxonRelationshipReference);
1348
1349 Classification classificationClone = Classification.NewInstance(config.getNewClassificationName());
1350
1351 if (config.isReuseClassificationReference()){
1352 TaxonNode anyNode = dao.findByUuid(config.getSubTreeUuids().iterator().next());
1353 if (anyNode != null){
1354 Reference oldClassificationRef = anyNode.getClassification().getReference();
1355 classificationClone.setReference(oldClassificationRef);
1356 }
1357 }else if (config.getClassificationReferenceUuid() != null) {
1358 Reference classificationReference = referenceDao.findByUuid(config.getClassificationReferenceUuid());
1359 classificationClone.setReference(classificationReference);
1360 }
1361
1362 //clone taxa and taxon nodes
1363 // List<Integer> childNodeIds = taxonNodeService.idList(taxonNodeFilter);
1364 // List<TaxonNode> childNodes = taxonNodeService.loadByIds(childNodeIds, null);
1365 List<TaxonNode> rootNodes = this.find(config.getSubTreeUuids());
1366 for (TaxonNode taxonNode : rootNodes) {
1367 cloneTaxonRecursive(taxonNode, classificationClone.getRootNode(), config);
1368 }
1369 classificationDao.saveOrUpdate(classificationClone);
1370 result.setCdmEntity(classificationClone);
1371 return result;
1372 }
1373
1374 private void cloneTaxonRecursive(TaxonNode originalParentNode, TaxonNode parentNodeClone,
1375 SubtreeCloneConfigurator config){
1376
1377 Taxon originalTaxon = CdmBase.deproxy(originalParentNode.getTaxon());
1378 TaxonNode childNodeClone;
1379 if (originalTaxon != null){
1380 String microReference = null;
1381 if (config.isReuseTaxa()){
1382 childNodeClone = parentNodeClone.addChildTaxon(originalTaxon, config.getParentChildReference(), microReference);
1383 }else{
1384 Taxon cloneTaxon = originalTaxon.clone(config.isIncludeSynonymsIncludingManAndProParte(),
1385 config.isIncludeTaxonRelationshipsExcludingManAndProParte(),
1386 config.isIncludeDescriptiveData(), config.isIncludeMedia());
1387
1388 //name
1389 if (!config.isReuseNames()){
1390 cloneTaxon.setName(cloneTaxon.getName().clone());
1391 //TODO needs further handling for name relationships etc., see #9349
1392 cloneTaxon.getSynonyms().forEach(syn ->
1393 syn.setName(syn.getName() == null ? null : syn.getName().clone()));
1394 }
1395
1396 if (!config.isReuseTaxonSecundum()){
1397 cloneTaxon.setSec(config.getTaxonSecundum());
1398 }
1399
1400 //add relation between taxa
1401 if (config.getRelationTypeToOldTaxon() != null){
1402 TaxonRelationship rel = cloneTaxon.addTaxonRelation(originalParentNode.getTaxon(), config.getRelationTypeToOldTaxon(),
1403 config.getRelationshipReference(), microReference);
1404 rel.setDoubtful(config.isRelationDoubtful());
1405 }
1406 childNodeClone = parentNodeClone.addChildTaxon(cloneTaxon, config.getParentChildReference(), microReference);
1407 }
1408
1409 //probably necessary as taxon nodes do not cascade
1410 dao.saveOrUpdate(childNodeClone);
1411
1412 }else{
1413 childNodeClone = parentNodeClone;
1414 }
1415 //add children
1416 if (config.isDoRecursive()){
1417 List<TaxonNode> originalChildNodes = originalParentNode.getChildNodes();
1418
1419 for (TaxonNode originalChildNode : originalChildNodes) {
1420 cloneTaxonRecursive(originalChildNode, childNodeClone, config);
1421 }
1422 }
1423 }
1424
1425 @Override
1426 public HomotypicGroupDto getHomotypicGroupDto(UUID homotypicGroupUuid, UUID nodeUuid) {
1427
1428 HomotypicalGroup group = homotypicalGroupDao.load(homotypicGroupUuid);
1429 if (group == null){
1430 return null;
1431 }
1432 return new HomotypicGroupDto(group, nodeUuid);
1433 }
1434
1435 @Override
1436 public TaxonNodeDto getTaxonNodeDto(UUID nodeUuid) {
1437 return dao.getTaxonNodeDto(nodeUuid);
1438 }
1439
1440 @Override
1441 public List<TaxonNodeDto> getTaxonNodeDtos(List<UUID> nodeUuids) {
1442 return dao.getTaxonNodeDtos(nodeUuids);
1443 }
1444
1445 @Override
1446 public List<TaxonNodeDto> getTaxonNodeDtosFromTaxon(UUID taxonUuid, String subTreeIndex) {
1447 return dao.getTaxonNodeDtosFromTaxon(taxonUuid, subTreeIndex);
1448 }
1449
1450 }