Project

General

Profile

Download (57.1 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.log4j.Logger;
25
import org.springframework.beans.factory.annotation.Autowired;
26
import org.springframework.security.core.Authentication;
27
import org.springframework.stereotype.Service;
28
import org.springframework.transaction.annotation.Transactional;
29
import org.springframework.transaction.interceptor.TransactionAspectSupport;
30

    
31
import eu.etaxonomy.cdm.api.service.UpdateResult.Status;
32
import eu.etaxonomy.cdm.api.service.config.ForSubtreeConfiguratorBase;
33
import eu.etaxonomy.cdm.api.service.config.NodeDeletionConfigurator.ChildHandling;
34
import eu.etaxonomy.cdm.api.service.config.PublishForSubtreeConfigurator;
35
import eu.etaxonomy.cdm.api.service.config.SecundumForSubtreeConfigurator;
36
import eu.etaxonomy.cdm.api.service.config.SubtreeCloneConfigurator;
37
import eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator;
38
import eu.etaxonomy.cdm.api.service.config.TaxonNodeDeletionConfigurator;
39
import eu.etaxonomy.cdm.api.service.dto.CdmEntityIdentifier;
40
import eu.etaxonomy.cdm.api.service.dto.CreateTaxonDTO;
41
import eu.etaxonomy.cdm.api.service.dto.TaxonDistributionDTO;
42
import eu.etaxonomy.cdm.api.service.pager.Pager;
43
import eu.etaxonomy.cdm.api.service.pager.PagerUtils;
44
import eu.etaxonomy.cdm.api.service.pager.impl.AbstractPagerImpl;
45
import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
46
import eu.etaxonomy.cdm.common.monitor.DefaultProgressMonitor;
47
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
48
import eu.etaxonomy.cdm.common.monitor.SubProgressMonitor;
49
import eu.etaxonomy.cdm.compare.taxon.HomotypicGroupTaxonComparator;
50
import eu.etaxonomy.cdm.compare.taxon.TaxonNodeSortMode;
51
import eu.etaxonomy.cdm.filter.TaxonNodeFilter;
52
import eu.etaxonomy.cdm.hibernate.HHH_9751_Util;
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 = Logger.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
        HHH_9751_Util.removeAllNull(childNodes);
169

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
245
            allRecords.addAll(synList);
246
        }
247

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

    
251
        TaxonName parentName = null;
252

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

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

    
278
    @Override
279
    public TaxonNodeDto dto(UUID taxonNodeUuid) {
280
        if (taxonNodeUuid == null){
281
            return null;
282
        }
283
        TaxonNode taxonNode = dao.load(taxonNodeUuid);
284
        if (taxonNode != null){
285
            return new TaxonNodeDto(taxonNode);
286
        }
287
        return null;
288
    }
289

    
290
    @Override
291
    @Autowired
292
    protected void setDao(ITaxonNodeDao dao) {
293
        this.dao = dao;
294
    }
295

    
296
    @Override
297
    @Transactional(readOnly = false)
298
    public DeleteResult makeTaxonNodeASynonymOfAnotherTaxonNode(TaxonNode oldTaxonNode, TaxonNode newAcceptedTaxonNode,
299
            SynonymType synonymType, Reference citation, String microReference, SecReferenceHandlingEnum secHandling, boolean setNameInSource)  {
300

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

    
307
        if(oldTaxonNode.equals(newAcceptedTaxonNode)){
308
            throw new IllegalArgumentException("Taxon can not be made synonym of its own.");
309
        }
310

    
311
        Classification classification = oldTaxonNode.getClassification();
312
        Taxon oldTaxon = HibernateProxyHelper.deproxy(oldTaxonNode.getTaxon());
313
        Taxon newAcceptedTaxon = (Taxon)this.taxonService.find(newAcceptedTaxonNode.getTaxon().getUuid());
314
        newAcceptedTaxon = HibernateProxyHelper.deproxy(newAcceptedTaxon);
315
        // Move oldTaxon to newTaxon
316
        //TaxonName synonymName = oldTaxon.getName();
317
        TaxonName newSynonymName = CdmBase.deproxy(oldTaxon.getName());
318
        HomotypicalGroup group = CdmBase.deproxy(newSynonymName.getHomotypicalGroup());
319
        if (synonymType == null){
320
            if (newSynonymName.isHomotypic(newAcceptedTaxon.getName())){
321
                synonymType = SynonymType.HOMOTYPIC_SYNONYM_OF();
322
            }else{
323
                synonymType = SynonymType.HETEROTYPIC_SYNONYM_OF();
324
            }
325
        }
326

    
327
        //set homotypic group
328
        TaxonName newAcceptedTaxonName = HibernateProxyHelper.deproxy(newAcceptedTaxon.getName(), TaxonName.class);
329
        newAcceptedTaxon.setName(newAcceptedTaxonName);
330
        Reference secNewAccepted = newAcceptedTaxon.getSec();
331
        Reference secOldAccepted = oldTaxon.getSec();
332
        boolean uuidsEqual = (secNewAccepted != null && secOldAccepted != null && secNewAccepted.equals(secOldAccepted)) || (secNewAccepted == null && secOldAccepted == null);
333
        Reference newSec = citation;
334
        if (citation == null && (secHandling != null && (secHandling.equals(SecReferenceHandlingEnum.KeepAlways) || (secHandling.equals(SecReferenceHandlingEnum.KeepWhenSame) && uuidsEqual)))){
335
            newSec = oldTaxon.getSec();
336
        }
337
        if (secHandling != null && secHandling.equals(SecReferenceHandlingEnum.AlwaysDelete)){
338
            newSec = null;
339
        }
340

    
341
        Synonym newSyn = newAcceptedTaxon.addSynonymName(newSynonymName, newSec, microReference, synonymType);
342
        if (newSec == null){
343
            newSyn.setSec(newSec);
344
        }
345
        newSyn.setPublish(oldTaxon.isPublish());
346

    
347
        // Move Synonyms to new Taxon
348
        // From ticket 3163 we can move taxon with accepted name having homotypic synonyms
349
        List<Synonym> synonymsInHomotypicalGroup = null;
350

    
351
        //the synonyms of the homotypical group of the old taxon
352
        if (synonymType.equals(SynonymType.HOMOTYPIC_SYNONYM_OF())){
353
        	synonymsInHomotypicalGroup = oldTaxon.getSynonymsInGroup(group);
354
        }
355

    
356
        Set<Synonym> syns = new HashSet<>(oldTaxon.getSynonyms());
357
        for(Synonym synonym : syns){
358
            SynonymType srt;
359
            if(synonym.getHomotypicGroup()!= null
360
                    && synonym.getHomotypicGroup().equals(newAcceptedTaxonName.getHomotypicalGroup())) {
361
                srt = SynonymType.HOMOTYPIC_SYNONYM_OF();
362
            } else if(synonym.getType() != null && synonym.getType().equals(SynonymType.HOMOTYPIC_SYNONYM_OF())) {
363
            	if (synonymType.equals(SynonymType.HOMOTYPIC_SYNONYM_OF())){
364
            		srt = SynonymType.HOMOTYPIC_SYNONYM_OF();
365
            	} else{
366
            		srt = SynonymType.HETEROTYPIC_SYNONYM_OF();
367
            	}
368
            } else {
369
                if (synonymsInHomotypicalGroup != null && synonymsInHomotypicalGroup.contains(synonym)){
370
                    srt = SynonymType.HOMOTYPIC_SYNONYM_OF();
371
                }else{
372
                    srt = synonym.getType();
373
                }
374

    
375
            }
376
            if (secHandling != null && !secHandling.equals(SecReferenceHandlingEnum.KeepAlways) && !secHandling.equals(SecReferenceHandlingEnum.KeepWhenSame)){
377
                synonym.setSec(newSec);
378
            }
379
            newAcceptedTaxon.addSynonym(synonym, srt);
380

    
381
        }
382

    
383

    
384
        // CHILD NODES
385
        if(oldTaxonNode.getChildNodes() != null && oldTaxonNode.getChildNodes().size() != 0){
386
        	List<TaxonNode> childNodes = new ArrayList<>();
387
        	for (TaxonNode childNode : oldTaxonNode.getChildNodes()){
388
        		childNodes.add(childNode);
389
        	}
390
            for(TaxonNode childNode :childNodes){
391
                newAcceptedTaxonNode.addChildNode(childNode, childNode.getReference(), childNode.getMicroReference()); // childNode.getSynonymToBeUsed()
392
            }
393
        }
394

    
395
        //Move Taxon RelationShips to new Taxon
396
        for(TaxonRelationship taxonRelationship : oldTaxon.getTaxonRelations()){
397
            Taxon fromTaxon = HibernateProxyHelper.deproxy(taxonRelationship.getFromTaxon());
398
            Taxon toTaxon = HibernateProxyHelper.deproxy(taxonRelationship.getToTaxon());
399
            if (fromTaxon == oldTaxon){
400
                newAcceptedTaxon.addTaxonRelation(taxonRelationship.getToTaxon(), taxonRelationship.getType(),
401
                        taxonRelationship.getCitation(), taxonRelationship.getCitationMicroReference());
402

    
403
            }else if(toTaxon == oldTaxon){
404
               fromTaxon.addTaxonRelation(newAcceptedTaxon, taxonRelationship.getType(),
405
                        taxonRelationship.getCitation(), taxonRelationship.getCitationMicroReference());
406
               taxonService.saveOrUpdate(fromTaxon);
407

    
408
            }else{
409
                logger.warn("Taxon is not part of its own Taxonrelationship");
410
            }
411
            // Remove old relationships
412

    
413
            fromTaxon.removeTaxonRelation(taxonRelationship);
414
            toTaxon.removeTaxonRelation(taxonRelationship);
415
            taxonRelationship.setToTaxon(null);
416
            taxonRelationship.setFromTaxon(null);
417
        }
418

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

    
439
        taxonService.saveOrUpdate(newAcceptedTaxon);
440

    
441
        taxonService.saveOrUpdate(oldTaxon);
442
        taxonService.getSession().flush();
443

    
444
        TaxonDeletionConfigurator conf = new TaxonDeletionConfigurator();
445
        conf.setDeleteSynonymsIfPossible(false);
446
        conf.setDeleteNameIfPossible(false);
447
        DeleteResult result = taxonService.isDeletable(oldTaxon.getUuid(), conf);
448

    
449

    
450
        if (result.isOk()){
451
        	 result = taxonService.deleteTaxon(oldTaxon.getUuid(), conf, classification.getUuid());
452

    
453
        }else{
454
        	result.setStatus(Status.OK);
455
        	TaxonNodeDeletionConfigurator config = new TaxonNodeDeletionConfigurator();
456
        	config.setDeleteElement(false);
457
        	conf.setTaxonNodeConfig(config);
458
        	result.includeResult(deleteTaxonNode(oldTaxonNode, conf));
459
        }
460

    
461
        result.addUpdatedObject(newAcceptedTaxon);
462

    
463

    
464
        //oldTaxonNode.delete();
465
        return result;
466
    }
467
    @Override
468
    @Transactional(readOnly = false)
469
    public UpdateResult makeTaxonNodeSynonymsOfAnotherTaxonNode( Set<UUID> oldTaxonNodeUuids,
470
            UUID newAcceptedTaxonNodeUUIDs,
471
            SynonymType synonymType,
472
            UUID citation,
473
            String microReference,
474
            SecReferenceHandlingEnum secHandling,
475
            boolean setNameInSource) {
476
    	UpdateResult result = new UpdateResult();
477
    	for (UUID nodeUuid: oldTaxonNodeUuids) {
478
    		result.includeResult(makeTaxonNodeASynonymOfAnotherTaxonNode(nodeUuid, newAcceptedTaxonNodeUUIDs, synonymType, citation, microReference, secHandling, setNameInSource));
479
    	}
480
    	return result;
481
    }
482

    
483
    @Override
484
    @Transactional(readOnly = false)
485
    public UpdateResult makeTaxonNodeASynonymOfAnotherTaxonNode(UUID oldTaxonNodeUuid,
486
            UUID newAcceptedTaxonNodeUUID,
487
            SynonymType synonymType,
488
            UUID citationUuid,
489
            String microReference,
490
            SecReferenceHandlingEnum secHandling,
491
            boolean setNameInSource) {
492

    
493
        TaxonNode oldTaxonNode = dao.load(oldTaxonNodeUuid);
494
        TaxonNode oldTaxonParentNode = oldTaxonNode.getParent();
495
        TaxonNode newTaxonNode = dao.load(newAcceptedTaxonNodeUUID);
496
        Reference citation = referenceDao.load(citationUuid);
497

    
498
        UpdateResult result = makeTaxonNodeASynonymOfAnotherTaxonNode(oldTaxonNode,
499
                newTaxonNode,
500
                synonymType,
501
                citation,
502
                microReference,
503
                secHandling, setNameInSource);
504
        result.addUpdatedCdmId(new CdmEntityIdentifier(oldTaxonParentNode.getId(), TaxonNode.class));
505
        result.addUpdatedCdmId(new CdmEntityIdentifier(newTaxonNode.getId(), TaxonNode.class));
506
        result.setCdmEntity(oldTaxonParentNode);
507
        return result;
508
    }
509

    
510
    @Override
511
    @Transactional(readOnly = false)
512
    public DeleteResult deleteTaxonNodes(List<TaxonNode> list, TaxonDeletionConfigurator config) {
513

    
514
        if (config == null){
515
        	config = new TaxonDeletionConfigurator();
516
        }
517
        DeleteResult result = new DeleteResult();
518
        Classification classification = null;
519
        List<TaxonNode> taxonNodes = new ArrayList<>(list);
520

    
521
        for (TaxonNode treeNode:taxonNodes){
522
        	if (treeNode != null){
523

    
524
        		TaxonNode taxonNode;
525
	            taxonNode = CdmBase.deproxy(treeNode);
526
	            TaxonNode parent = taxonNode.getParent();
527
	            	//check whether the node has children or the children are already deleted
528
	            if(taxonNode.hasChildNodes()) {
529
            		List<TaxonNode> children = new ArrayList<> ();
530
            		List<TaxonNode> childNodesList = taxonNode.getChildNodes();
531
        			children.addAll(childNodesList);
532
        			//To avoid NPE when child is also in list of taxonNodes, remove it from the list
533
        			Iterator<TaxonNode> it = taxonNodes.iterator();
534
        			for (TaxonNode child: children) {
535
        				while (it.hasNext()) {
536
        					if (it.next().equals(child)) {
537
        						it.remove();
538
        					}
539
        				}
540
        			}
541
        			int compare = config.getTaxonNodeConfig().getChildHandling().compareTo(ChildHandling.DELETE);
542
        			boolean childHandling = (compare == 0)? true: false;
543
            		if (childHandling){
544
            			boolean changeDeleteTaxon = false;
545
            			if (!config.getTaxonNodeConfig().isDeleteTaxon()){
546
            				config.getTaxonNodeConfig().setDeleteTaxon(true);
547
            				changeDeleteTaxon = true;
548
            			}
549
            			DeleteResult resultNodes = deleteTaxonNodes(children, config);
550
            			if (!resultNodes.isOk()){
551
                            result.addExceptions(resultNodes.getExceptions());
552
                            result.setStatus(resultNodes.getStatus());
553
                        }
554
            			if (changeDeleteTaxon){
555
            				config.getTaxonNodeConfig().setDeleteTaxon(false);
556
            			}
557

    
558
            		} else {
559
            			//move the children to the parent
560

    
561
            			for (TaxonNode child: childNodesList){
562
            				parent.addChildNode(child, child.getReference(), child.getMicroReference());
563
            			}
564

    
565
            		}
566
            	}
567

    
568
	            classification = taxonNode.getClassification();
569

    
570
	            if (classification.getRootNode().equals(taxonNode)){
571
	            	classification.removeRootNode();
572
	            	classification = null;
573
	            }else if (classification.getChildNodes().contains(taxonNode)){
574
            		Taxon taxon = taxonNode.getTaxon();
575
            		classification.deleteChildNode(taxonNode);
576

    
577
	            	//node is rootNode
578
	            	if (taxon != null){
579

    
580
	            		if (config.getTaxonNodeConfig().isDeleteTaxon()){
581
	            		    taxonService.saveOrUpdate(taxon);
582
	            		    saveOrUpdate(taxonNode);
583

    
584
			            	TaxonDeletionConfigurator configNew = new TaxonDeletionConfigurator();
585
			            	configNew.setClassificationUuid(classification.getUuid());
586
			            	DeleteResult resultTaxon = taxonService.deleteTaxon(taxon.getUuid(), configNew, classification.getUuid());
587
			            	if (!resultTaxon.isOk()){
588
                                result.addExceptions(resultTaxon.getExceptions());
589
                                result.setStatus(resultTaxon.getStatus());
590
                            }
591

    
592
		            	}
593
	            	}
594
            		classification = null;
595

    
596
	            } else {
597
	            	//classification = null;
598
	            	Taxon taxon = taxonNode.getTaxon();
599
	            	taxon = CdmBase.deproxy(taxon);
600
	            	if (taxon != null){
601
	            		taxon.removeTaxonNode(taxonNode);
602
	            		if (config.getTaxonNodeConfig().isDeleteTaxon()){
603
			            	TaxonDeletionConfigurator configNew = new TaxonDeletionConfigurator();
604
			            	saveOrUpdate(taxonNode);
605
			            	taxonService.saveOrUpdate(taxon);
606
			            	DeleteResult resultTaxon = taxonService.deleteTaxon(taxon.getUuid(), configNew, classification.getUuid());
607

    
608
                            if (!resultTaxon.isOk()){
609
                                result.addExceptions(resultTaxon.getExceptions());
610
                                result.setStatus(resultTaxon.getStatus());
611
                            }
612
		            	}
613
	            	}
614

    
615
	            }
616

    
617
	            result.addUpdatedObject(parent);
618
	            if(result.getCdmEntity() == null){
619
	                result.setCdmEntity(taxonNode);
620
                }
621
	            UUID uuid = dao.delete(taxonNode);
622
	            logger.debug("Deleted node " +uuid.toString());
623

    
624
	        }
625
        }
626
        /*if (classification != null){
627
            result.addUpdatedObject(classification);
628
        	DeleteResult resultClassification = classService.delete(classification);
629
        	 if (!resultClassification.isOk()){
630
                 result.addExceptions(resultClassification.getExceptions());
631
                 result.setStatus(resultClassification.getStatus());
632
             }
633
        }*/
634
        return result;
635
    }
636

    
637

    
638
    @Override
639
    @Transactional(readOnly = false)
640
    public DeleteResult deleteTaxonNodes(Collection<UUID> nodeUuids, TaxonDeletionConfigurator config) {
641
        List<TaxonNode> nodes = new ArrayList<>();
642
        for(UUID nodeUuid : nodeUuids) {
643
            nodes.add(dao.load(nodeUuid));
644
        }
645
        return deleteTaxonNodes(nodes, config);
646
    }
647

    
648

    
649
    @Override
650
    @Transactional(readOnly = false)
651
    public DeleteResult deleteTaxonNode(UUID nodeUUID, TaxonDeletionConfigurator config) {
652

    
653
    	TaxonNode node = CdmBase.deproxy(dao.load(nodeUUID));
654
    	return deleteTaxonNode(node, config);
655
    }
656

    
657
    @Override
658
    @Transactional(readOnly = false)
659
    public DeleteResult deleteTaxonNode(TaxonNode node, TaxonDeletionConfigurator config) {
660
        DeleteResult result = new DeleteResult();
661
        if (node == null){
662
            result.setAbort();
663
            result.addException(new Exception("The TaxonNode was already deleted."));
664
            return result;
665
        }
666
        Taxon taxon = null;
667
        try{
668
            taxon = HibernateProxyHelper.deproxy(node.getTaxon());
669
        }catch(NullPointerException e){
670
            result.setAbort();
671
            result.addException(new Exception("The Taxon was already deleted."));
672

    
673
        }
674

    
675

    
676
    	TaxonNode parent = HibernateProxyHelper.deproxy(node.getParent(), TaxonNode.class);
677
    	if (config == null){
678
    		config = new TaxonDeletionConfigurator();
679
    	}
680

    
681

    
682
    	if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.MOVE_TO_PARENT)){
683
    	   Object[] children = node.getChildNodes().toArray();
684
    	   TaxonNode childNode;
685
    	   for (Object child: children){
686
    	       childNode = (TaxonNode) child;
687
    	       parent.addChildNode(childNode, childNode.getReference(), childNode.getMicroReference());
688

    
689
    	   }
690
    	}else{
691
    	    result.includeResult(deleteTaxonNodes(node.getChildNodes(), config));
692
    	}
693

    
694
    	//remove node from DescriptiveDataSet
695
        commonService.getReferencingObjects(node).stream()
696
        .filter(obj->obj instanceof DescriptiveDataSet)
697
        .forEach(dataSet->{
698
            ((DescriptiveDataSet)dataSet).removeTaxonSubtree(node);
699
            dataSetService.saveOrUpdate((DescriptiveDataSet) dataSet);
700
        });
701

    
702
    	if (taxon != null){
703
        	if (config.getTaxonNodeConfig().isDeleteTaxon() && (config.isDeleteInAllClassifications() || taxon.getTaxonNodes().size() == 1)){
704
        		result = taxonService.deleteTaxon(taxon.getUuid(), config, node.getClassification().getUuid());
705
        		result.addUpdatedObject(parent);
706
        		if (result.isOk()){
707
        			return result;
708
        		}
709
        	} else {
710
        	    result.addUpdatedObject(taxon);
711
        	}
712
    	}
713
    	result.setCdmEntity(node);
714
    	boolean success = true;
715
    	if (taxon != null){
716
    	    success = taxon.removeTaxonNode(node);
717
    	    taxonService.saveOrUpdate(taxon);
718
    	}
719
    	dao.saveOrUpdate(parent);
720

    
721
    	result.addUpdatedObject(parent);
722

    
723
    	if (success){
724
			result.setStatus(Status.OK);
725
			if (parent != null){
726
    			parent = HibernateProxyHelper.deproxy(parent, TaxonNode.class);
727
    			int index = parent.getChildNodes().indexOf(node);
728
    			if (index > -1){
729
    			    parent.removeChild(index);
730
    			}
731
			}
732
    		if (!dao.delete(node, config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)).equals(null)){
733
    		    result.getUpdatedObjects().remove(node);
734
    			result.addDeletedObject(node);
735
    			return result;
736
    		} else {
737
    			result.setError();
738
    			return result;
739
    		}
740
    	}else{
741
    	    if (dao.findByUuid(node.getUuid()) != null){
742
        		result.setError();
743
        		result.addException(new Exception("The node can not be removed from the taxon."));
744
    		}
745
    		return result;
746
    	}
747
    }
748

    
749

    
750
    @Override
751
    public List<TaxonNode> listAllNodesForClassification(Classification classification, Integer start, Integer end) {
752
        return dao.getTaxonOfAcceptedTaxaByClassification(classification, start, end);
753
    }
754

    
755
    @Override
756
    public int countAllNodesForClassification(Classification classification) {
757
        return dao.countTaxonOfAcceptedTaxaByClassification(classification);
758
    }
759

    
760
    @Override
761
    @Transactional
762
    public UpdateResult moveTaxonNode(UUID taxonNodeUuid, UUID targetNodeUuid, int movingType){
763
        TaxonNode taxonNode = HibernateProxyHelper.deproxy(dao.load(taxonNodeUuid));
764
    	TaxonNode targetNode = HibernateProxyHelper.deproxy(dao.load(targetNodeUuid));
765
    	UpdateResult result = moveTaxonNode(taxonNode, targetNode, movingType);
766
    	return result;
767
    }
768

    
769
    @Override
770
    @Transactional
771
    public UpdateResult moveTaxonNode(TaxonNode taxonNode, TaxonNode newParent, int movingType){
772
        UpdateResult result = new UpdateResult();
773

    
774
        TaxonNode parentParent = HibernateProxyHelper.deproxy(newParent.getParent());
775
        Integer sortIndex = -1;
776
        if (movingType == 0){
777
            sortIndex = 0;
778
        }else if (movingType == 1){
779
            sortIndex = newParent.getSortIndex();
780
            newParent = parentParent;
781
        } else if (movingType == 2){
782
            sortIndex = newParent.getSortIndex() +1;
783
            newParent = parentParent;
784
        } else{
785
            result.setAbort();
786
            result.addException(new Exception("The moving type "+ movingType +" is not supported."));
787
        }
788

    
789
        taxonNode = newParent.addChildNode(taxonNode, sortIndex, taxonNode.getReference(),  taxonNode.getMicroReference());
790
        result.addUpdatedObject(taxonNode);
791

    
792
        return result;
793
    }
794

    
795
    @Override
796
    @Transactional
797
    public UpdateResult moveTaxonNodes(Set<UUID> taxonNodeUuids, UUID newParentNodeUuid, int movingType, IProgressMonitor monitor){
798

    
799
        if (monitor == null){
800
            monitor = DefaultProgressMonitor.NewInstance();
801
        }
802
        UpdateResult result = new UpdateResult();
803

    
804
        TaxonNode targetNode = dao.load(newParentNodeUuid);
805
        List<TaxonNode> nodes = dao.list(taxonNodeUuids, null, null, null, null);
806

    
807
        monitor.beginTask("Move Taxonnodes", nodes.size()*2);
808
        monitor.subTask("move taxon nodes");
809
        for (TaxonNode node: nodes){
810
            if (!monitor.isCanceled()){
811
                if (!nodes.contains(node.getParent())){
812
                    result.includeResult(moveTaxonNode(node, targetNode, movingType));
813
                }
814
                monitor.worked(1);
815
            }else{
816
                monitor.done();
817
                result.setAbort();
818
                break;
819
            }
820
        }
821
        if (!monitor.isCanceled()){
822
            monitor.subTask("saving and reindex");
823
            dao.saveOrUpdateAll(nodes);
824
        }else{
825
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
826
        }
827

    
828
        monitor.done();
829
        return result;
830
    }
831

    
832
    @Override
833
    public Pager<TaxonNodeAgentRelation> pageTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid,
834
            UUID agentUuid, UUID rankUuid, UUID relTypeUuid, Integer pageSize, Integer pageIndex, List<String> propertyPaths) {
835

    
836
        List<TaxonNodeAgentRelation> records = null;
837

    
838
        long count = dao.countTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid);
839
        if(PagerUtils.hasResultsInRange(count, pageIndex, pageSize)) {
840
            records = dao.listTaxonNodeAgentRelations(taxonUuid, classificationUuid,
841
                    agentUuid, rankUuid, relTypeUuid, PagerUtils.startFor(pageSize, pageIndex), PagerUtils.limitFor(pageSize), propertyPaths);
842
        }
843

    
844
        Pager<TaxonNodeAgentRelation> pager = new DefaultPagerImpl<>(pageIndex, count, pageSize, records);
845
        return pager;
846
    }
847

    
848
    @Override
849
    @Transactional
850
    public UpdateResult createNewTaxonNode(UUID parentNodeUuid, CreateTaxonDTO taxonDto,
851
            NamedSource source, String microref,
852
            TaxonNodeStatus status, Map<Language,LanguageString> statusNote){
853

    
854
        UpdateResult result = new UpdateResult();
855
        TaxonNode child = null;
856
        TaxonNode parent = null;
857
        try{
858
            TaxonName name = null;
859
            Taxon taxon = null;
860
            if (taxonDto.getTaxonUuid() != null){
861
                taxon = (Taxon) taxonService.load(taxonDto.getTaxonUuid());
862
                if (taxon == null){
863
                    throw new RuntimeException("Taxon for not found for id " + taxonDto.getTaxonUuid());
864
                }
865
            }else{
866
                if (taxonDto.getNameUuid() != null){
867
                    name = nameService.load(taxonDto.getNameUuid());
868
                    if (name == null){
869
                        throw new RuntimeException("Taxon name not found for id " + taxonDto.getTaxonUuid());
870
                    }
871
                } else {
872
                    UpdateResult tmpResult = nameService.parseName(taxonDto.getTaxonNameString(),
873
                            taxonDto.getCode(), taxonDto.getPreferredRank(),  true);
874
                    result.addUpdatedObjects(tmpResult.getUpdatedObjects());
875
                    name = (TaxonName)tmpResult.getCdmEntity();
876
                }
877
                Reference sec = null;
878
                if (taxonDto.getSecUuid() != null ){
879
                    sec = referenceService.load(taxonDto.getSecUuid());
880
                }
881
                if (name != null && !name.isPersited()){
882
                    for (HybridRelationship rel : name.getHybridChildRelations()){
883
                        if (!rel.getHybridName().isPersited()) {
884
                            nameService.save(rel.getHybridName());
885
                        }
886
                        if (!rel.getParentName().isPersited()) {
887
                            nameService.save(rel.getParentName());
888
                        }
889
                    }
890
                }
891
                taxon = Taxon.NewInstance(name, sec);
892
                taxon.setPublish(taxonDto.isPublish());
893
            }
894

    
895
            parent = dao.load(parentNodeUuid);
896
            if (source != null){
897
                if (source.isPersited()){
898
                    source = (NamedSource) sourceDao.load(source.getUuid());
899
                }
900
                if (source.getCitation() != null){
901
                    source.setCitation(referenceService.load(source.getCitation().getUuid()));
902
                }
903
                if (source.getNameUsedInSource() !=null){
904
                    source.setNameUsedInSource(nameService.load(source.getNameUsedInSource().getUuid()));
905
                }
906
            }
907

    
908
            child = parent.addChildTaxon(taxon, source);
909
            child.setStatus(status);
910

    
911
            if (statusNote != null){
912
                child.getStatusNote().putAll(statusNote);
913
            }
914

    
915
        }catch(Exception e){
916
            result.addException(e);
917
            result.setError();
918
            return result;
919
        }
920
        child = dao.save(child);
921

    
922
        result.addUpdatedObject(parent);
923
        if (child != null){
924
            result.setCdmEntity(child);
925
        }
926
        return result;
927
    }
928

    
929
    @Override
930
    @Transactional
931
    public UpdateResult addTaxonNodeAgentRelation(UUID taxonNodeUUID, UUID agentUUID, DefinedTerm relationshipType){
932
        UpdateResult result = new UpdateResult();
933
        TaxonNode node = dao.load(taxonNodeUUID);
934
        TeamOrPersonBase<?> agent = (TeamOrPersonBase<?>) agentService.load(agentUUID);
935
        node.addAgentRelation(relationshipType, agent);
936
        try{
937
            dao.merge(node, true);
938
        }catch (Exception e){
939
            result.setError();
940
            result.addException(e);
941
        }
942
        result.setCdmEntity(node);
943
        return result;
944
    }
945

    
946
    @Override
947
    @Transactional(readOnly=false)
948
    public UpdateResult setSecundumForSubtree(SecundumForSubtreeConfigurator config) {
949
        UpdateResult result = new UpdateResult();
950
        IProgressMonitor monitor = config.getMonitor();
951

    
952
        if (monitor == null){
953
            monitor = DefaultProgressMonitor.NewInstance();
954
        }
955
        monitor.beginTask("Update secundum reference for subtree", 100);
956
        monitor.subTask("Check start conditions");
957

    
958
        if (config.getSubtreeUuid() == null){
959
            result.setError();
960
            result.addException(new NullPointerException("No subtree given"));
961
            monitor.done();
962
            return result;
963
        }
964
        monitor.worked(1);
965
        TaxonNode subTree = load(config.getSubtreeUuid());
966
        if (subTree == null){
967
            result.setError();
968
            result.addException(new NullPointerException("Subtree does not exist"));
969
            monitor.done();
970
            return result;
971
        }
972
        monitor.worked(1);
973

    
974
        Reference newSec = null;
975
        if (config.getNewSecundum() != null){
976
            newSec = referenceService.load(config.getNewSecundum().getUuid());
977
            if (newSec == null){
978
                result.setError();
979
                result.addException(new NullPointerException("New secundum reference does not exist"));
980
                monitor.done();
981
                return result;
982
            }
983
        }
984
        monitor.worked(1);
985

    
986
        monitor.subTask("Count records");
987
        try {
988
            boolean includeRelatedTaxa = config.isIncludeProParteSynonyms() || config.isIncludeMisapplications();
989

    
990
            TreeIndex subTreeIndex = TreeIndex.NewInstance(subTree.treeIndex());
991
            int count = config.isIncludeAcceptedTaxa() ? dao.countSecundumForSubtreeAcceptedTaxa(subTreeIndex, newSec, config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail()):0;
992
            monitor.worked(2);
993
            count += config.isIncludeSynonyms() ? dao.countSecundumForSubtreeSynonyms(subTreeIndex, newSec, config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail()) :0;
994
            monitor.worked(3);
995
            count += includeRelatedTaxa ? dao.countSecundumForSubtreeRelations(subTreeIndex, newSec, config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail()):0;
996
            monitor.worked(2);
997
            if (monitor.isCanceled()){
998
                return result;
999
            }
1000

    
1001
            SubProgressMonitor subMonitor = SubProgressMonitor.NewStarted(monitor, 90, "Updating secundum for subtree", count * 2);  //*2 1 tick for update and 1 tick for commit
1002
            //Reference ref = config.getNewSecundum();
1003
            if (config.isIncludeAcceptedTaxa()){
1004
                monitor.subTask("Update Accepted Taxa");
1005
                Set<CdmBase> updatedTaxa = dao.setSecundumForSubtreeAcceptedTaxa(subTreeIndex, newSec,
1006
                        config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail(), subMonitor);
1007
                result.addUpdatedObjects(updatedTaxa);
1008
                if (monitor.isCanceled()){
1009
                    return result;
1010
                }
1011
            }
1012
            if (config.isIncludeSynonyms()){
1013
               monitor.subTask("Update Synonyms");
1014
               Set<CdmBase> updatedSynonyms = dao.setSecundumForSubtreeSynonyms(subTreeIndex, newSec,
1015
                       config.isOverwriteExisting(), config.isIncludeSharedTaxa() , config.isEmptySecundumDetail(), subMonitor);
1016
               result.addUpdatedObjects(updatedSynonyms);
1017
               if (monitor.isCanceled()){
1018
                   return result;
1019
               }
1020
            }
1021
            if (includeRelatedTaxa){
1022
                monitor.subTask("Update Related Taxa");
1023
                Set<UUID> relationTypes = getRelationTypesForSubtree(config);
1024
                Set<CdmBase> updatedRels = dao.setSecundumForSubtreeRelations(subTreeIndex, newSec,
1025
                        relationTypes, config.isOverwriteExisting(), config.isIncludeSharedTaxa() , config.isEmptySecundumDetail(), subMonitor);
1026
                result.addUpdatedObjects(updatedRels);
1027
                if (monitor.isCanceled()){
1028
                    return result;
1029
                }
1030
            }
1031
        } catch (Exception e) {
1032
            result.setError();
1033
            result.addException(e);
1034
        }
1035
        monitor.done();
1036
        return result;
1037
    }
1038

    
1039
    @Override
1040
    @Transactional(readOnly=false)
1041
    public UpdateResult setPublishForSubtree(PublishForSubtreeConfigurator config){
1042
        UpdateResult result = new UpdateResult();
1043
        IProgressMonitor monitor = config.getMonitor();
1044
        if (monitor == null){
1045
            monitor = DefaultProgressMonitor.NewInstance();
1046
        }
1047
        monitor.beginTask("Update publish flag for subtree", 100);
1048
        monitor.subTask("Check start conditions");
1049

    
1050
        if (config.getSubtreeUuid() == null){
1051
            result.setError();
1052
            result.addException(new NullPointerException("No subtree given"));
1053
            monitor.done();
1054
            return result;
1055
        }
1056
        monitor.worked(1);
1057

    
1058
        TaxonNode subTree = find(config.getSubtreeUuid());
1059
        if (subTree == null){
1060
            result.setError();
1061
            result.addException(new NullPointerException("Subtree does not exist"));
1062
            monitor.done();
1063
            return result;
1064
        }
1065
        monitor.worked(1);
1066

    
1067
        monitor.subTask("Count records");
1068
        boolean includeAcceptedTaxa = config.isIncludeAcceptedTaxa();
1069
        boolean publish = config.isPublish();
1070
        boolean includeSynonyms = config.isIncludeSynonyms();
1071
        boolean includeSharedTaxa = config.isIncludeSharedTaxa();
1072
        boolean includeHybrids = config.isIncludeHybrids();
1073
        boolean includeRelatedTaxa = config.isIncludeProParteSynonyms() || config.isIncludeMisapplications();
1074
        try {
1075
            TreeIndex subTreeIndex = TreeIndex.NewInstance(subTree.treeIndex());
1076
            int count = includeAcceptedTaxa ? dao.countPublishForSubtreeAcceptedTaxa(subTreeIndex, publish, includeSharedTaxa, includeHybrids):0;
1077
            monitor.worked(3);
1078
            count += includeSynonyms ? dao.countPublishForSubtreeSynonyms(subTreeIndex, publish, includeSharedTaxa, includeHybrids):0;
1079
            monitor.worked(3);
1080
            count += includeRelatedTaxa ? dao.countPublishForSubtreeRelatedTaxa(subTreeIndex, publish, includeSharedTaxa, includeHybrids):0;
1081
            monitor.worked(2);
1082
            if (monitor.isCanceled()){
1083
                return result;
1084
            }
1085

    
1086
            SubProgressMonitor subMonitor = SubProgressMonitor.NewStarted(monitor, 90, "Updating secundum for subtree", count);
1087
            if (includeAcceptedTaxa){
1088
                monitor.subTask("Update Accepted Taxa");
1089
                @SuppressWarnings("rawtypes")
1090
                Set<TaxonBase> updatedTaxa = dao.setPublishForSubtreeAcceptedTaxa(subTreeIndex,
1091
                        publish, includeSharedTaxa, includeHybrids, subMonitor);
1092
                result.addUpdatedObjects(updatedTaxa);
1093
                if (monitor.isCanceled()){
1094
                    return result;
1095
                }
1096
            }
1097
            if (includeSynonyms){
1098
                monitor.subTask("Update Synonyms");
1099
                @SuppressWarnings("rawtypes")
1100
                Set<TaxonBase> updatedSynonyms = dao.setPublishForSubtreeSynonyms(subTreeIndex,
1101
                        publish, includeSharedTaxa, includeHybrids, subMonitor);
1102
                result.addUpdatedObjects(updatedSynonyms);
1103
                if (monitor.isCanceled()){
1104
                    return result;
1105
                }
1106
            }
1107
            if (includeRelatedTaxa){
1108
                monitor.subTask("Update Related Taxa");
1109
                Set<UUID> relationTypes = getRelationTypesForSubtree(config);
1110
                if (config.isIncludeMisapplications()){
1111
                    relationTypes.addAll(TaxonRelationshipType.misappliedNameUuids());
1112
                }
1113
                if (config.isIncludeProParteSynonyms()){
1114
                    relationTypes.addAll(TaxonRelationshipType.proParteOrPartialSynonymUuids());
1115
                }
1116
                @SuppressWarnings("rawtypes")
1117
                Set<TaxonBase> updatedTaxa = dao.setPublishForSubtreeRelatedTaxa(subTreeIndex, publish,
1118
                        relationTypes, includeSharedTaxa, includeHybrids, subMonitor);
1119
                result.addUpdatedObjects(updatedTaxa);
1120
                if (monitor.isCanceled()){
1121
                    return result;
1122
                }
1123
            }
1124
        } catch (Exception e) {
1125
            result.setError();
1126
            result.addException(e);
1127
        }
1128

    
1129
        monitor.done();
1130
        return result;
1131
    }
1132

    
1133
    private Set<UUID> getRelationTypesForSubtree(ForSubtreeConfiguratorBase config) {
1134
        Set<UUID> relationTypes = new HashSet<>();
1135
        if (config.isIncludeMisapplications()){
1136
            relationTypes.addAll(TaxonRelationshipType.misappliedNameUuids());
1137
        }
1138
        if (config.isIncludeProParteSynonyms()){
1139
            relationTypes.addAll(TaxonRelationshipType.proParteOrPartialSynonymUuids());
1140
        }
1141
        return relationTypes;
1142
    }
1143

    
1144
    @Override
1145
    public long count(TaxonNodeFilter filter){
1146
        return nodeFilterDao.count(filter);
1147
    }
1148

    
1149
    @Override
1150
    public List<UUID> uuidList(TaxonNodeFilter filter){
1151
        return nodeFilterDao.listUuids(filter);
1152
    }
1153

    
1154
    @Override
1155
    public List<Integer> idList(TaxonNodeFilter filter){
1156
        return nodeFilterDao.idList(filter);
1157
    }
1158

    
1159
    @Override
1160
    public TaxonNodeDto findCommonParentDto(Collection<TaxonNodeDto> nodes) {
1161
        TaxonNodeDto commonParent = null;
1162
        List<String> treePath = null;
1163
        for (TaxonNodeDto nodeDto : nodes) {
1164
            String nodeTreeIndex = nodeDto.getTreeIndex();
1165
            nodeTreeIndex = nodeTreeIndex.replaceFirst("#", "");
1166
            String[] split = nodeTreeIndex.split("#");
1167
            if(treePath == null){
1168
                treePath = Arrays.asList(split);
1169
            }
1170
            else{
1171
                List<String> match = new ArrayList<>();
1172
                for(int i=0;i<treePath.size();i++){
1173
                    if(i>=split.length){
1174
                        //current tree index is shorter so break
1175
                        break;
1176
                    }
1177
                    else if(split[i].equals(treePath.get(i))){
1178
                        //match found
1179
                        match.add(treePath.get(i));
1180
                    }
1181
                    else{
1182
                        //first mismatch found
1183
                        break;
1184
                    }
1185
                }
1186
                treePath = match;
1187
                if(treePath.isEmpty()){
1188
                    //no common parent found for at least two nodes
1189
                    //-> they belong to a different classification
1190
                    break;
1191
                }
1192
            }
1193
        }
1194
        if(treePath!=null && !treePath.isEmpty()) {
1195
            //get last index
1196
            int nodeId = Integer.parseInt(treePath.get(treePath.size()-1));
1197
            TaxonNode taxonNode = dao.load(nodeId, null);
1198
            commonParent = new TaxonNodeDto(taxonNode);
1199
        }
1200
        return commonParent;
1201
    }
1202

    
1203
    @Override
1204
    public List<TaxonDistributionDTO> getTaxonDistributionDTO(List<UUID> nodeUuids, List<String> propertyPaths,
1205
            Authentication authentication, boolean openChildren, TaxonNodeSortMode sortMode){
1206

    
1207
        nodeUuids = nodeUuids.stream().distinct().collect(Collectors.toList());
1208
        List<TaxonNode> nodes = new ArrayList<>();
1209

    
1210
        List<TaxonNode> parentNodes = load(nodeUuids, propertyPaths);
1211
        if (sortMode != null){
1212
            parentNodes.sort(sortMode.comparator());
1213
        }
1214
        if (openChildren){
1215
            //TODO we could remove nodes which are children of other nodes in parentNodes list here as they are duplicates
1216
            for (TaxonNode node: parentNodes){
1217
                if (node == null || nodes.contains(node)){
1218
                    continue;
1219
                }
1220
                nodes.add(node);
1221
                List<TaxonNode> children = new ArrayList<>();
1222
                children.addAll(loadChildNodesOfTaxonNode(node,
1223
                        propertyPaths, true,  true, sortMode));
1224
                for (TaxonNode child: children){
1225
                    if (!nodes.contains(child)){
1226
                        nodes.add(child);
1227
                    }
1228
                }
1229
            }
1230
        }else{
1231
            nodes.addAll(nodes);
1232
        }
1233

    
1234
        List<TaxonDistributionDTO> result = new ArrayList<>();
1235
        boolean hasPermission = false;
1236
        for(TaxonNode node: nodes){
1237
            if (authentication != null ) {
1238
                hasPermission = permissionEvaluator.hasPermission(authentication, node, Operation.UPDATE);
1239
            }else {
1240
                hasPermission = true;
1241
            }
1242
            if (node.getTaxon() != null && hasPermission){
1243
                try{
1244
                    TaxonDistributionDTO dto = new TaxonDistributionDTO(node);
1245
                    result.add(dto);
1246
                }catch(Exception e){
1247
                    logger.error(e.getMessage(), e);
1248
                }
1249
            }
1250
        }
1251
        return result;
1252
    }
1253

    
1254
    @Override
1255
    public <S extends TaxonNode> Pager<S> page(Class<S> clazz, List<Restriction<?>> restrictions, Integer pageSize,
1256
            Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths) {
1257
        return page(clazz, restrictions, pageSize, pageIndex, orderHints, propertyPaths, INCLUDE_UNPUBLISHED);
1258
    }
1259

    
1260
    @Override
1261
    public <S extends TaxonNode> Pager<S> page(Class<S> clazz, List<Restriction<?>> restrictions, Integer pageSize,
1262
            Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths, boolean includeUnpublished) {
1263

    
1264
        List<S> records;
1265
        long resultSize = dao.count(clazz, restrictions);
1266
        if(AbstractPagerImpl.hasResultsInRange(resultSize, pageIndex, pageSize)){
1267
            records = dao.list(clazz, restrictions, pageSize, pageIndex, orderHints, propertyPaths, includeUnpublished);
1268
        } else {
1269
            records = new ArrayList<>();
1270
        }
1271
        Pager<S> pager = new DefaultPagerImpl<>(pageIndex, resultSize, pageSize, records);
1272
        return pager;
1273
    }
1274

    
1275
    @Override
1276
    public List<TaxonDistributionDTO> getTaxonDistributionDTO(List<UUID> nodeUuids,
1277
            List<String> propertyPaths, boolean openChildren) {
1278
        return getTaxonDistributionDTO(nodeUuids, propertyPaths, null, openChildren, null);
1279
    }
1280

    
1281
    @Override
1282
    @Transactional(readOnly = false)
1283
    public UpdateResult cloneSubtree(SubtreeCloneConfigurator config) {
1284
        UpdateResult result = new UpdateResult();
1285

    
1286
        if (config.getSubTreeUuids().isEmpty()){
1287
            return result;
1288
        }
1289

    
1290
        //TODO error handling
1291
        Reference taxonSecundum = config.isReuseTaxa() || config.isReuseTaxonSecundum() || config.getTaxonSecundumUuid() == null ?
1292
                null : referenceDao.findByUuid(config.getTaxonSecundumUuid());
1293
        config.setTaxonSecundum(taxonSecundum);
1294

    
1295
        Reference parentChildReference = config.isReuseParentChildReference() || config.getParentChildReferenceUuid() == null ?
1296
                null : referenceDao.findByUuid(config.getParentChildReferenceUuid());
1297
        config.setParentChildReference(parentChildReference);
1298

    
1299
        Reference taxonRelationshipReference = config.getRelationTypeToOldTaxon() == null ?
1300
                null : referenceDao.findByUuid(config.getRelationshipReferenceUuid());
1301
        config.setRelationshipReference(taxonRelationshipReference);
1302

    
1303
        Classification classificationClone = Classification.NewInstance(config.getNewClassificationName());
1304

    
1305
        if (config.isReuseClassificationReference()){
1306
            TaxonNode anyNode = dao.findByUuid(config.getSubTreeUuids().iterator().next());
1307
            if (anyNode != null){
1308
                Reference oldClassificationRef = anyNode.getClassification().getReference();
1309
                classificationClone.setReference(oldClassificationRef);
1310
            }
1311
        }else if (config.getClassificationReferenceUuid() != null) {
1312
            Reference classificationReference = referenceDao.findByUuid(config.getClassificationReferenceUuid());
1313
            classificationClone.setReference(classificationReference);
1314
        }
1315

    
1316
        //clone taxa and taxon nodes
1317
//      List<Integer> childNodeIds = taxonNodeService.idList(taxonNodeFilter);
1318
//      List<TaxonNode> childNodes = taxonNodeService.loadByIds(childNodeIds, null);
1319
        List<TaxonNode> rootNodes = this.find(config.getSubTreeUuids());
1320
        for (TaxonNode taxonNode : rootNodes) {
1321
            cloneTaxonRecursive(taxonNode, classificationClone.getRootNode(), config);
1322
        }
1323
        classificationDao.saveOrUpdate(classificationClone);
1324
        result.setCdmEntity(classificationClone);
1325
        return result;
1326
    }
1327

    
1328
    private void cloneTaxonRecursive(TaxonNode originalParentNode, TaxonNode parentNodeClone,
1329
            SubtreeCloneConfigurator config){
1330

    
1331
        Taxon originalTaxon = CdmBase.deproxy(originalParentNode.getTaxon());
1332
        TaxonNode childNodeClone;
1333
        if (originalTaxon != null){
1334
            String microReference = null;
1335
            if (config.isReuseTaxa()){
1336
                childNodeClone = parentNodeClone.addChildTaxon(originalTaxon, config.getParentChildReference(), microReference);
1337
            }else{
1338
                Taxon cloneTaxon = originalTaxon.clone(config.isIncludeSynonymsIncludingManAndProParte(),
1339
                        config.isIncludeTaxonRelationshipsExcludingManAndProParte(),
1340
                        config.isIncludeDescriptiveData(), config.isIncludeMedia());
1341

    
1342
                //name
1343
                if (!config.isReuseNames()){
1344
                    cloneTaxon.setName(cloneTaxon.getName().clone());
1345
                    //TODO needs further handling for name relationships etc., see #9349
1346
                    cloneTaxon.getSynonyms().forEach(syn ->
1347
                        syn.setName(syn.getName() == null ? null : syn.getName().clone()));
1348
                }
1349

    
1350
                if (!config.isReuseTaxonSecundum()){
1351
                    cloneTaxon.setSec(config.getTaxonSecundum());
1352
                }
1353

    
1354
                //add relation between taxa
1355
                if (config.getRelationTypeToOldTaxon() != null){
1356
                    TaxonRelationship rel = cloneTaxon.addTaxonRelation(originalParentNode.getTaxon(), config.getRelationTypeToOldTaxon(),
1357
                            config.getRelationshipReference(), microReference);
1358
                    rel.setDoubtful(config.isRelationDoubtful());
1359
                }
1360
                childNodeClone = parentNodeClone.addChildTaxon(cloneTaxon, config.getParentChildReference(), microReference);
1361
            }
1362

    
1363
            //probably necessary as taxon nodes do not cascade
1364
            dao.saveOrUpdate(childNodeClone);
1365

    
1366
        }else{
1367
            childNodeClone = parentNodeClone;
1368
        }
1369
        //add children
1370
        if (config.isDoRecursive()){
1371
            List<TaxonNode> originalChildNodes = originalParentNode.getChildNodes();
1372
            HHH_9751_Util.removeAllNull(originalChildNodes);
1373

    
1374
            for (TaxonNode originalChildNode : originalChildNodes) {
1375
                cloneTaxonRecursive(originalChildNode, childNodeClone, config);
1376
            }
1377
        }
1378
    }
1379

    
1380
    @Override
1381
    public HomotypicGroupDto getHomotypicGroupDto(UUID homotypicGroupUuid, UUID nodeUuid) {
1382

    
1383
        HomotypicalGroup group = homotypicalGroupDao.load(homotypicGroupUuid);
1384
        if (group == null){
1385
            return null;
1386
        }
1387
        return new HomotypicGroupDto(group, nodeUuid);
1388
    }
1389
}
(88-88/97)