Project

General

Profile

Download (60 KB) Statistics
| Branch: | Tag: | Revision:
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.HHH_9751_Util;
54
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
55
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
56
import eu.etaxonomy.cdm.model.common.CdmBase;
57
import eu.etaxonomy.cdm.model.common.Language;
58
import eu.etaxonomy.cdm.model.common.LanguageString;
59
import eu.etaxonomy.cdm.model.common.TreeIndex;
60
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
61
import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
62
import eu.etaxonomy.cdm.model.description.DescriptiveDataSet;
63
import eu.etaxonomy.cdm.model.description.TaxonDescription;
64
import eu.etaxonomy.cdm.model.metadata.SecReferenceHandlingEnum;
65
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
66
import eu.etaxonomy.cdm.model.name.HybridRelationship;
67
import eu.etaxonomy.cdm.model.name.Rank;
68
import eu.etaxonomy.cdm.model.name.TaxonName;
69
import eu.etaxonomy.cdm.model.permission.Operation;
70
import eu.etaxonomy.cdm.model.reference.NamedSource;
71
import eu.etaxonomy.cdm.model.reference.Reference;
72
import eu.etaxonomy.cdm.model.taxon.Classification;
73
import eu.etaxonomy.cdm.model.taxon.ITaxonTreeNode;
74
import eu.etaxonomy.cdm.model.taxon.Synonym;
75
import eu.etaxonomy.cdm.model.taxon.SynonymType;
76
import eu.etaxonomy.cdm.model.taxon.Taxon;
77
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
78
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
79
import eu.etaxonomy.cdm.model.taxon.TaxonNodeAgentRelation;
80
import eu.etaxonomy.cdm.model.taxon.TaxonNodeStatus;
81
import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
82
import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
83
import eu.etaxonomy.cdm.model.term.DefinedTerm;
84
import eu.etaxonomy.cdm.persistence.dao.common.Restriction;
85
import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;
86
import eu.etaxonomy.cdm.persistence.dao.name.IHomotypicalGroupDao;
87
import eu.etaxonomy.cdm.persistence.dao.reference.IOriginalSourceDao;
88
import eu.etaxonomy.cdm.persistence.dao.reference.IReferenceDao;
89
import eu.etaxonomy.cdm.persistence.dao.taxon.IClassificationDao;
90
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeDao;
91
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeFilterDao;
92
import eu.etaxonomy.cdm.persistence.dto.HomotypicGroupDto;
93
import eu.etaxonomy.cdm.persistence.dto.TaxonNodeDto;
94
import eu.etaxonomy.cdm.persistence.permission.ICdmPermissionEvaluator;
95
import eu.etaxonomy.cdm.persistence.query.OrderHint;
96

    
97
/**
98
 * @author n.hoffmann
99
 * @since Apr 9, 2010
100
 */
101
@Service
102
@Transactional(readOnly = true)
103
public class TaxonNodeServiceImpl
104
           extends AnnotatableServiceBase<TaxonNode, ITaxonNodeDao>
105
           implements ITaxonNodeService{
106

    
107
    private static final Logger logger = LogManager.getLogger(TaxonNodeServiceImpl.class);
108

    
109
    @Autowired
110
    private IBeanInitializer defaultBeanInitializer;
111

    
112
    @Autowired
113
    private ITaxonService taxonService;
114

    
115
//    @Autowired
116
//    private IReferenceService referenceService;
117

    
118
    @Autowired
119
    private IDescriptiveDataSetService dataSetService;
120

    
121
    @Autowired
122
    private IAgentService agentService;
123

    
124
    @Autowired
125
    private INameService nameService;
126

    
127
    @Autowired
128
    private IOriginalSourceDao sourceDao;
129

    
130
    @Autowired
131
    private ITaxonNodeFilterDao nodeFilterDao;
132

    
133
    @Autowired
134
    private IReferenceDao referenceDao;
135

    
136
    @Autowired
137
    private IClassificationDao classificationDao;
138

    
139
    @Autowired
140
    private IHomotypicalGroupDao homotypicalGroupDao;
141

    
142
    @Autowired
143
    IProgressMonitorService progressMonitorService;
144

    
145
    @Autowired
146
    private ICdmPermissionEvaluator permissionEvaluator;
147

    
148
    @Override
149
    public List<TaxonNode> loadChildNodesOfTaxonNode(TaxonNode taxonNode,
150
            List<String> propertyPaths, boolean recursive,  boolean includeUnpublished,
151
            TaxonNodeSortMode sortMode) {
152

    
153
        taxonNode = load(taxonNode.getUuid());
154
        List<TaxonNode> childNodes;
155
        if (recursive == true){
156
            Comparator<TaxonNode> comparator = sortMode == null? null : sortMode.comparator();
157
            childNodes = dao.listChildrenOf(taxonNode, null, null, recursive, includeUnpublished, propertyPaths, comparator);
158
        }else if (includeUnpublished){
159
            childNodes = new ArrayList<>(taxonNode.getChildNodes());
160
        }else{
161
            childNodes = new ArrayList<>();
162
            for (TaxonNode node:taxonNode.getChildNodes()){
163
                if (node.getTaxon().isPublish()){
164
                    childNodes.add(node);
165
                }
166
            }
167
        }
168

    
169
        HHH_9751_Util.removeAllNull(childNodes);
170

    
171
        if (recursive == false && sortMode != null){
172
            Comparator<TaxonNode> comparator = sortMode.comparator();
173
        	Collections.sort(childNodes, comparator);
174
        }
175
        defaultBeanInitializer.initializeAll(childNodes, propertyPaths);
176
        return childNodes;
177
    }
178

    
179
    @Override
180
    public List<TaxonNode> listChildrenOf(TaxonNode node, Integer pageSize, Integer pageIndex,
181
            boolean recursive, boolean includeUnpublished, List<String> propertyPaths){
182
        return dao.listChildrenOf(node, pageSize, pageIndex, recursive, includeUnpublished, propertyPaths, null);
183
    }
184

    
185
    @Override
186
    public TaxonNodeDto getParentUuidAndTitleCache(ITaxonTreeNode child) {
187
        UUID uuid = child.getUuid();
188
        int id = child.getId();
189
        TaxonNodeDto uuidAndTitleCache = new TaxonNodeDto(uuid, id, null);
190
        return getParentUuidAndTitleCache(uuidAndTitleCache);
191
    }
192

    
193
    @Override
194
    public TaxonNodeDto getParentUuidAndTitleCache(TaxonNodeDto child) {
195
        return dao.getParentUuidAndTitleCache(child);
196
    }
197

    
198
    @Override
199
    public List<TaxonNodeDto> listChildNodesAsTaxonNodeDto(TaxonNodeDto parent) {
200
        return dao.listChildNodesAsTaxonNodeDto(parent);
201
    }
202

    
203
    @Override
204
    public List<TaxonNodeDto> getUuidAndTitleCache(Integer limit, String pattern, UUID classificationUuid) {
205
        return dao.getUuidAndTitleCache(limit, pattern, classificationUuid, true);
206
    }
207

    
208
    @Override
209
    public List<TaxonNodeDto> listChildNodesAsTaxonNodeDto(ITaxonTreeNode parent) {
210
        List<String> propertyPaths = new ArrayList<>();
211
        propertyPaths.add("parent");
212
        parent = dao.load(parent.getId(), propertyPaths);
213
        TaxonNodeDto uuidAndTitleCache = new TaxonNodeDto(parent);
214
        return listChildNodesAsTaxonNodeDto(uuidAndTitleCache);
215
    }
216

    
217
    @Override
218
    public List<TaxonNodeDto> taxonNodeDtoParentRank(Classification classification, Rank rank, TaxonName name) {
219
    	return dao.getParentTaxonNodeDtoForRank(classification, rank, name);
220
    }
221

    
222
    @Override
223
    public List<TaxonNodeDto> taxonNodeDtoParentRank(Classification classification, Rank rank, TaxonBase<?> taxonBase) {
224
        return dao.getParentTaxonNodeDtoForRank(classification, rank, taxonBase);
225
    }
226

    
227
    @Override
228
    public Pager<TaxonNodeDto> pageChildNodesDTOs(UUID taxonNodeUuid, boolean recursive,  boolean includeUnpublished,
229
            boolean doSynonyms, TaxonNodeSortMode sortMode,
230
            Integer pageSize, Integer pageIndex) {
231

    
232
        TaxonNode parentNode = dao.load(taxonNodeUuid);
233

    
234
        List<CdmBase> allRecords = new ArrayList<>();
235

    
236
        //acceptedTaxa
237
        List<TaxonNode> childNodes = loadChildNodesOfTaxonNode(parentNode, null, recursive, includeUnpublished, sortMode);
238
        allRecords.addAll(childNodes);
239

    
240
        //add synonyms if pager is not yet full synonyms
241
        if (doSynonyms){
242
            List<Synonym> synList = new ArrayList<>(parentNode.getTaxon().getSynonyms());
243
            Collections.sort(synList, new HomotypicGroupTaxonComparator(null));
244
            //TODO: test sorting
245

    
246
            allRecords.addAll(synList);
247
        }
248

    
249
        List<TaxonNodeDto> dtos = new ArrayList<>(pageSize==null?25:pageSize);
250
        long totalCount = Long.valueOf(allRecords.size());
251

    
252
        TaxonName parentName = null;
253

    
254
        for(CdmBase item : PagerUtils.pageList(allRecords, pageIndex, pageSize)) {
255
            if (item.isInstanceOf(TaxonNode.class)){
256
                dtos.add(new TaxonNodeDto(CdmBase.deproxy(item, TaxonNode.class)));
257
            }else if (item.isInstanceOf(Synonym.class)){
258
                Synonym synonym = CdmBase.deproxy(item, Synonym.class);
259
                parentName = parentName == null? parentNode.getTaxon().getName(): parentName;
260
                boolean isHomotypic = synonym.getName().isHomotypic(parentName);
261
                dtos.add(new TaxonNodeDto(synonym, isHomotypic));
262
            }
263
        }
264
        return new DefaultPagerImpl<>(pageIndex, totalCount, pageSize , dtos);
265
    }
266

    
267
    @Override
268
    public TaxonNodeDto parentDto(UUID taxonNodeUuid) {
269
        if (taxonNodeUuid == null){
270
            return null;
271
        }
272
        TaxonNode taxonNode = dao.load(taxonNodeUuid);
273
        if(taxonNode.getParent() != null) {
274
            return new TaxonNodeDto(taxonNode.getParent());
275
        }
276
        return null;
277
    }
278

    
279
    @Override
280
    public TaxonNodeDto dto(UUID taxonNodeUuid) {
281
        if (taxonNodeUuid == null){
282
            return null;
283
        }
284
        return dao.getTaxonNodeDto(taxonNodeUuid);
285
    }
286

    
287
    @Override
288
    public TaxonNodeDto dto(UUID taxonUuid, UUID classificationUuid) {
289
        if (taxonUuid == null){
290
            return null;
291
        }
292
        List<TaxonNodeDto> taxonNodes = dao.getTaxonNodeForTaxonInClassificationDto(taxonUuid, classificationUuid);
293
        if (!taxonNodes.isEmpty()){
294
            return taxonNodes.get(0);
295
        }
296
        return null;
297
    }
298

    
299
    @Override
300
    @Autowired
301
    protected void setDao(ITaxonNodeDao dao) {
302
        this.dao = dao;
303
    }
304

    
305
    @Override
306
    @Transactional(readOnly = false)
307
    public DeleteResult makeTaxonNodeASynonymOfAnotherTaxonNode(TaxonNode oldTaxonNode, TaxonNode newAcceptedTaxonNode,
308
            SynonymType synonymType, Reference citation, String microReference, SecReferenceHandlingEnum secHandling, boolean setNameInSource)  {
309

    
310
        // TODO at the moment this method only moves synonym-, concept relations and descriptions to the new accepted taxon
311
        // in a future version we also want to move cdm data like annotations, marker, so., but we will need a policy for that
312
        if (oldTaxonNode == null || newAcceptedTaxonNode == null || oldTaxonNode.getTaxon().getName() == null){
313
            throw new IllegalArgumentException("A mandatory parameter was null.");
314
        }
315

    
316
        if(oldTaxonNode.equals(newAcceptedTaxonNode)){
317
            throw new IllegalArgumentException("Taxon can not be made synonym of its own.");
318
        }
319

    
320
        Classification classification = oldTaxonNode.getClassification();
321
        Taxon oldTaxon = HibernateProxyHelper.deproxy(oldTaxonNode.getTaxon());
322
        Taxon newAcceptedTaxon = (Taxon)this.taxonService.find(newAcceptedTaxonNode.getTaxon().getUuid());
323
        newAcceptedTaxon = HibernateProxyHelper.deproxy(newAcceptedTaxon);
324
        // Move oldTaxon to newTaxon
325
        //TaxonName synonymName = oldTaxon.getName();
326
        TaxonName newSynonymName = CdmBase.deproxy(oldTaxon.getName());
327
        HomotypicalGroup group = CdmBase.deproxy(newSynonymName.getHomotypicalGroup());
328
        if (synonymType == null){
329
            if (newSynonymName.isHomotypic(newAcceptedTaxon.getName())){
330
                synonymType = SynonymType.HOMOTYPIC_SYNONYM_OF();
331
            }else{
332
                synonymType = SynonymType.HETEROTYPIC_SYNONYM_OF();
333
            }
334
        }
335

    
336
        //set homotypic group
337
        TaxonName newAcceptedTaxonName = HibernateProxyHelper.deproxy(newAcceptedTaxon.getName(), TaxonName.class);
338
        newAcceptedTaxon.setName(newAcceptedTaxonName);
339
        Reference secNewAccepted = newAcceptedTaxon.getSec();
340
        Reference secOldAccepted = oldTaxon.getSec();
341
        boolean uuidsEqual = (secNewAccepted != null && secOldAccepted != null && secNewAccepted.equals(secOldAccepted)) || (secNewAccepted == null && secOldAccepted == null);
342
        Reference newSec = citation;
343
        //keep when same only warns in ui, the sec still
344
        if (secHandling != null &&  secHandling.equals(SecReferenceHandlingEnum.KeepOrWarn) ){
345
            newSec = oldTaxon.getSec();
346
        }
347
        if (secHandling != null && secHandling.equals(SecReferenceHandlingEnum.AlwaysDelete)){
348
            newSec = null;
349
        }
350

    
351
        Synonym newSyn = newAcceptedTaxon.addSynonymName(newSynonymName, newSec, microReference, synonymType);
352
        if (newSec == null){
353
            newSyn.setSec(newSec);
354
        }
355
        newSyn.setPublish(oldTaxon.isPublish());
356

    
357
        // Move Synonyms to new Taxon
358
        // From ticket 3163 we can move taxon with accepted name having homotypic synonyms
359
        List<Synonym> synonymsInHomotypicalGroup = null;
360

    
361
        //the synonyms of the homotypical group of the old taxon
362
        if (synonymType.equals(SynonymType.HOMOTYPIC_SYNONYM_OF())){
363
        	synonymsInHomotypicalGroup = oldTaxon.getSynonymsInGroup(group);
364
        }
365

    
366
        Set<Synonym> syns = new HashSet<>(oldTaxon.getSynonyms());
367
        for(Synonym synonym : syns){
368
            SynonymType srt;
369
            if(synonym.getHomotypicGroup()!= null
370
                    && synonym.getHomotypicGroup().equals(newAcceptedTaxonName.getHomotypicalGroup())) {
371
                srt = SynonymType.HOMOTYPIC_SYNONYM_OF();
372
            } else if(synonym.getType() != null && synonym.getType().equals(SynonymType.HOMOTYPIC_SYNONYM_OF())) {
373
            	if (synonymType.equals(SynonymType.HOMOTYPIC_SYNONYM_OF())){
374
            		srt = SynonymType.HOMOTYPIC_SYNONYM_OF();
375
            	} else{
376
            		srt = SynonymType.HETEROTYPIC_SYNONYM_OF();
377
            	}
378
            } else {
379
                if (synonymsInHomotypicalGroup != null && synonymsInHomotypicalGroup.contains(synonym)){
380
                    srt = SynonymType.HOMOTYPIC_SYNONYM_OF();
381
                }else{
382
                    srt = synonym.getType();
383
                }
384
            }
385
            if (secHandling != null &&  !secHandling.equals(SecReferenceHandlingEnum.KeepOrWarn)){
386
                synonym.setSec(newSec);
387
            }
388
            newAcceptedTaxon.addSynonym(synonym, srt);
389
        }
390

    
391
        // CHILD NODES
392
        if(oldTaxonNode.getChildNodes() != null && oldTaxonNode.getChildNodes().size() != 0){
393
        	List<TaxonNode> childNodes = new ArrayList<>();
394
        	for (TaxonNode childNode : oldTaxonNode.getChildNodes()){
395
        		childNodes.add(childNode);
396
        	}
397
            for(TaxonNode childNode :childNodes){
398
                newAcceptedTaxonNode.addChildNode(childNode, childNode.getReference(), childNode.getMicroReference()); // childNode.getSynonymToBeUsed()
399
            }
400
        }
401

    
402
        //Move Taxon RelationShips to new Taxon
403
        for(TaxonRelationship taxonRelationship : oldTaxon.getTaxonRelations()){
404
            Taxon fromTaxon = HibernateProxyHelper.deproxy(taxonRelationship.getFromTaxon());
405
            Taxon toTaxon = HibernateProxyHelper.deproxy(taxonRelationship.getToTaxon());
406
            if (fromTaxon == oldTaxon){
407
                newAcceptedTaxon.addTaxonRelation(taxonRelationship.getToTaxon(), taxonRelationship.getType(),
408
                        taxonRelationship.getCitation(), taxonRelationship.getCitationMicroReference());
409

    
410
            }else if(toTaxon == oldTaxon){
411
               fromTaxon.addTaxonRelation(newAcceptedTaxon, taxonRelationship.getType(),
412
                        taxonRelationship.getCitation(), taxonRelationship.getCitationMicroReference());
413
               taxonService.saveOrUpdate(fromTaxon);
414

    
415
            }else{
416
                logger.warn("Taxon is not part of its own Taxonrelationship");
417
            }
418
            // Remove old relationships
419

    
420
            fromTaxon.removeTaxonRelation(taxonRelationship);
421
            toTaxon.removeTaxonRelation(taxonRelationship);
422
            taxonRelationship.setToTaxon(null);
423
            taxonRelationship.setFromTaxon(null);
424
        }
425

    
426
        //Move descriptions to new taxon
427
        List<TaxonDescription> descriptions = new ArrayList<TaxonDescription>( oldTaxon.getDescriptions()); //to avoid concurrent modification errors (newAcceptedTaxon.addDescription() modifies also oldtaxon.descritpions())
428
        for(TaxonDescription description : descriptions){
429
            String message = "Description copied from former accepted taxon: %s (Old title: %s)";
430
            message = String.format(message, oldTaxon.getTitleCache(), description.getTitleCache());
431
            description.setTitleCache(message, true);
432
            if (setNameInSource) {
433
                for (DescriptionElementBase element: description.getElements()){
434
                    for (DescriptionElementSource source: element.getSources()){
435
                        if (source.getNameUsedInSource() == null){
436
                            source.setNameUsedInSource(newSynonymName);
437
                        }
438
                    }
439
                }
440
            }
441
            //oldTaxon.removeDescription(description, false);
442
            newAcceptedTaxon.addDescription(description);
443
        }
444
        oldTaxon.clearDescriptions();
445

    
446
        taxonService.saveOrUpdate(newAcceptedTaxon);
447

    
448
        taxonService.saveOrUpdate(oldTaxon);
449
        taxonService.getSession().flush();
450

    
451
        TaxonDeletionConfigurator conf = new TaxonDeletionConfigurator();
452
        conf.setDeleteSynonymsIfPossible(false);
453
        conf.setDeleteNameIfPossible(false);
454
        DeleteResult taxonDeleteResult = taxonService.isDeletable(oldTaxon.getUuid(), conf);
455

    
456
        DeleteResult result;
457
        if (taxonDeleteResult.isOk()){
458
        	 result = taxonService.deleteTaxon(oldTaxon.getUuid(), conf, classification.getUuid());
459
        }else{
460
        	TaxonNodeDeletionConfigurator config = new TaxonNodeDeletionConfigurator();
461
        	config.setDeleteElement(false);
462
        	conf.setTaxonNodeConfig(config);
463
        	result = deleteTaxonNode(oldTaxonNode, conf);
464
        	result.getRelatedObjects().addAll(taxonDeleteResult.getRelatedObjects());  //we want to know what causes that the taxon can not be deleted
465
        	result.getExceptions().addAll(taxonDeleteResult.getExceptions()); //same for the exceptions
466
        }
467

    
468
        result.addUpdatedObject(newAcceptedTaxon);
469

    
470
        //oldTaxonNode.delete();
471
        return result;
472
    }
473

    
474
    @Override
475
    @Transactional(readOnly = false)
476
    public DeleteResult makeTaxonNodeSynonymsOfAnotherTaxonNode( Set<UUID> oldTaxonNodeUuids,
477
            UUID newAcceptedTaxonNodeUUIDs,
478
            SynonymType synonymType,
479
            UUID citation,
480
            String microReference,
481
            SecReferenceHandlingEnum secHandling,
482
            boolean setNameInSource) {
483
    	DeleteResult result = new DeleteResult();
484
    	for (UUID nodeUuid: oldTaxonNodeUuids) {
485
    		result.includeResult(makeTaxonNodeASynonymOfAnotherTaxonNode(nodeUuid, newAcceptedTaxonNodeUUIDs, synonymType, citation, microReference, secHandling, setNameInSource));
486
    	}
487
    	return result;
488
    }
489

    
490
    @Override
491
    @Transactional(readOnly = false)
492
    public DeleteResult makeTaxonNodeASynonymOfAnotherTaxonNode(UUID oldTaxonNodeUuid,
493
            UUID newAcceptedTaxonNodeUUID,
494
            SynonymType synonymType,
495
            UUID citationUuid,
496
            String microReference,
497
            SecReferenceHandlingEnum secHandling,
498
            boolean setNameInSource) {
499

    
500
        TaxonNode oldTaxonNode = dao.load(oldTaxonNodeUuid);
501
        TaxonNode oldTaxonParentNode = oldTaxonNode.getParent();
502
        TaxonNode newTaxonNode = dao.load(newAcceptedTaxonNodeUUID);
503
        Reference citation = referenceDao.load(citationUuid);
504

    
505
        switch (secHandling){
506
            case AlwaysDelete:
507
                citation = null;
508
                break;
509
            case UseNewParentSec:
510
                citation = newTaxonNode.getTaxon() != null? newTaxonNode.getTaxon().getSec(): null;
511
                break;
512
            case KeepOrWarn:
513

    
514
                Reference synSec = oldTaxonNode.getTaxon().getSec();
515
                if (synSec != null ){
516
                    citation = CdmBase.deproxy(synSec);
517
                }
518
                break;
519
            case KeepOrSelect:
520

    
521
                break;
522
            default:
523
                break;
524
        }
525

    
526
        DeleteResult result = makeTaxonNodeASynonymOfAnotherTaxonNode(oldTaxonNode,
527
                newTaxonNode,
528
                synonymType,
529
                citation,
530
                microReference,
531
                secHandling, setNameInSource);
532

    
533
        result.addUpdatedCdmId(CdmEntityIdentifier.NewInstance(oldTaxonParentNode));
534
        result.addUpdatedCdmId(CdmEntityIdentifier.NewInstance(newTaxonNode));
535
        result.setCdmEntity(oldTaxonParentNode);
536
        return result;
537
    }
538

    
539
    @Override
540
    @Transactional(readOnly = false)
541
    public DeleteResult deleteTaxonNodes(List<TaxonNode> list, TaxonDeletionConfigurator config) {
542

    
543
        if (config == null){
544
        	config = new TaxonDeletionConfigurator();
545
        }
546
        DeleteResult result = new DeleteResult();
547
        Classification classification = null;
548
        List<TaxonNode> taxonNodes = new ArrayList<>(list);
549

    
550
        for (TaxonNode treeNode:taxonNodes){
551
        	if (treeNode != null){
552

    
553
        		TaxonNode taxonNode;
554
	            taxonNode = CdmBase.deproxy(treeNode);
555
	            TaxonNode parent = taxonNode.getParent();
556
	            	//check whether the node has children or the children are already deleted
557
	            if(taxonNode.hasChildNodes()) {
558
            		List<TaxonNode> children = new ArrayList<> ();
559
            		List<TaxonNode> childNodesList = taxonNode.getChildNodes();
560
        			children.addAll(childNodesList);
561
        			//To avoid NPE when child is also in list of taxonNodes, remove it from the list
562
        			Iterator<TaxonNode> it = taxonNodes.iterator();
563
        			for (TaxonNode child: children) {
564
        				while (it.hasNext()) {
565
        					if (it.next().equals(child)) {
566
        						it.remove();
567
        					}
568
        				}
569
        			}
570
        			int compare = config.getTaxonNodeConfig().getChildHandling().compareTo(ChildHandling.DELETE);
571
        			boolean childHandling = (compare == 0)? true: false;
572
            		if (childHandling){
573
            			boolean changeDeleteTaxon = false;
574
            			if (!config.getTaxonNodeConfig().isDeleteTaxon()){
575
            				config.getTaxonNodeConfig().setDeleteTaxon(true);
576
            				changeDeleteTaxon = true;
577
            			}
578
            			DeleteResult resultNodes = deleteTaxonNodes(children, config);
579
            			if (!resultNodes.isOk()){
580
                            result.addExceptions(resultNodes.getExceptions());
581
                            result.setStatus(resultNodes.getStatus());
582
                        }
583
            			if (changeDeleteTaxon){
584
            				config.getTaxonNodeConfig().setDeleteTaxon(false);
585
            			}
586

    
587
            		} else {
588
            			//move the children to the parent
589

    
590
            			for (TaxonNode child: childNodesList){
591
            				parent.addChildNode(child, child.getReference(), child.getMicroReference());
592
            			}
593
            		}
594
            	}
595

    
596
	            classification = taxonNode.getClassification();
597

    
598
	            if (classification.getRootNode().equals(taxonNode)){
599
	            	classification.removeRootNode();
600
	            	classification = null;
601
	            }else if (classification.getChildNodes().contains(taxonNode)){
602
            		Taxon taxon = taxonNode.getTaxon();
603
            		classification.deleteChildNode(taxonNode);
604

    
605
	            	//node is rootNode
606
	            	if (taxon != null){
607

    
608
	            		if (config.getTaxonNodeConfig().isDeleteTaxon()){
609
	            		    taxonService.saveOrUpdate(taxon);
610
	            		    saveOrUpdate(taxonNode);
611

    
612
			            	TaxonDeletionConfigurator configNew = new TaxonDeletionConfigurator();
613
			            	configNew.setClassificationUuid(classification.getUuid());
614
			            	DeleteResult resultTaxon = taxonService.deleteTaxon(taxon.getUuid(), configNew, classification.getUuid());
615
			            	if (!resultTaxon.isOk()){
616
                                result.addExceptions(resultTaxon.getExceptions());
617
                                result.setStatus(resultTaxon.getStatus());
618
                            }
619

    
620
		            	}
621
	            	}
622
            		classification = null;
623

    
624
	            } else {
625
	            	//classification = null;
626
	            	Taxon taxon = taxonNode.getTaxon();
627
	            	taxon = CdmBase.deproxy(taxon);
628
	            	if (taxon != null){
629
	            		taxon.removeTaxonNode(taxonNode);
630
	            		if (config.getTaxonNodeConfig().isDeleteTaxon()){
631
			            	TaxonDeletionConfigurator configNew = new TaxonDeletionConfigurator();
632
			            	saveOrUpdate(taxonNode);
633
			            	taxonService.saveOrUpdate(taxon);
634
			            	DeleteResult resultTaxon = taxonService.deleteTaxon(taxon.getUuid(), configNew, classification.getUuid());
635

    
636
                            if (!resultTaxon.isOk()){
637
                                result.addExceptions(resultTaxon.getExceptions());
638
                                result.setStatus(resultTaxon.getStatus());
639
                            }
640
		            	}
641
	            	}
642

    
643
	            }
644

    
645
	            result.addUpdatedObject(parent);
646
	            if(result.getCdmEntity() == null){
647
	                result.setCdmEntity(taxonNode);
648
                }
649
	            UUID uuid = dao.delete(taxonNode);
650
	            logger.debug("Deleted node " +uuid.toString());
651

    
652
	        }
653
        }
654
        /*if (classification != null){
655
            result.addUpdatedObject(classification);
656
        	DeleteResult resultClassification = classService.delete(classification);
657
        	 if (!resultClassification.isOk()){
658
                 result.addExceptions(resultClassification.getExceptions());
659
                 result.setStatus(resultClassification.getStatus());
660
             }
661
        }*/
662
        return result;
663
    }
664

    
665

    
666
    @Override
667
    @Transactional(readOnly = false)
668
    public DeleteResult deleteTaxonNodes(Collection<UUID> nodeUuids, TaxonDeletionConfigurator config) {
669
        List<TaxonNode> nodes = new ArrayList<>();
670
        for(UUID nodeUuid : nodeUuids) {
671
            nodes.add(dao.load(nodeUuid));
672
        }
673
        return deleteTaxonNodes(nodes, config);
674
    }
675

    
676

    
677
    @Override
678
    @Transactional(readOnly = false)
679
    public DeleteResult deleteTaxonNode(UUID nodeUUID, TaxonDeletionConfigurator config) {
680

    
681
    	TaxonNode node = CdmBase.deproxy(dao.load(nodeUUID));
682
    	return deleteTaxonNode(node, config);
683
    }
684

    
685
    @Override
686
    @Transactional(readOnly = false)
687
    public DeleteResult deleteTaxonNode(TaxonNode node, TaxonDeletionConfigurator config) {
688
        DeleteResult result = new DeleteResult();
689
        if (node == null){
690
            result.setAbort();
691
            result.addException(new Exception("The TaxonNode was already deleted."));
692
            return result;
693
        }
694
        Taxon taxon = null;
695
        try{
696
            taxon = HibernateProxyHelper.deproxy(node.getTaxon());
697
        }catch(NullPointerException e){
698
            result.setAbort();
699
            result.addException(new Exception("The Taxon was already deleted."));
700
        }
701

    
702
    	TaxonNode parent = HibernateProxyHelper.deproxy(node.getParent(), TaxonNode.class);
703
    	if (config == null){
704
    		config = new TaxonDeletionConfigurator();
705
    	}
706

    
707
    	if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.MOVE_TO_PARENT)){
708
    	   Object[] children = node.getChildNodes().toArray();
709
    	   TaxonNode childNode;
710
    	   for (Object child: children){
711
    	       childNode = (TaxonNode) child;
712
    	       parent.addChildNode(childNode, childNode.getReference(), childNode.getMicroReference());
713
    	   }
714
    	}else{
715
    	    DeleteResult tmpResult = deleteTaxonNodes(node.getChildNodes(), config);
716
    	    result.includeResult(tmpResult);
717
    	}
718

    
719
    	//remove node from DescriptiveDataSet
720
        commonService.getReferencingObjects(node).stream()
721
            .filter(obj->obj instanceof DescriptiveDataSet)
722
            .forEach(dataSet->{
723
                ((DescriptiveDataSet)dataSet).removeTaxonSubtree(node);
724
                dataSetService.saveOrUpdate((DescriptiveDataSet) dataSet);
725
        });
726

    
727
    	if (taxon != null){
728
        	if (config.getTaxonNodeConfig().isDeleteTaxon() && (config.isDeleteInAllClassifications() || taxon.getTaxonNodes().size() == 1)){
729
        		result = taxonService.deleteTaxon(taxon.getUuid(), config, node.getClassification().getUuid());
730
        		result.addUpdatedObject(parent);
731
        		if (result.isOk()){
732
        			return result;
733
        		}
734
        	} else {
735
        	    result.addUpdatedObject(taxon);
736
        	}
737
    	}
738
    	result.setCdmEntity(node);
739
    	boolean success = true;
740
    	if (taxon != null){
741
    	    success = taxon.removeTaxonNode(node);
742
    	    taxonService.saveOrUpdate(taxon);
743
    	}
744
    	dao.saveOrUpdate(parent);
745

    
746
    	result.addUpdatedObject(parent);
747

    
748
    	if (success){
749
			result.setStatus(Status.OK);
750
			if (parent != null){
751
    			parent = HibernateProxyHelper.deproxy(parent, TaxonNode.class);
752
    			int index = parent.getChildNodes().indexOf(node);
753
    			if (index > -1){
754
    			    parent.removeChild(index);
755
    			}
756
			}
757
    		if (!dao.delete(node, config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)).equals(null)){
758
    		    result.getUpdatedObjects().remove(node);
759
    			result.addDeletedObject(node);
760
    			return result;
761
    		} else {
762
    			result.setError();
763
    			return result;
764
    		}
765
    	}else{
766
    	    if (dao.findByUuid(node.getUuid()) != null){
767
        		result.setError();
768
        		result.addException(new Exception("The node can not be removed from the taxon."));
769
    		}
770
    		return result;
771
    	}
772
    }
773

    
774

    
775
    @Override
776
    public List<TaxonNode> listAllNodesForClassification(Classification classification, Integer start, Integer end) {
777
        return dao.getTaxonOfAcceptedTaxaByClassification(classification, start, end);
778
    }
779

    
780
    @Override
781
    public int countAllNodesForClassification(Classification classification) {
782
        return dao.countTaxonOfAcceptedTaxaByClassification(classification);
783
    }
784

    
785
    @Override
786
    @Transactional
787
    public UpdateResult moveTaxonNode(UUID taxonNodeUuid, UUID targetNodeUuid, int movingType, SecReferenceHandlingEnum secHandling, UUID secUuid){
788
        TaxonNode taxonNode = HibernateProxyHelper.deproxy(dao.load(taxonNodeUuid));
789
    	TaxonNode targetNode = HibernateProxyHelper.deproxy(dao.load(targetNodeUuid));
790
    	Reference sec = null;
791
    	if (secUuid != null){
792
    	    sec = HibernateProxyHelper.deproxy(referenceDao.load(secUuid));
793
    	}
794
    	UpdateResult result = moveTaxonNode(taxonNode, targetNode, movingType, secHandling, sec);
795
    	return result;
796
    }
797

    
798
    @Override
799
    @Transactional
800
    public UpdateResult moveTaxonNode(TaxonNode taxonNode, TaxonNode newParent, int movingType, SecReferenceHandlingEnum secHandling, Reference sec){
801
        UpdateResult result = new UpdateResult();
802

    
803
        TaxonNode parentParent = HibernateProxyHelper.deproxy(newParent.getParent());
804
        Integer sortIndex = -1;
805
        if (movingType == 0){
806
            sortIndex = 0;
807
        }else if (movingType == 1){
808
            sortIndex = newParent.getSortIndex();
809
            newParent = parentParent;
810
        } else if (movingType == 2){
811
            sortIndex = newParent.getSortIndex() +1;
812
            newParent = parentParent;
813
        } else{
814
            result.setAbort();
815
            result.addException(new Exception("The moving type "+ movingType +" is not supported."));
816
        }
817

    
818
        if (secHandling.equals(SecReferenceHandlingEnum.AlwaysSelect) || (secHandling.equals(SecReferenceHandlingEnum.KeepOrSelect) && sec != null)){
819
            if (taxonNode.getTaxon() != null){
820
                taxonNode.getTaxon().setSec(sec);
821
            }
822
        }else if (secHandling.equals(SecReferenceHandlingEnum.AlwaysDelete)){
823
            if (taxonNode.getTaxon() != null){
824
                taxonNode.getTaxon().setSec(null);
825
            }
826
        }else if (secHandling.equals(SecReferenceHandlingEnum.UseNewParentSec)){
827
            if (taxonNode.getTaxon() != null && newParent.getTaxon()!= null){
828
                taxonNode.getTaxon().setSec(newParent.getTaxon().getSec());
829
            }
830
        }
831

    
832
        taxonNode = newParent.addChildNode(taxonNode, sortIndex, taxonNode.getReference(),  taxonNode.getMicroReference());
833
        result.addUpdatedObject(taxonNode);
834

    
835
        return result;
836
    }
837

    
838
    @Override
839
    @Transactional
840
    public UpdateResult moveTaxonNodes(Set<UUID> taxonNodeUuids, UUID newParentNodeUuid, int movingType, SecReferenceHandlingEnum secHandling, UUID secUuid, IProgressMonitor monitor){
841

    
842
        if (monitor == null){
843
            monitor = DefaultProgressMonitor.NewInstance();
844
        }
845
        UpdateResult result = new UpdateResult();
846
        List<String> taxonNodePropertyPath = new ArrayList<>();
847
        taxonNodePropertyPath.add("taxon.secSource.*");
848
        taxonNodePropertyPath.add("parent.taxon.secSource.*");
849
        TaxonNode targetNode = dao.load(newParentNodeUuid, taxonNodePropertyPath);
850
        List<TaxonNode> nodes = dao.list(taxonNodeUuids, null, null, null, null);
851
        Reference sec = referenceDao.load(secUuid);
852

    
853
        monitor.beginTask("Move Taxonnodes", nodes.size()*2);
854
        monitor.subTask("move taxon nodes");
855
        for (TaxonNode node: nodes){
856
            if (!monitor.isCanceled()){
857
                if (!nodes.contains(node.getParent())){
858
                    result.includeResult(moveTaxonNode(node, targetNode, movingType, secHandling, sec));
859
                }
860
                monitor.worked(1);
861
            }else{
862
                monitor.done();
863
                result.setAbort();
864
                break;
865
            }
866
        }
867
        if (!monitor.isCanceled()){
868
            monitor.subTask("saving and reindex");
869
            dao.saveOrUpdateAll(nodes);
870
        }else{
871
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
872
        }
873

    
874
        monitor.done();
875
        return result;
876
    }
877

    
878
    @Override
879
    public Pager<TaxonNodeAgentRelation> pageTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid,
880
            UUID agentUuid, UUID rankUuid, UUID relTypeUuid, Integer pageSize, Integer pageIndex, List<String> propertyPaths) {
881

    
882
        List<TaxonNodeAgentRelation> records = null;
883

    
884
        long count = dao.countTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid);
885
        if(PagerUtils.hasResultsInRange(count, pageIndex, pageSize)) {
886
            records = dao.listTaxonNodeAgentRelations(taxonUuid, classificationUuid,
887
                    agentUuid, rankUuid, relTypeUuid, PagerUtils.startFor(pageSize, pageIndex), PagerUtils.limitFor(pageSize), propertyPaths);
888
        }
889

    
890
        Pager<TaxonNodeAgentRelation> pager = new DefaultPagerImpl<>(pageIndex, count, pageSize, records);
891
        return pager;
892
    }
893

    
894
    @Override
895
    @Transactional
896
    public UpdateResult createNewTaxonNode(UUID parentNodeUuid, CreateTaxonDTO taxonDto,
897
            NamedSource source, String microref,
898
            TaxonNodeStatus status, Map<Language,LanguageString> statusNote){
899

    
900
        UpdateResult result = new UpdateResult();
901
        TaxonNode child = null;
902
        TaxonNode parent = null;
903
        try{
904
            TaxonName name = null;
905
            Taxon taxon = null;
906
            if (taxonDto.getTaxonUuid() != null){
907
                taxon = (Taxon) taxonService.load(taxonDto.getTaxonUuid());
908
                if (taxon == null){
909
                    throw new RuntimeException("Taxon for not found for id " + taxonDto.getTaxonUuid());
910
                }
911
            }else{
912
                if (taxonDto.getNameUuid() != null){
913
                    name = nameService.load(taxonDto.getNameUuid());
914
                    if (name == null){
915
                        throw new RuntimeException("Taxon name not found for id " + taxonDto.getTaxonUuid());
916
                    }
917
                } else {
918
                    UpdateResult tmpResult = nameService.parseName(taxonDto.getTaxonNameString(),
919
                            taxonDto.getCode(), taxonDto.getPreferredRank(),  true);
920
                    result.addUpdatedObjects(tmpResult.getUpdatedObjects());
921
                    name = (TaxonName)tmpResult.getCdmEntity();
922
                }
923
                Reference sec = null;
924
                if (taxonDto.getSecUuid() != null ){
925
                    sec = referenceDao.load(taxonDto.getSecUuid());
926
                }
927
                if (name != null && !name.isPersited()){
928
                    for (HybridRelationship rel : name.getHybridChildRelations()){
929
                        if (!rel.getHybridName().isPersited()) {
930
                            nameService.save(rel.getHybridName());
931
                        }
932
                        if (!rel.getParentName().isPersited()) {
933
                            nameService.save(rel.getParentName());
934
                        }
935
                    }
936
                }
937
                taxon = Taxon.NewInstance(name, sec);
938
                taxon.setPublish(taxonDto.isPublish());
939
            }
940

    
941
            parent = dao.load(parentNodeUuid);
942
            if (source != null){
943
                if (source.isPersited()){
944
                    source = (NamedSource) sourceDao.load(source.getUuid());
945
                }
946
                if (source.getCitation() != null){
947
                    source.setCitation(referenceDao.load(source.getCitation().getUuid()));
948
                }
949
                if (source.getNameUsedInSource() !=null){
950
                    source.setNameUsedInSource(nameService.load(source.getNameUsedInSource().getUuid()));
951
                }
952
            }
953

    
954
            child = parent.addChildTaxon(taxon, source);
955
            child.setStatus(status);
956

    
957
            if (statusNote != null){
958
                child.getStatusNote().putAll(statusNote);
959
            }
960

    
961
        }catch(Exception e){
962
            result.addException(e);
963
            result.setError();
964
            return result;
965
        }
966
        child = dao.save(child);
967

    
968
        result.addUpdatedObject(parent);
969
        if (child != null){
970
            result.setCdmEntity(child);
971
        }
972
        return result;
973
    }
974

    
975
    @Override
976
    @Transactional
977
    public UpdateResult addTaxonNodeAgentRelation(UUID taxonNodeUUID, UUID agentUUID, DefinedTerm relationshipType){
978
        UpdateResult result = new UpdateResult();
979
        TaxonNode node = dao.load(taxonNodeUUID);
980
        TeamOrPersonBase<?> agent = (TeamOrPersonBase<?>) agentService.load(agentUUID);
981
        node.addAgentRelation(relationshipType, agent);
982
        try{
983
            dao.merge(node, true);
984
        }catch (Exception e){
985
            result.setError();
986
            result.addException(e);
987
        }
988
        result.setCdmEntity(node);
989
        return result;
990
    }
991

    
992
    @Override
993
    @Transactional(readOnly=false)
994
    public UpdateResult setSecundumForSubtree(SecundumForSubtreeConfigurator config) {
995
        UpdateResult result = new UpdateResult();
996
        IProgressMonitor monitor = config.getMonitor();
997

    
998
        if (monitor == null){
999
            monitor = DefaultProgressMonitor.NewInstance();
1000
        }
1001
        monitor.beginTask("Update secundum reference for subtree", 100);
1002
        monitor.subTask("Check start conditions");
1003

    
1004
        if (config.getSubtreeUuid() == null){
1005
            result.setError();
1006
            result.addException(new NullPointerException("No subtree given"));
1007
            monitor.done();
1008
            return result;
1009
        }
1010
        monitor.worked(1);
1011
        TaxonNode subTree = load(config.getSubtreeUuid());
1012
        if (subTree == null){
1013
            result.setError();
1014
            result.addException(new NullPointerException("Subtree does not exist"));
1015
            monitor.done();
1016
            return result;
1017
        }
1018
        monitor.worked(1);
1019

    
1020
        Reference newSec = null;
1021
        if (config.getNewSecundum() != null){
1022
            newSec = referenceDao.load(config.getNewSecundum().getUuid());
1023
            if (newSec == null){
1024
                result.setError();
1025
                result.addException(new NullPointerException("New secundum reference does not exist"));
1026
                monitor.done();
1027
                return result;
1028
            }
1029
        }
1030
        monitor.worked(1);
1031

    
1032
        monitor.subTask("Count records");
1033
        try {
1034
            boolean includeRelatedTaxa = config.isIncludeProParteSynonyms() || config.isIncludeMisapplications();
1035

    
1036
            TreeIndex subTreeIndex = TreeIndex.NewInstance(subTree.treeIndex());
1037
            int count = config.isIncludeAcceptedTaxa() ? dao.countSecundumForSubtreeAcceptedTaxa(subTreeIndex, newSec, config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail()):0;
1038
            monitor.worked(2);
1039
            count += config.isIncludeSynonyms() ? dao.countSecundumForSubtreeSynonyms(subTreeIndex, newSec, config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail()) :0;
1040
            monitor.worked(3);
1041
            count += includeRelatedTaxa ? dao.countSecundumForSubtreeRelations(subTreeIndex, newSec, config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail()):0;
1042
            monitor.worked(2);
1043
            if (monitor.isCanceled()){
1044
                return result;
1045
            }
1046

    
1047
            SubProgressMonitor subMonitor = SubProgressMonitor.NewStarted(monitor, 90, "Updating secundum for subtree", count * 2);  //*2 1 tick for update and 1 tick for commit
1048
            //Reference ref = config.getNewSecundum();
1049
            if (config.isIncludeAcceptedTaxa()){
1050
                monitor.subTask("Update Accepted Taxa");
1051
                Set<CdmBase> updatedTaxa = dao.setSecundumForSubtreeAcceptedTaxa(subTreeIndex, newSec,
1052
                        config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail(), subMonitor);
1053
                result.addUpdatedObjects(updatedTaxa);
1054
                if (monitor.isCanceled()){
1055
                    return result;
1056
                }
1057
            }
1058
            if (config.isIncludeSynonyms()){
1059
               monitor.subTask("Update Synonyms");
1060
               Set<CdmBase> updatedSynonyms = dao.setSecundumForSubtreeSynonyms(subTreeIndex, newSec,
1061
                       config.isOverwriteExisting(), config.isIncludeSharedTaxa() , config.isEmptySecundumDetail(), subMonitor);
1062
               result.addUpdatedObjects(updatedSynonyms);
1063
               if (monitor.isCanceled()){
1064
                   return result;
1065
               }
1066
            }
1067
            if (includeRelatedTaxa){
1068
                monitor.subTask("Update Related Taxa");
1069
                Set<UUID> relationTypes = getRelationTypesForSubtree(config);
1070
                Set<CdmBase> updatedRels = dao.setSecundumForSubtreeRelations(subTreeIndex, newSec,
1071
                        relationTypes, config.isOverwriteExisting(), config.isIncludeSharedTaxa() , config.isEmptySecundumDetail(), subMonitor);
1072
                result.addUpdatedObjects(updatedRels);
1073
                if (monitor.isCanceled()){
1074
                    return result;
1075
                }
1076
            }
1077
        } catch (Exception e) {
1078
            result.setError();
1079
            result.addException(e);
1080
        }
1081
        monitor.done();
1082
        return result;
1083
    }
1084

    
1085
    @Override
1086
    @Transactional(readOnly=false)
1087
    public UpdateResult setPublishForSubtree(PublishForSubtreeConfigurator config){
1088
        UpdateResult result = new UpdateResult();
1089
        IProgressMonitor monitor = config.getMonitor();
1090
        if (monitor == null){
1091
            monitor = DefaultProgressMonitor.NewInstance();
1092
        }
1093
        monitor.beginTask("Update publish flag for subtree", 100);
1094
        monitor.subTask("Check start conditions");
1095

    
1096
        if (config.getSubtreeUuid() == null){
1097
            result.setError();
1098
            result.addException(new NullPointerException("No subtree given"));
1099
            monitor.done();
1100
            return result;
1101
        }
1102
        monitor.worked(1);
1103

    
1104
        TaxonNode subTree = find(config.getSubtreeUuid());
1105
        if (subTree == null){
1106
            result.setError();
1107
            result.addException(new NullPointerException("Subtree does not exist"));
1108
            monitor.done();
1109
            return result;
1110
        }
1111
        monitor.worked(1);
1112

    
1113
        monitor.subTask("Count records");
1114
        boolean includeAcceptedTaxa = config.isIncludeAcceptedTaxa();
1115
        boolean publish = config.isPublish();
1116
        boolean includeSynonyms = config.isIncludeSynonyms();
1117
        boolean includeSharedTaxa = config.isIncludeSharedTaxa();
1118
        boolean includeHybrids = config.isIncludeHybrids();
1119
        boolean includeRelatedTaxa = config.isIncludeProParteSynonyms() || config.isIncludeMisapplications();
1120
        try {
1121
            TreeIndex subTreeIndex = TreeIndex.NewInstance(subTree.treeIndex());
1122
            int count = includeAcceptedTaxa ? dao.countPublishForSubtreeAcceptedTaxa(subTreeIndex, publish, includeSharedTaxa, includeHybrids):0;
1123
            monitor.worked(3);
1124
            count += includeSynonyms ? dao.countPublishForSubtreeSynonyms(subTreeIndex, publish, includeSharedTaxa, includeHybrids):0;
1125
            monitor.worked(3);
1126
            count += includeRelatedTaxa ? dao.countPublishForSubtreeRelatedTaxa(subTreeIndex, publish, includeSharedTaxa, includeHybrids):0;
1127
            monitor.worked(2);
1128
            if (monitor.isCanceled()){
1129
                return result;
1130
            }
1131

    
1132
            SubProgressMonitor subMonitor = SubProgressMonitor.NewStarted(monitor, 90, "Updating secundum for subtree", count);
1133
            if (includeAcceptedTaxa){
1134
                monitor.subTask("Update Accepted Taxa");
1135
                @SuppressWarnings("rawtypes")
1136
                Set<TaxonBase> updatedTaxa = dao.setPublishForSubtreeAcceptedTaxa(subTreeIndex,
1137
                        publish, includeSharedTaxa, includeHybrids, subMonitor);
1138
                result.addUpdatedObjects(updatedTaxa);
1139
                if (monitor.isCanceled()){
1140
                    return result;
1141
                }
1142
            }
1143
            if (includeSynonyms){
1144
                monitor.subTask("Update Synonyms");
1145
                @SuppressWarnings("rawtypes")
1146
                Set<TaxonBase> updatedSynonyms = dao.setPublishForSubtreeSynonyms(subTreeIndex,
1147
                        publish, includeSharedTaxa, includeHybrids, subMonitor);
1148
                result.addUpdatedObjects(updatedSynonyms);
1149
                if (monitor.isCanceled()){
1150
                    return result;
1151
                }
1152
            }
1153
            if (includeRelatedTaxa){
1154
                monitor.subTask("Update Related Taxa");
1155
                Set<UUID> relationTypes = getRelationTypesForSubtree(config);
1156
                if (config.isIncludeMisapplications()){
1157
                    relationTypes.addAll(TaxonRelationshipType.misappliedNameUuids());
1158
                }
1159
                if (config.isIncludeProParteSynonyms()){
1160
                    relationTypes.addAll(TaxonRelationshipType.proParteOrPartialSynonymUuids());
1161
                }
1162
                @SuppressWarnings("rawtypes")
1163
                Set<TaxonBase> updatedTaxa = dao.setPublishForSubtreeRelatedTaxa(subTreeIndex, publish,
1164
                        relationTypes, includeSharedTaxa, includeHybrids, subMonitor);
1165
                result.addUpdatedObjects(updatedTaxa);
1166
                if (monitor.isCanceled()){
1167
                    return result;
1168
                }
1169
            }
1170
        } catch (Exception e) {
1171
            result.setError();
1172
            result.addException(e);
1173
        }
1174

    
1175
        monitor.done();
1176
        return result;
1177
    }
1178

    
1179
    private Set<UUID> getRelationTypesForSubtree(ForSubtreeConfiguratorBase config) {
1180
        Set<UUID> relationTypes = new HashSet<>();
1181
        if (config.isIncludeMisapplications()){
1182
            relationTypes.addAll(TaxonRelationshipType.misappliedNameUuids());
1183
        }
1184
        if (config.isIncludeProParteSynonyms()){
1185
            relationTypes.addAll(TaxonRelationshipType.proParteOrPartialSynonymUuids());
1186
        }
1187
        return relationTypes;
1188
    }
1189

    
1190
    @Override
1191
    public long count(TaxonNodeFilter filter){
1192
        return nodeFilterDao.count(filter);
1193
    }
1194

    
1195
    @Override
1196
    public List<UUID> uuidList(TaxonNodeFilter filter){
1197
        return nodeFilterDao.listUuids(filter);
1198
    }
1199

    
1200
    @Override
1201
    public List<Integer> idList(TaxonNodeFilter filter){
1202
        return nodeFilterDao.idList(filter);
1203
    }
1204

    
1205
    @Override
1206
    public TaxonNodeDto findCommonParentDto(Collection<TaxonNodeDto> nodes) {
1207
        TaxonNodeDto commonParent = null;
1208
        List<String> treePath = null;
1209
        for (TaxonNodeDto nodeDto : nodes) {
1210
            if (nodeDto == null){
1211
                continue;
1212
            }
1213
            String nodeTreeIndex = nodeDto.getTreeIndex();
1214
            nodeTreeIndex = nodeTreeIndex.replaceFirst("#", "");
1215
            String[] split = nodeTreeIndex.split("#");
1216
            if(treePath == null){
1217
                treePath = Arrays.asList(split);
1218
            }
1219
            else{
1220
                List<String> match = new ArrayList<>();
1221
                for(int i=0;i<treePath.size();i++){
1222
                    if(i>=split.length){
1223
                        //current tree index is shorter so break
1224
                        break;
1225
                    }
1226
                    else if(split[i].equals(treePath.get(i))){
1227
                        //match found
1228
                        match.add(treePath.get(i));
1229
                    }
1230
                    else{
1231
                        //first mismatch found
1232
                        break;
1233
                    }
1234
                }
1235
                treePath = match;
1236
                if(treePath.isEmpty()){
1237
                    //no common parent found for at least two nodes
1238
                    //-> they belong to a different classification
1239
                    break;
1240
                }
1241
            }
1242
        }
1243
        if(treePath!=null && !treePath.isEmpty()) {
1244
            //get last index
1245
            int nodeId = Integer.parseInt(treePath.get(treePath.size()-1));
1246
            TaxonNode taxonNode = dao.load(nodeId, null);
1247
            commonParent = new TaxonNodeDto(taxonNode);
1248
        }
1249
        return commonParent;
1250
    }
1251

    
1252
    @Override
1253
    public List<TaxonDistributionDTO> getTaxonDistributionDTO(List<UUID> nodeUuids, List<String> propertyPaths,
1254
            Authentication authentication, boolean openChildren, TaxonNodeSortMode sortMode){
1255

    
1256
        nodeUuids = nodeUuids.stream().distinct().collect(Collectors.toList());
1257
        List<TaxonNode> nodes = new ArrayList<>();
1258

    
1259
        List<TaxonNode> parentNodes = load(nodeUuids, propertyPaths);
1260
        if (sortMode != null){
1261
            parentNodes.sort(sortMode.comparator());
1262
        }
1263
        if (openChildren){
1264
            //TODO we could remove nodes which are children of other nodes in parentNodes list here as they are duplicates
1265
            for (TaxonNode node: parentNodes){
1266
                if (node == null || nodes.contains(node)){
1267
                    continue;
1268
                }
1269
                nodes.add(node);
1270
                List<TaxonNode> children = new ArrayList<>();
1271
                children.addAll(loadChildNodesOfTaxonNode(node,
1272
                        propertyPaths, true,  true, sortMode));
1273
                for (TaxonNode child: children){
1274
                    if (!nodes.contains(child)){
1275
                        nodes.add(child);
1276
                    }
1277
                }
1278
            }
1279
        }else{
1280
            nodes.addAll(nodes);
1281
        }
1282

    
1283
        List<TaxonDistributionDTO> result = new ArrayList<>();
1284
        boolean hasPermission = false;
1285
        for(TaxonNode node: nodes){
1286
            if (authentication != null ) {
1287
                hasPermission = permissionEvaluator.hasPermission(authentication, node, Operation.UPDATE);
1288
            }else {
1289
                hasPermission = true;
1290
            }
1291
            if (node.getTaxon() != null && hasPermission){
1292
                try{
1293
                    TaxonDistributionDTO dto = new TaxonDistributionDTO(node);
1294
                    result.add(dto);
1295
                }catch(Exception e){
1296
                    logger.error(e.getMessage(), e);
1297
                }
1298
            }
1299
        }
1300
        return result;
1301
    }
1302

    
1303
    @Override
1304
    public <S extends TaxonNode> Pager<S> page(Class<S> clazz, List<Restriction<?>> restrictions, Integer pageSize,
1305
            Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths) {
1306
        return page(clazz, restrictions, pageSize, pageIndex, orderHints, propertyPaths, INCLUDE_UNPUBLISHED);
1307
    }
1308

    
1309
    @Override
1310
    public <S extends TaxonNode> Pager<S> page(Class<S> clazz, List<Restriction<?>> restrictions, Integer pageSize,
1311
            Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths, boolean includeUnpublished) {
1312

    
1313
        List<S> records;
1314
        long resultSize = dao.count(clazz, restrictions);
1315
        if(AbstractPagerImpl.hasResultsInRange(resultSize, pageIndex, pageSize)){
1316
            records = dao.list(clazz, restrictions, pageSize, pageIndex, orderHints, propertyPaths, includeUnpublished);
1317
        } else {
1318
            records = new ArrayList<>();
1319
        }
1320
        Pager<S> pager = new DefaultPagerImpl<>(pageIndex, resultSize, pageSize, records);
1321
        return pager;
1322
    }
1323

    
1324
    @Override
1325
    public List<TaxonDistributionDTO> getTaxonDistributionDTO(List<UUID> nodeUuids,
1326
            List<String> propertyPaths, boolean openChildren) {
1327
        return getTaxonDistributionDTO(nodeUuids, propertyPaths, null, openChildren, null);
1328
    }
1329

    
1330
    @Override
1331
    @Transactional(readOnly = false)
1332
    public UpdateResult cloneSubtree(SubtreeCloneConfigurator config) {
1333
        UpdateResult result = new UpdateResult();
1334

    
1335
        if (config.getSubTreeUuids().isEmpty()){
1336
            return result;
1337
        }
1338

    
1339
        //TODO error handling
1340
        Reference taxonSecundum = config.isReuseTaxa() || config.isReuseTaxonSecundum() || config.getTaxonSecundumUuid() == null ?
1341
                null : referenceDao.findByUuid(config.getTaxonSecundumUuid());
1342
        config.setTaxonSecundum(taxonSecundum);
1343

    
1344
        Reference parentChildReference = config.isReuseParentChildReference() || config.getParentChildReferenceUuid() == null ?
1345
                null : referenceDao.findByUuid(config.getParentChildReferenceUuid());
1346
        config.setParentChildReference(parentChildReference);
1347

    
1348
        Reference taxonRelationshipReference = config.getRelationTypeToOldTaxon() == null ?
1349
                null : referenceDao.findByUuid(config.getRelationshipReferenceUuid());
1350
        config.setRelationshipReference(taxonRelationshipReference);
1351

    
1352
        Classification classificationClone = Classification.NewInstance(config.getNewClassificationName());
1353

    
1354
        if (config.isReuseClassificationReference()){
1355
            TaxonNode anyNode = dao.findByUuid(config.getSubTreeUuids().iterator().next());
1356
            if (anyNode != null){
1357
                Reference oldClassificationRef = anyNode.getClassification().getReference();
1358
                classificationClone.setReference(oldClassificationRef);
1359
            }
1360
        }else if (config.getClassificationReferenceUuid() != null) {
1361
            Reference classificationReference = referenceDao.findByUuid(config.getClassificationReferenceUuid());
1362
            classificationClone.setReference(classificationReference);
1363
        }
1364

    
1365
        //clone taxa and taxon nodes
1366
//      List<Integer> childNodeIds = taxonNodeService.idList(taxonNodeFilter);
1367
//      List<TaxonNode> childNodes = taxonNodeService.loadByIds(childNodeIds, null);
1368
        List<TaxonNode> rootNodes = this.find(config.getSubTreeUuids());
1369
        for (TaxonNode taxonNode : rootNodes) {
1370
            cloneTaxonRecursive(taxonNode, classificationClone.getRootNode(), config);
1371
        }
1372
        classificationDao.saveOrUpdate(classificationClone);
1373
        result.setCdmEntity(classificationClone);
1374
        return result;
1375
    }
1376

    
1377
    private void cloneTaxonRecursive(TaxonNode originalParentNode, TaxonNode parentNodeClone,
1378
            SubtreeCloneConfigurator config){
1379

    
1380
        Taxon originalTaxon = CdmBase.deproxy(originalParentNode.getTaxon());
1381
        TaxonNode childNodeClone;
1382
        if (originalTaxon != null){
1383
            String microReference = null;
1384
            if (config.isReuseTaxa()){
1385
                childNodeClone = parentNodeClone.addChildTaxon(originalTaxon, config.getParentChildReference(), microReference);
1386
            }else{
1387
                Taxon cloneTaxon = originalTaxon.clone(config.isIncludeSynonymsIncludingManAndProParte(),
1388
                        config.isIncludeTaxonRelationshipsExcludingManAndProParte(),
1389
                        config.isIncludeDescriptiveData(), config.isIncludeMedia());
1390

    
1391
                //name
1392
                if (!config.isReuseNames()){
1393
                    cloneTaxon.setName(cloneTaxon.getName().clone());
1394
                    //TODO needs further handling for name relationships etc., see #9349
1395
                    cloneTaxon.getSynonyms().forEach(syn ->
1396
                        syn.setName(syn.getName() == null ? null : syn.getName().clone()));
1397
                }
1398

    
1399
                if (!config.isReuseTaxonSecundum()){
1400
                    cloneTaxon.setSec(config.getTaxonSecundum());
1401
                }
1402

    
1403
                //add relation between taxa
1404
                if (config.getRelationTypeToOldTaxon() != null){
1405
                    TaxonRelationship rel = cloneTaxon.addTaxonRelation(originalParentNode.getTaxon(), config.getRelationTypeToOldTaxon(),
1406
                            config.getRelationshipReference(), microReference);
1407
                    rel.setDoubtful(config.isRelationDoubtful());
1408
                }
1409
                childNodeClone = parentNodeClone.addChildTaxon(cloneTaxon, config.getParentChildReference(), microReference);
1410
            }
1411

    
1412
            //probably necessary as taxon nodes do not cascade
1413
            dao.saveOrUpdate(childNodeClone);
1414

    
1415
        }else{
1416
            childNodeClone = parentNodeClone;
1417
        }
1418
        //add children
1419
        if (config.isDoRecursive()){
1420
            List<TaxonNode> originalChildNodes = originalParentNode.getChildNodes();
1421
            HHH_9751_Util.removeAllNull(originalChildNodes);
1422

    
1423
            for (TaxonNode originalChildNode : originalChildNodes) {
1424
                cloneTaxonRecursive(originalChildNode, childNodeClone, config);
1425
            }
1426
        }
1427
    }
1428

    
1429
    @Override
1430
    public HomotypicGroupDto getHomotypicGroupDto(UUID homotypicGroupUuid, UUID nodeUuid) {
1431

    
1432
        HomotypicalGroup group = homotypicalGroupDao.load(homotypicGroupUuid);
1433
        if (group == null){
1434
            return null;
1435
        }
1436
        return new HomotypicGroupDto(group, nodeUuid);
1437
    }
1438

    
1439
    @Override
1440
    public TaxonNodeDto getTaxonNodeDto(UUID nodeUuid) {
1441
        return dao.getTaxonNodeDto(nodeUuid);
1442
    }
1443

    
1444
    @Override
1445
    public List<TaxonNodeDto> getTaxonNodeDtos(List<UUID> nodeUuids) {
1446
        return dao.getTaxonNodeDtos(nodeUuids);
1447
    }
1448

    
1449
    @Override
1450
    public List<TaxonNodeDto> getTaxonNodeDtosFromTaxon(UUID taxonUuid, String subTreeIndex) {
1451
        return dao.getTaxonNodeDtosFromTaxon(taxonUuid, subTreeIndex);
1452
    }
1453

    
1454
}
(86-86/95)