Project

General

Profile

Download (59.8 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.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(TaxonNodeServiceImpl.class);
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
}
(86-86/95)