Project

General

Profile

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

    
32
import eu.etaxonomy.cdm.api.service.UpdateResult.Status;
33
import eu.etaxonomy.cdm.api.service.config.ForSubtreeConfiguratorBase;
34
import eu.etaxonomy.cdm.api.service.config.NodeDeletionConfigurator.ChildHandling;
35
import eu.etaxonomy.cdm.api.service.config.PublishForSubtreeConfigurator;
36
import eu.etaxonomy.cdm.api.service.config.SecundumForSubtreeConfigurator;
37
import eu.etaxonomy.cdm.api.service.config.SubtreeCloneConfigurator;
38
import eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator;
39
import eu.etaxonomy.cdm.api.service.config.TaxonNodeDeletionConfigurator;
40
import eu.etaxonomy.cdm.api.service.dto.CdmEntityIdentifier;
41
import eu.etaxonomy.cdm.api.service.dto.CreateTaxonDTO;
42
import eu.etaxonomy.cdm.api.service.dto.TaxonDistributionDTO;
43
import eu.etaxonomy.cdm.api.service.pager.Pager;
44
import eu.etaxonomy.cdm.api.service.pager.PagerUtils;
45
import eu.etaxonomy.cdm.api.service.pager.impl.AbstractPagerImpl;
46
import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
47
import eu.etaxonomy.cdm.common.monitor.DefaultProgressMonitor;
48
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
49
import eu.etaxonomy.cdm.common.monitor.SubProgressMonitor;
50
import eu.etaxonomy.cdm.compare.taxon.HomotypicGroupTaxonComparator;
51
import eu.etaxonomy.cdm.compare.taxon.TaxonNodeSortMode;
52
import eu.etaxonomy.cdm.filter.TaxonNodeFilter;
53
import eu.etaxonomy.cdm.hibernate.HHH_9751_Util;
54
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
55
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
56
import eu.etaxonomy.cdm.model.common.CdmBase;
57
import eu.etaxonomy.cdm.model.common.Language;
58
import eu.etaxonomy.cdm.model.common.LanguageString;
59
import eu.etaxonomy.cdm.model.common.TreeIndex;
60
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
61
import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
62
import eu.etaxonomy.cdm.model.description.DescriptiveDataSet;
63
import eu.etaxonomy.cdm.model.description.TaxonDescription;
64
import eu.etaxonomy.cdm.model.metadata.SecReferenceHandlingEnum;
65
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
66
import eu.etaxonomy.cdm.model.name.HybridRelationship;
67
import eu.etaxonomy.cdm.model.name.Rank;
68
import eu.etaxonomy.cdm.model.name.TaxonName;
69
import eu.etaxonomy.cdm.model.permission.Operation;
70
import eu.etaxonomy.cdm.model.reference.NamedSource;
71
import eu.etaxonomy.cdm.model.reference.Reference;
72
import eu.etaxonomy.cdm.model.taxon.Classification;
73
import eu.etaxonomy.cdm.model.taxon.ITaxonTreeNode;
74
import eu.etaxonomy.cdm.model.taxon.Synonym;
75
import eu.etaxonomy.cdm.model.taxon.SynonymType;
76
import eu.etaxonomy.cdm.model.taxon.Taxon;
77
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
78
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
79
import eu.etaxonomy.cdm.model.taxon.TaxonNodeAgentRelation;
80
import eu.etaxonomy.cdm.model.taxon.TaxonNodeStatus;
81
import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
82
import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
83
import eu.etaxonomy.cdm.model.term.DefinedTerm;
84
import eu.etaxonomy.cdm.persistence.dao.common.Restriction;
85
import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;
86
import eu.etaxonomy.cdm.persistence.dao.name.IHomotypicalGroupDao;
87
import eu.etaxonomy.cdm.persistence.dao.reference.IOriginalSourceDao;
88
import eu.etaxonomy.cdm.persistence.dao.reference.IReferenceDao;
89
import eu.etaxonomy.cdm.persistence.dao.taxon.IClassificationDao;
90
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeDao;
91
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeFilterDao;
92
import eu.etaxonomy.cdm.persistence.dto.HomotypicGroupDto;
93
import eu.etaxonomy.cdm.persistence.dto.SortableTaxonNodeQueryResult;
94
import eu.etaxonomy.cdm.persistence.dto.SortableTaxonNodeQueryResultComparator;
95
import eu.etaxonomy.cdm.persistence.dto.TaxonNodeDto;
96
import eu.etaxonomy.cdm.persistence.permission.ICdmPermissionEvaluator;
97
import eu.etaxonomy.cdm.persistence.query.OrderHint;
98

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

    
109
    private static final Logger logger = Logger.getLogger(TaxonNodeServiceImpl.class);
110

    
111
    @Autowired
112
    private IBeanInitializer defaultBeanInitializer;
113

    
114
    @Autowired
115
    private ITaxonService taxonService;
116

    
117
    @Autowired
118
    private IReferenceService referenceService;
119

    
120
    @Autowired
121
    private IDescriptiveDataSetService dataSetService;
122

    
123
    @Autowired
124
    private IAgentService agentService;
125

    
126
    @Autowired
127
    private INameService nameService;
128

    
129
    @Autowired
130
    private IOriginalSourceDao sourceDao;
131

    
132
    @Autowired
133
    private ITaxonNodeFilterDao nodeFilterDao;
134

    
135
    @Autowired
136
    private IReferenceDao referenceDao;
137

    
138
    @Autowired
139
    private IClassificationDao classificationDao;
140

    
141
    @Autowired
142
    private IHomotypicalGroupDao homotypicalGroupDao;
143

    
144
    @Autowired
145
    IProgressMonitorService progressMonitorService;
146

    
147
    @Autowired
148
    private ICdmPermissionEvaluator permissionEvaluator;
149

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

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

    
171
        HHH_9751_Util.removeAllNull(childNodes);
172

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

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

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

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

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

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

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

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

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

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

    
234
        TaxonNode parentNode = dao.load(taxonNodeUuid);
235

    
236
        List<CdmBase> allRecords = new ArrayList<>();
237

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

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

    
248
            allRecords.addAll(synList);
249
        }
250

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

    
254
        TaxonName parentName = null;
255

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

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

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

    
293
    @Override
294
    public TaxonNodeDto dto(UUID taxonUuid, UUID classificationUuid) {
295
        if (taxonUuid == null){
296
            return null;
297
        }
298
        List<TaxonNodeDto> taxonNodes = dao.getTaxonNodeForTaxonInClassificationDto(taxonUuid, classificationUuid);
299
        if (!taxonNodes.isEmpty()){
300
            return taxonNodes.get(0);
301
        }
302
        return null;
303
    }
304

    
305
    @Override
306
    @Autowired
307
    protected void setDao(ITaxonNodeDao dao) {
308
        this.dao = dao;
309
    }
310

    
311
    @Override
312
    @Transactional(readOnly = false)
313
    public DeleteResult makeTaxonNodeASynonymOfAnotherTaxonNode(TaxonNode oldTaxonNode, TaxonNode newAcceptedTaxonNode,
314
            SynonymType synonymType, Reference citation, String microReference, SecReferenceHandlingEnum secHandling, boolean setNameInSource)  {
315

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

    
322
        if(oldTaxonNode.equals(newAcceptedTaxonNode)){
323
            throw new IllegalArgumentException("Taxon can not be made synonym of its own.");
324
        }
325

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

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

    
357
        Synonym newSyn = newAcceptedTaxon.addSynonymName(newSynonymName, newSec, microReference, synonymType);
358
        if (newSec == null){
359
            newSyn.setSec(newSec);
360
        }
361
        newSyn.setPublish(oldTaxon.isPublish());
362

    
363
        // Move Synonyms to new Taxon
364
        // From ticket 3163 we can move taxon with accepted name having homotypic synonyms
365
        List<Synonym> synonymsInHomotypicalGroup = null;
366

    
367
        //the synonyms of the homotypical group of the old taxon
368
        if (synonymType.equals(SynonymType.HOMOTYPIC_SYNONYM_OF())){
369
        	synonymsInHomotypicalGroup = oldTaxon.getSynonymsInGroup(group);
370
        }
371

    
372
        Set<Synonym> syns = new HashSet<>(oldTaxon.getSynonyms());
373
        for(Synonym synonym : syns){
374
            SynonymType srt;
375
            if(synonym.getHomotypicGroup()!= null
376
                    && synonym.getHomotypicGroup().equals(newAcceptedTaxonName.getHomotypicalGroup())) {
377
                srt = SynonymType.HOMOTYPIC_SYNONYM_OF();
378
            } else if(synonym.getType() != null && synonym.getType().equals(SynonymType.HOMOTYPIC_SYNONYM_OF())) {
379
            	if (synonymType.equals(SynonymType.HOMOTYPIC_SYNONYM_OF())){
380
            		srt = SynonymType.HOMOTYPIC_SYNONYM_OF();
381
            	} else{
382
            		srt = SynonymType.HETEROTYPIC_SYNONYM_OF();
383
            	}
384
            } else {
385
                if (synonymsInHomotypicalGroup != null && synonymsInHomotypicalGroup.contains(synonym)){
386
                    srt = SynonymType.HOMOTYPIC_SYNONYM_OF();
387
                }else{
388
                    srt = synonym.getType();
389
                }
390

    
391
            }
392
            if (secHandling != null &&  !secHandling.equals(SecReferenceHandlingEnum.KeepOrWarn)){
393
                synonym.setSec(newSec);
394
            }
395
            newAcceptedTaxon.addSynonym(synonym, srt);
396

    
397
        }
398

    
399

    
400
        // CHILD NODES
401
        if(oldTaxonNode.getChildNodes() != null && oldTaxonNode.getChildNodes().size() != 0){
402
        	List<TaxonNode> childNodes = new ArrayList<>();
403
        	for (TaxonNode childNode : oldTaxonNode.getChildNodes()){
404
        		childNodes.add(childNode);
405
        	}
406
            for(TaxonNode childNode :childNodes){
407
                newAcceptedTaxonNode.addChildNode(childNode, childNode.getReference(), childNode.getMicroReference()); // childNode.getSynonymToBeUsed()
408
            }
409
        }
410

    
411
        //Move Taxon RelationShips to new Taxon
412
        for(TaxonRelationship taxonRelationship : oldTaxon.getTaxonRelations()){
413
            Taxon fromTaxon = HibernateProxyHelper.deproxy(taxonRelationship.getFromTaxon());
414
            Taxon toTaxon = HibernateProxyHelper.deproxy(taxonRelationship.getToTaxon());
415
            if (fromTaxon == oldTaxon){
416
                newAcceptedTaxon.addTaxonRelation(taxonRelationship.getToTaxon(), taxonRelationship.getType(),
417
                        taxonRelationship.getCitation(), taxonRelationship.getCitationMicroReference());
418

    
419
            }else if(toTaxon == oldTaxon){
420
               fromTaxon.addTaxonRelation(newAcceptedTaxon, taxonRelationship.getType(),
421
                        taxonRelationship.getCitation(), taxonRelationship.getCitationMicroReference());
422
               taxonService.saveOrUpdate(fromTaxon);
423

    
424
            }else{
425
                logger.warn("Taxon is not part of its own Taxonrelationship");
426
            }
427
            // Remove old relationships
428

    
429
            fromTaxon.removeTaxonRelation(taxonRelationship);
430
            toTaxon.removeTaxonRelation(taxonRelationship);
431
            taxonRelationship.setToTaxon(null);
432
            taxonRelationship.setFromTaxon(null);
433
        }
434

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

    
455
        taxonService.saveOrUpdate(newAcceptedTaxon);
456

    
457
        taxonService.saveOrUpdate(oldTaxon);
458
        taxonService.getSession().flush();
459

    
460
        TaxonDeletionConfigurator conf = new TaxonDeletionConfigurator();
461
        conf.setDeleteSynonymsIfPossible(false);
462
        conf.setDeleteNameIfPossible(false);
463
        DeleteResult result = taxonService.isDeletable(oldTaxon.getUuid(), conf);
464

    
465

    
466
        if (result.isOk()){
467
        	 result = taxonService.deleteTaxon(oldTaxon.getUuid(), conf, classification.getUuid());
468

    
469
        }else{
470
        	result.setStatus(Status.OK);
471
        	TaxonNodeDeletionConfigurator config = new TaxonNodeDeletionConfigurator();
472
        	config.setDeleteElement(false);
473
        	conf.setTaxonNodeConfig(config);
474
        	result.includeResult(deleteTaxonNode(oldTaxonNode, conf));
475
        }
476

    
477
        result.addUpdatedObject(newAcceptedTaxon);
478

    
479

    
480
        //oldTaxonNode.delete();
481
        return result;
482
    }
483
    @Override
484
    @Transactional(readOnly = false)
485
    public DeleteResult makeTaxonNodeSynonymsOfAnotherTaxonNode( Set<UUID> oldTaxonNodeUuids,
486
            UUID newAcceptedTaxonNodeUUIDs,
487
            SynonymType synonymType,
488
            UUID citation,
489
            String microReference,
490
            SecReferenceHandlingEnum secHandling,
491
            boolean setNameInSource) {
492
    	DeleteResult result = new DeleteResult();
493
    	for (UUID nodeUuid: oldTaxonNodeUuids) {
494
    		result.includeResult(makeTaxonNodeASynonymOfAnotherTaxonNode(nodeUuid, newAcceptedTaxonNodeUUIDs, synonymType, citation, microReference, secHandling, setNameInSource));
495
    	}
496
    	return result;
497
    }
498

    
499
    @Override
500
    @Transactional(readOnly = false)
501
    public DeleteResult makeTaxonNodeASynonymOfAnotherTaxonNode(UUID oldTaxonNodeUuid,
502
            UUID newAcceptedTaxonNodeUUID,
503
            SynonymType synonymType,
504
            UUID citationUuid,
505
            String microReference,
506
            SecReferenceHandlingEnum secHandling,
507
            boolean setNameInSource) {
508

    
509
        TaxonNode oldTaxonNode = dao.load(oldTaxonNodeUuid);
510
        TaxonNode oldTaxonParentNode = oldTaxonNode.getParent();
511
        TaxonNode newTaxonNode = dao.load(newAcceptedTaxonNodeUUID);
512
        Reference citation = referenceDao.load(citationUuid);
513

    
514
        switch (secHandling){
515
        case AlwaysDelete:
516
            citation = null;
517
            break;
518
        case UseNewParentSec:
519
            citation = newTaxonNode.getTaxon() != null? newTaxonNode.getTaxon().getSec(): null;
520
            break;
521
        case KeepOrWarn:
522

    
523
            Reference synSec = oldTaxonNode.getTaxon().getSec();
524
            if (synSec != null ){
525
                citation = CdmBase.deproxy(synSec);
526
            }
527
            break;
528
        case KeepOrSelect:
529

    
530
            break;
531
        default:
532
            break;
533
    }
534

    
535

    
536
        DeleteResult result = makeTaxonNodeASynonymOfAnotherTaxonNode(oldTaxonNode,
537
                newTaxonNode,
538
                synonymType,
539
                citation,
540
                microReference,
541
                secHandling, setNameInSource);
542

    
543
        result.addUpdatedCdmId(new CdmEntityIdentifier(oldTaxonParentNode.getId(), TaxonNode.class));
544
        result.addUpdatedCdmId(new CdmEntityIdentifier(newTaxonNode.getId(), TaxonNode.class));
545
        result.setCdmEntity(oldTaxonParentNode);
546
        return result;
547
    }
548

    
549
    @Override
550
    @Transactional(readOnly = false)
551
    public DeleteResult deleteTaxonNodes(List<TaxonNode> list, TaxonDeletionConfigurator config) {
552

    
553
        if (config == null){
554
        	config = new TaxonDeletionConfigurator();
555
        }
556
        DeleteResult result = new DeleteResult();
557
        Classification classification = null;
558
        List<TaxonNode> taxonNodes = new ArrayList<>(list);
559

    
560
        for (TaxonNode treeNode:taxonNodes){
561
        	if (treeNode != null){
562

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

    
597
            		} else {
598
            			//move the children to the parent
599

    
600
            			for (TaxonNode child: childNodesList){
601
            				parent.addChildNode(child, child.getReference(), child.getMicroReference());
602
            			}
603

    
604
            		}
605
            	}
606

    
607
	            classification = taxonNode.getClassification();
608

    
609
	            if (classification.getRootNode().equals(taxonNode)){
610
	            	classification.removeRootNode();
611
	            	classification = null;
612
	            }else if (classification.getChildNodes().contains(taxonNode)){
613
            		Taxon taxon = taxonNode.getTaxon();
614
            		classification.deleteChildNode(taxonNode);
615

    
616
	            	//node is rootNode
617
	            	if (taxon != null){
618

    
619
	            		if (config.getTaxonNodeConfig().isDeleteTaxon()){
620
	            		    taxonService.saveOrUpdate(taxon);
621
	            		    saveOrUpdate(taxonNode);
622

    
623
			            	TaxonDeletionConfigurator configNew = new TaxonDeletionConfigurator();
624
			            	configNew.setClassificationUuid(classification.getUuid());
625
			            	DeleteResult resultTaxon = taxonService.deleteTaxon(taxon.getUuid(), configNew, classification.getUuid());
626
			            	if (!resultTaxon.isOk()){
627
                                result.addExceptions(resultTaxon.getExceptions());
628
                                result.setStatus(resultTaxon.getStatus());
629
                            }
630

    
631
		            	}
632
	            	}
633
            		classification = null;
634

    
635
	            } else {
636
	            	//classification = null;
637
	            	Taxon taxon = taxonNode.getTaxon();
638
	            	taxon = CdmBase.deproxy(taxon);
639
	            	if (taxon != null){
640
	            		taxon.removeTaxonNode(taxonNode);
641
	            		if (config.getTaxonNodeConfig().isDeleteTaxon()){
642
			            	TaxonDeletionConfigurator configNew = new TaxonDeletionConfigurator();
643
			            	saveOrUpdate(taxonNode);
644
			            	taxonService.saveOrUpdate(taxon);
645
			            	DeleteResult resultTaxon = taxonService.deleteTaxon(taxon.getUuid(), configNew, classification.getUuid());
646

    
647
                            if (!resultTaxon.isOk()){
648
                                result.addExceptions(resultTaxon.getExceptions());
649
                                result.setStatus(resultTaxon.getStatus());
650
                            }
651
		            	}
652
	            	}
653

    
654
	            }
655

    
656
	            result.addUpdatedObject(parent);
657
	            if(result.getCdmEntity() == null){
658
	                result.setCdmEntity(taxonNode);
659
                }
660
	            UUID uuid = dao.delete(taxonNode);
661
	            logger.debug("Deleted node " +uuid.toString());
662

    
663
	        }
664
        }
665
        /*if (classification != null){
666
            result.addUpdatedObject(classification);
667
        	DeleteResult resultClassification = classService.delete(classification);
668
        	 if (!resultClassification.isOk()){
669
                 result.addExceptions(resultClassification.getExceptions());
670
                 result.setStatus(resultClassification.getStatus());
671
             }
672
        }*/
673
        return result;
674
    }
675

    
676

    
677
    @Override
678
    @Transactional(readOnly = false)
679
    public DeleteResult deleteTaxonNodes(Collection<UUID> nodeUuids, TaxonDeletionConfigurator config) {
680
        List<TaxonNode> nodes = new ArrayList<>();
681
        for(UUID nodeUuid : nodeUuids) {
682
            nodes.add(dao.load(nodeUuid));
683
        }
684
        return deleteTaxonNodes(nodes, config);
685
    }
686

    
687

    
688
    @Override
689
    @Transactional(readOnly = false)
690
    public DeleteResult deleteTaxonNode(UUID nodeUUID, TaxonDeletionConfigurator config) {
691

    
692
    	TaxonNode node = CdmBase.deproxy(dao.load(nodeUUID));
693
    	return deleteTaxonNode(node, config);
694
    }
695

    
696
    @Override
697
    @Transactional(readOnly = false)
698
    public DeleteResult deleteTaxonNode(TaxonNode node, TaxonDeletionConfigurator config) {
699
        DeleteResult result = new DeleteResult();
700
        if (node == null){
701
            result.setAbort();
702
            result.addException(new Exception("The TaxonNode was already deleted."));
703
            return result;
704
        }
705
        Taxon taxon = null;
706
        try{
707
            taxon = HibernateProxyHelper.deproxy(node.getTaxon());
708
        }catch(NullPointerException e){
709
            result.setAbort();
710
            result.addException(new Exception("The Taxon was already deleted."));
711

    
712
        }
713

    
714

    
715
    	TaxonNode parent = HibernateProxyHelper.deproxy(node.getParent(), TaxonNode.class);
716
    	if (config == null){
717
    		config = new TaxonDeletionConfigurator();
718
    	}
719

    
720

    
721
    	if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.MOVE_TO_PARENT)){
722
    	   Object[] children = node.getChildNodes().toArray();
723
    	   TaxonNode childNode;
724
    	   for (Object child: children){
725
    	       childNode = (TaxonNode) child;
726
    	       parent.addChildNode(childNode, childNode.getReference(), childNode.getMicroReference());
727

    
728
    	   }
729
    	}else{
730
    	    result.includeResult(deleteTaxonNodes(node.getChildNodes(), config));
731
    	}
732

    
733
    	//remove node from DescriptiveDataSet
734
        commonService.getReferencingObjects(node).stream()
735
        .filter(obj->obj instanceof DescriptiveDataSet)
736
        .forEach(dataSet->{
737
            ((DescriptiveDataSet)dataSet).removeTaxonSubtree(node);
738
            dataSetService.saveOrUpdate((DescriptiveDataSet) dataSet);
739
        });
740

    
741
    	if (taxon != null){
742
        	if (config.getTaxonNodeConfig().isDeleteTaxon() && (config.isDeleteInAllClassifications() || taxon.getTaxonNodes().size() == 1)){
743
        		result = taxonService.deleteTaxon(taxon.getUuid(), config, node.getClassification().getUuid());
744
        		result.addUpdatedObject(parent);
745
        		if (result.isOk()){
746
        			return result;
747
        		}
748
        	} else {
749
        	    result.addUpdatedObject(taxon);
750
        	}
751
    	}
752
    	result.setCdmEntity(node);
753
    	boolean success = true;
754
    	if (taxon != null){
755
    	    success = taxon.removeTaxonNode(node);
756
    	    taxonService.saveOrUpdate(taxon);
757
    	}
758
    	dao.saveOrUpdate(parent);
759

    
760
    	result.addUpdatedObject(parent);
761

    
762
    	if (success){
763
			result.setStatus(Status.OK);
764
			if (parent != null){
765
    			parent = HibernateProxyHelper.deproxy(parent, TaxonNode.class);
766
    			int index = parent.getChildNodes().indexOf(node);
767
    			if (index > -1){
768
    			    parent.removeChild(index);
769
    			}
770
			}
771
    		if (!dao.delete(node, config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)).equals(null)){
772
    		    result.getUpdatedObjects().remove(node);
773
    			result.addDeletedObject(node);
774
    			return result;
775
    		} else {
776
    			result.setError();
777
    			return result;
778
    		}
779
    	}else{
780
    	    if (dao.findByUuid(node.getUuid()) != null){
781
        		result.setError();
782
        		result.addException(new Exception("The node can not be removed from the taxon."));
783
    		}
784
    		return result;
785
    	}
786
    }
787

    
788

    
789
    @Override
790
    public List<TaxonNode> listAllNodesForClassification(Classification classification, Integer start, Integer end) {
791
        return dao.getTaxonOfAcceptedTaxaByClassification(classification, start, end);
792
    }
793

    
794
    @Override
795
    public int countAllNodesForClassification(Classification classification) {
796
        return dao.countTaxonOfAcceptedTaxaByClassification(classification);
797
    }
798

    
799
    @Override
800
    @Transactional
801
    public UpdateResult moveTaxonNode(UUID taxonNodeUuid, UUID targetNodeUuid, int movingType, SecReferenceHandlingEnum secHandling, UUID secUuid){
802
        TaxonNode taxonNode = HibernateProxyHelper.deproxy(dao.load(taxonNodeUuid));
803
    	TaxonNode targetNode = HibernateProxyHelper.deproxy(dao.load(targetNodeUuid));
804
    	Reference sec = null;
805
    	if (secUuid != null){
806
    	    sec = HibernateProxyHelper.deproxy(referenceDao.load(secUuid));
807
    	}
808
    	UpdateResult result = moveTaxonNode(taxonNode, targetNode, movingType, secHandling, sec);
809
    	return result;
810
    }
811

    
812
    @Override
813
    @Transactional
814
    public UpdateResult moveTaxonNode(TaxonNode taxonNode, TaxonNode newParent, int movingType, SecReferenceHandlingEnum secHandling, Reference sec){
815
        UpdateResult result = new UpdateResult();
816

    
817
        TaxonNode parentParent = HibernateProxyHelper.deproxy(newParent.getParent());
818
        Integer sortIndex = -1;
819
        if (movingType == 0){
820
            sortIndex = 0;
821
        }else if (movingType == 1){
822
            sortIndex = newParent.getSortIndex();
823
            newParent = parentParent;
824
        } else if (movingType == 2){
825
            sortIndex = newParent.getSortIndex() +1;
826
            newParent = parentParent;
827
        } else{
828
            result.setAbort();
829
            result.addException(new Exception("The moving type "+ movingType +" is not supported."));
830
        }
831

    
832
        if (secHandling.equals(SecReferenceHandlingEnum.AlwaysSelect) || (secHandling.equals(SecReferenceHandlingEnum.KeepOrSelect) && sec != null)){
833
            if (taxonNode.getTaxon() != null){
834
                taxonNode.getTaxon().setSec(sec);
835
            }
836
        }else if (secHandling.equals(SecReferenceHandlingEnum.AlwaysDelete)){
837
            if (taxonNode.getTaxon() != null){
838
                taxonNode.getTaxon().setSec(null);
839
            }
840
        }else if (secHandling.equals(SecReferenceHandlingEnum.UseNewParentSec)){
841
            if (taxonNode.getTaxon() != null && newParent.getTaxon()!= null){
842
                taxonNode.getTaxon().setSec(newParent.getTaxon().getSec());
843
            }
844
        }
845

    
846
        taxonNode = newParent.addChildNode(taxonNode, sortIndex, taxonNode.getReference(),  taxonNode.getMicroReference());
847
        result.addUpdatedObject(taxonNode);
848

    
849
        return result;
850
    }
851

    
852
    @Override
853
    @Transactional
854
    public UpdateResult moveTaxonNodes(Set<UUID> taxonNodeUuids, UUID newParentNodeUuid, int movingType, SecReferenceHandlingEnum secHandling, UUID secUuid, IProgressMonitor monitor){
855

    
856
        if (monitor == null){
857
            monitor = DefaultProgressMonitor.NewInstance();
858
        }
859
        UpdateResult result = new UpdateResult();
860
        List<String> taxonNodePropertyPath = new ArrayList<>();
861
        taxonNodePropertyPath.add("taxon.secSource.*");
862
        taxonNodePropertyPath.add("parent.taxon.secSource.*");
863
        TaxonNode targetNode = dao.load(newParentNodeUuid, taxonNodePropertyPath);
864
        List<TaxonNode> nodes = dao.list(taxonNodeUuids, null, null, null, null);
865
        Reference sec = referenceDao.load(secUuid);
866

    
867
        monitor.beginTask("Move Taxonnodes", nodes.size()*2);
868
        monitor.subTask("move taxon nodes");
869
        for (TaxonNode node: nodes){
870
            if (!monitor.isCanceled()){
871
                if (!nodes.contains(node.getParent())){
872
                    result.includeResult(moveTaxonNode(node, targetNode, movingType, secHandling, sec));
873
                }
874
                monitor.worked(1);
875
            }else{
876
                monitor.done();
877
                result.setAbort();
878
                break;
879
            }
880
        }
881
        if (!monitor.isCanceled()){
882
            monitor.subTask("saving and reindex");
883
            dao.saveOrUpdateAll(nodes);
884
        }else{
885
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
886
        }
887

    
888
        monitor.done();
889
        return result;
890
    }
891

    
892
    @Override
893
    public Pager<TaxonNodeAgentRelation> pageTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid,
894
            UUID agentUuid, UUID rankUuid, UUID relTypeUuid, Integer pageSize, Integer pageIndex, List<String> propertyPaths) {
895

    
896
        List<TaxonNodeAgentRelation> records = null;
897

    
898
        long count = dao.countTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid);
899
        if(PagerUtils.hasResultsInRange(count, pageIndex, pageSize)) {
900
            records = dao.listTaxonNodeAgentRelations(taxonUuid, classificationUuid,
901
                    agentUuid, rankUuid, relTypeUuid, PagerUtils.startFor(pageSize, pageIndex), PagerUtils.limitFor(pageSize), propertyPaths);
902
        }
903

    
904
        Pager<TaxonNodeAgentRelation> pager = new DefaultPagerImpl<>(pageIndex, count, pageSize, records);
905
        return pager;
906
    }
907

    
908
    @Override
909
    @Transactional
910
    public UpdateResult createNewTaxonNode(UUID parentNodeUuid, CreateTaxonDTO taxonDto,
911
            NamedSource source, String microref,
912
            TaxonNodeStatus status, Map<Language,LanguageString> statusNote){
913

    
914
        UpdateResult result = new UpdateResult();
915
        TaxonNode child = null;
916
        TaxonNode parent = null;
917
        try{
918
            TaxonName name = null;
919
            Taxon taxon = null;
920
            if (taxonDto.getTaxonUuid() != null){
921
                taxon = (Taxon) taxonService.load(taxonDto.getTaxonUuid());
922
                if (taxon == null){
923
                    throw new RuntimeException("Taxon for not found for id " + taxonDto.getTaxonUuid());
924
                }
925
            }else{
926
                if (taxonDto.getNameUuid() != null){
927
                    name = nameService.load(taxonDto.getNameUuid());
928
                    if (name == null){
929
                        throw new RuntimeException("Taxon name not found for id " + taxonDto.getTaxonUuid());
930
                    }
931
                } else {
932
                    UpdateResult tmpResult = nameService.parseName(taxonDto.getTaxonNameString(),
933
                            taxonDto.getCode(), taxonDto.getPreferredRank(),  true);
934
                    result.addUpdatedObjects(tmpResult.getUpdatedObjects());
935
                    name = (TaxonName)tmpResult.getCdmEntity();
936
                }
937
                Reference sec = null;
938
                if (taxonDto.getSecUuid() != null ){
939
                    sec = referenceService.load(taxonDto.getSecUuid());
940
                }
941
                if (name != null && !name.isPersited()){
942
                    for (HybridRelationship rel : name.getHybridChildRelations()){
943
                        if (!rel.getHybridName().isPersited()) {
944
                            nameService.save(rel.getHybridName());
945
                        }
946
                        if (!rel.getParentName().isPersited()) {
947
                            nameService.save(rel.getParentName());
948
                        }
949
                    }
950
                }
951
                taxon = Taxon.NewInstance(name, sec);
952
                taxon.setPublish(taxonDto.isPublish());
953
            }
954

    
955
            parent = dao.load(parentNodeUuid);
956
            if (source != null){
957
                if (source.isPersited()){
958
                    source = (NamedSource) sourceDao.load(source.getUuid());
959
                }
960
                if (source.getCitation() != null){
961
                    source.setCitation(referenceService.load(source.getCitation().getUuid()));
962
                }
963
                if (source.getNameUsedInSource() !=null){
964
                    source.setNameUsedInSource(nameService.load(source.getNameUsedInSource().getUuid()));
965
                }
966
            }
967

    
968
            child = parent.addChildTaxon(taxon, source);
969
            child.setStatus(status);
970

    
971
            if (statusNote != null){
972
                child.getStatusNote().putAll(statusNote);
973
            }
974

    
975
        }catch(Exception e){
976
            result.addException(e);
977
            result.setError();
978
            return result;
979
        }
980
        child = dao.save(child);
981

    
982
        result.addUpdatedObject(parent);
983
        if (child != null){
984
            result.setCdmEntity(child);
985
        }
986
        return result;
987
    }
988

    
989
    @Override
990
    @Transactional
991
    public UpdateResult addTaxonNodeAgentRelation(UUID taxonNodeUUID, UUID agentUUID, DefinedTerm relationshipType){
992
        UpdateResult result = new UpdateResult();
993
        TaxonNode node = dao.load(taxonNodeUUID);
994
        TeamOrPersonBase<?> agent = (TeamOrPersonBase<?>) agentService.load(agentUUID);
995
        node.addAgentRelation(relationshipType, agent);
996
        try{
997
            dao.merge(node, true);
998
        }catch (Exception e){
999
            result.setError();
1000
            result.addException(e);
1001
        }
1002
        result.setCdmEntity(node);
1003
        return result;
1004
    }
1005

    
1006
    @Override
1007
    @Transactional(readOnly=false)
1008
    public UpdateResult setSecundumForSubtree(SecundumForSubtreeConfigurator config) {
1009
        UpdateResult result = new UpdateResult();
1010
        IProgressMonitor monitor = config.getMonitor();
1011

    
1012
        if (monitor == null){
1013
            monitor = DefaultProgressMonitor.NewInstance();
1014
        }
1015
        monitor.beginTask("Update secundum reference for subtree", 100);
1016
        monitor.subTask("Check start conditions");
1017

    
1018
        if (config.getSubtreeUuid() == null){
1019
            result.setError();
1020
            result.addException(new NullPointerException("No subtree given"));
1021
            monitor.done();
1022
            return result;
1023
        }
1024
        monitor.worked(1);
1025
        TaxonNode subTree = load(config.getSubtreeUuid());
1026
        if (subTree == null){
1027
            result.setError();
1028
            result.addException(new NullPointerException("Subtree does not exist"));
1029
            monitor.done();
1030
            return result;
1031
        }
1032
        monitor.worked(1);
1033

    
1034
        Reference newSec = null;
1035
        if (config.getNewSecundum() != null){
1036
            newSec = referenceService.load(config.getNewSecundum().getUuid());
1037
            if (newSec == null){
1038
                result.setError();
1039
                result.addException(new NullPointerException("New secundum reference does not exist"));
1040
                monitor.done();
1041
                return result;
1042
            }
1043
        }
1044
        monitor.worked(1);
1045

    
1046
        monitor.subTask("Count records");
1047
        try {
1048
            boolean includeRelatedTaxa = config.isIncludeProParteSynonyms() || config.isIncludeMisapplications();
1049

    
1050
            TreeIndex subTreeIndex = TreeIndex.NewInstance(subTree.treeIndex());
1051
            int count = config.isIncludeAcceptedTaxa() ? dao.countSecundumForSubtreeAcceptedTaxa(subTreeIndex, newSec, config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail()):0;
1052
            monitor.worked(2);
1053
            count += config.isIncludeSynonyms() ? dao.countSecundumForSubtreeSynonyms(subTreeIndex, newSec, config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail()) :0;
1054
            monitor.worked(3);
1055
            count += includeRelatedTaxa ? dao.countSecundumForSubtreeRelations(subTreeIndex, newSec, config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail()):0;
1056
            monitor.worked(2);
1057
            if (monitor.isCanceled()){
1058
                return result;
1059
            }
1060

    
1061
            SubProgressMonitor subMonitor = SubProgressMonitor.NewStarted(monitor, 90, "Updating secundum for subtree", count * 2);  //*2 1 tick for update and 1 tick for commit
1062
            //Reference ref = config.getNewSecundum();
1063
            if (config.isIncludeAcceptedTaxa()){
1064
                monitor.subTask("Update Accepted Taxa");
1065
                Set<CdmBase> updatedTaxa = dao.setSecundumForSubtreeAcceptedTaxa(subTreeIndex, newSec,
1066
                        config.isOverwriteExisting(), config.isIncludeSharedTaxa(), config.isEmptySecundumDetail(), subMonitor);
1067
                result.addUpdatedObjects(updatedTaxa);
1068
                if (monitor.isCanceled()){
1069
                    return result;
1070
                }
1071
            }
1072
            if (config.isIncludeSynonyms()){
1073
               monitor.subTask("Update Synonyms");
1074
               Set<CdmBase> updatedSynonyms = dao.setSecundumForSubtreeSynonyms(subTreeIndex, newSec,
1075
                       config.isOverwriteExisting(), config.isIncludeSharedTaxa() , config.isEmptySecundumDetail(), subMonitor);
1076
               result.addUpdatedObjects(updatedSynonyms);
1077
               if (monitor.isCanceled()){
1078
                   return result;
1079
               }
1080
            }
1081
            if (includeRelatedTaxa){
1082
                monitor.subTask("Update Related Taxa");
1083
                Set<UUID> relationTypes = getRelationTypesForSubtree(config);
1084
                Set<CdmBase> updatedRels = dao.setSecundumForSubtreeRelations(subTreeIndex, newSec,
1085
                        relationTypes, config.isOverwriteExisting(), config.isIncludeSharedTaxa() , config.isEmptySecundumDetail(), subMonitor);
1086
                result.addUpdatedObjects(updatedRels);
1087
                if (monitor.isCanceled()){
1088
                    return result;
1089
                }
1090
            }
1091
        } catch (Exception e) {
1092
            result.setError();
1093
            result.addException(e);
1094
        }
1095
        monitor.done();
1096
        return result;
1097
    }
1098

    
1099
    @Override
1100
    @Transactional(readOnly=false)
1101
    public UpdateResult setPublishForSubtree(PublishForSubtreeConfigurator config){
1102
        UpdateResult result = new UpdateResult();
1103
        IProgressMonitor monitor = config.getMonitor();
1104
        if (monitor == null){
1105
            monitor = DefaultProgressMonitor.NewInstance();
1106
        }
1107
        monitor.beginTask("Update publish flag for subtree", 100);
1108
        monitor.subTask("Check start conditions");
1109

    
1110
        if (config.getSubtreeUuid() == null){
1111
            result.setError();
1112
            result.addException(new NullPointerException("No subtree given"));
1113
            monitor.done();
1114
            return result;
1115
        }
1116
        monitor.worked(1);
1117

    
1118
        TaxonNode subTree = find(config.getSubtreeUuid());
1119
        if (subTree == null){
1120
            result.setError();
1121
            result.addException(new NullPointerException("Subtree does not exist"));
1122
            monitor.done();
1123
            return result;
1124
        }
1125
        monitor.worked(1);
1126

    
1127
        monitor.subTask("Count records");
1128
        boolean includeAcceptedTaxa = config.isIncludeAcceptedTaxa();
1129
        boolean publish = config.isPublish();
1130
        boolean includeSynonyms = config.isIncludeSynonyms();
1131
        boolean includeSharedTaxa = config.isIncludeSharedTaxa();
1132
        boolean includeHybrids = config.isIncludeHybrids();
1133
        boolean includeRelatedTaxa = config.isIncludeProParteSynonyms() || config.isIncludeMisapplications();
1134
        try {
1135
            TreeIndex subTreeIndex = TreeIndex.NewInstance(subTree.treeIndex());
1136
            int count = includeAcceptedTaxa ? dao.countPublishForSubtreeAcceptedTaxa(subTreeIndex, publish, includeSharedTaxa, includeHybrids):0;
1137
            monitor.worked(3);
1138
            count += includeSynonyms ? dao.countPublishForSubtreeSynonyms(subTreeIndex, publish, includeSharedTaxa, includeHybrids):0;
1139
            monitor.worked(3);
1140
            count += includeRelatedTaxa ? dao.countPublishForSubtreeRelatedTaxa(subTreeIndex, publish, includeSharedTaxa, includeHybrids):0;
1141
            monitor.worked(2);
1142
            if (monitor.isCanceled()){
1143
                return result;
1144
            }
1145

    
1146
            SubProgressMonitor subMonitor = SubProgressMonitor.NewStarted(monitor, 90, "Updating secundum for subtree", count);
1147
            if (includeAcceptedTaxa){
1148
                monitor.subTask("Update Accepted Taxa");
1149
                @SuppressWarnings("rawtypes")
1150
                Set<TaxonBase> updatedTaxa = dao.setPublishForSubtreeAcceptedTaxa(subTreeIndex,
1151
                        publish, includeSharedTaxa, includeHybrids, subMonitor);
1152
                result.addUpdatedObjects(updatedTaxa);
1153
                if (monitor.isCanceled()){
1154
                    return result;
1155
                }
1156
            }
1157
            if (includeSynonyms){
1158
                monitor.subTask("Update Synonyms");
1159
                @SuppressWarnings("rawtypes")
1160
                Set<TaxonBase> updatedSynonyms = dao.setPublishForSubtreeSynonyms(subTreeIndex,
1161
                        publish, includeSharedTaxa, includeHybrids, subMonitor);
1162
                result.addUpdatedObjects(updatedSynonyms);
1163
                if (monitor.isCanceled()){
1164
                    return result;
1165
                }
1166
            }
1167
            if (includeRelatedTaxa){
1168
                monitor.subTask("Update Related Taxa");
1169
                Set<UUID> relationTypes = getRelationTypesForSubtree(config);
1170
                if (config.isIncludeMisapplications()){
1171
                    relationTypes.addAll(TaxonRelationshipType.misappliedNameUuids());
1172
                }
1173
                if (config.isIncludeProParteSynonyms()){
1174
                    relationTypes.addAll(TaxonRelationshipType.proParteOrPartialSynonymUuids());
1175
                }
1176
                @SuppressWarnings("rawtypes")
1177
                Set<TaxonBase> updatedTaxa = dao.setPublishForSubtreeRelatedTaxa(subTreeIndex, publish,
1178
                        relationTypes, includeSharedTaxa, includeHybrids, subMonitor);
1179
                result.addUpdatedObjects(updatedTaxa);
1180
                if (monitor.isCanceled()){
1181
                    return result;
1182
                }
1183
            }
1184
        } catch (Exception e) {
1185
            result.setError();
1186
            result.addException(e);
1187
        }
1188

    
1189
        monitor.done();
1190
        return result;
1191
    }
1192

    
1193
    private Set<UUID> getRelationTypesForSubtree(ForSubtreeConfiguratorBase config) {
1194
        Set<UUID> relationTypes = new HashSet<>();
1195
        if (config.isIncludeMisapplications()){
1196
            relationTypes.addAll(TaxonRelationshipType.misappliedNameUuids());
1197
        }
1198
        if (config.isIncludeProParteSynonyms()){
1199
            relationTypes.addAll(TaxonRelationshipType.proParteOrPartialSynonymUuids());
1200
        }
1201
        return relationTypes;
1202
    }
1203

    
1204
    @Override
1205
    public long count(TaxonNodeFilter filter){
1206
        return nodeFilterDao.count(filter);
1207
    }
1208

    
1209
    @Override
1210
    public List<UUID> uuidList(TaxonNodeFilter filter){
1211
        return nodeFilterDao.listUuids(filter);
1212
    }
1213

    
1214
    @Override
1215
    public List<Integer> idList(TaxonNodeFilter filter){
1216
        return nodeFilterDao.idList(filter);
1217
    }
1218

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

    
1263
    @Override
1264
    public List<TaxonDistributionDTO> getTaxonDistributionDTO(List<UUID> nodeUuids, List<String> propertyPaths,
1265
            Authentication authentication, boolean openChildren, TaxonNodeSortMode sortMode){
1266

    
1267
        nodeUuids = nodeUuids.stream().distinct().collect(Collectors.toList());
1268
        List<TaxonNode> nodes = new ArrayList<>();
1269

    
1270
        List<TaxonNode> parentNodes = load(nodeUuids, propertyPaths);
1271
        if (sortMode != null){
1272
            parentNodes.sort(sortMode.comparator());
1273
        }
1274
        if (openChildren){
1275
            //TODO we could remove nodes which are children of other nodes in parentNodes list here as they are duplicates
1276
            for (TaxonNode node: parentNodes){
1277
                if (node == null || nodes.contains(node)){
1278
                    continue;
1279
                }
1280
                nodes.add(node);
1281
                List<TaxonNode> children = new ArrayList<>();
1282
                children.addAll(loadChildNodesOfTaxonNode(node,
1283
                        propertyPaths, true,  true, sortMode));
1284
                for (TaxonNode child: children){
1285
                    if (!nodes.contains(child)){
1286
                        nodes.add(child);
1287
                    }
1288
                }
1289
            }
1290
        }else{
1291
            nodes.addAll(nodes);
1292
        }
1293

    
1294
        List<TaxonDistributionDTO> result = new ArrayList<>();
1295
        boolean hasPermission = false;
1296
        for(TaxonNode node: nodes){
1297
            if (authentication != null ) {
1298
                hasPermission = permissionEvaluator.hasPermission(authentication, node, Operation.UPDATE);
1299
            }else {
1300
                hasPermission = true;
1301
            }
1302
            if (node.getTaxon() != null && hasPermission){
1303
                try{
1304
                    TaxonDistributionDTO dto = new TaxonDistributionDTO(node);
1305
                    result.add(dto);
1306
                }catch(Exception e){
1307
                    logger.error(e.getMessage(), e);
1308
                }
1309
            }
1310
        }
1311
        return result;
1312
    }
1313

    
1314
    @Override
1315
    public <S extends TaxonNode> Pager<S> page(Class<S> clazz, List<Restriction<?>> restrictions, Integer pageSize,
1316
            Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths) {
1317
        return page(clazz, restrictions, pageSize, pageIndex, orderHints, propertyPaths, INCLUDE_UNPUBLISHED);
1318
    }
1319

    
1320
    @Override
1321
    public <S extends TaxonNode> Pager<S> page(Class<S> clazz, List<Restriction<?>> restrictions, Integer pageSize,
1322
            Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths, boolean includeUnpublished) {
1323

    
1324
        List<S> records;
1325
        long resultSize = dao.count(clazz, restrictions);
1326
        if(AbstractPagerImpl.hasResultsInRange(resultSize, pageIndex, pageSize)){
1327
            records = dao.list(clazz, restrictions, pageSize, pageIndex, orderHints, propertyPaths, includeUnpublished);
1328
        } else {
1329
            records = new ArrayList<>();
1330
        }
1331
        Pager<S> pager = new DefaultPagerImpl<>(pageIndex, resultSize, pageSize, records);
1332
        return pager;
1333
    }
1334

    
1335
    @Override
1336
    public List<TaxonDistributionDTO> getTaxonDistributionDTO(List<UUID> nodeUuids,
1337
            List<String> propertyPaths, boolean openChildren) {
1338
        return getTaxonDistributionDTO(nodeUuids, propertyPaths, null, openChildren, null);
1339
    }
1340

    
1341
    @Override
1342
    @Transactional(readOnly = false)
1343
    public UpdateResult cloneSubtree(SubtreeCloneConfigurator config) {
1344
        UpdateResult result = new UpdateResult();
1345

    
1346
        if (config.getSubTreeUuids().isEmpty()){
1347
            return result;
1348
        }
1349

    
1350
        //TODO error handling
1351
        Reference taxonSecundum = config.isReuseTaxa() || config.isReuseTaxonSecundum() || config.getTaxonSecundumUuid() == null ?
1352
                null : referenceDao.findByUuid(config.getTaxonSecundumUuid());
1353
        config.setTaxonSecundum(taxonSecundum);
1354

    
1355
        Reference parentChildReference = config.isReuseParentChildReference() || config.getParentChildReferenceUuid() == null ?
1356
                null : referenceDao.findByUuid(config.getParentChildReferenceUuid());
1357
        config.setParentChildReference(parentChildReference);
1358

    
1359
        Reference taxonRelationshipReference = config.getRelationTypeToOldTaxon() == null ?
1360
                null : referenceDao.findByUuid(config.getRelationshipReferenceUuid());
1361
        config.setRelationshipReference(taxonRelationshipReference);
1362

    
1363
        Classification classificationClone = Classification.NewInstance(config.getNewClassificationName());
1364

    
1365
        if (config.isReuseClassificationReference()){
1366
            TaxonNode anyNode = dao.findByUuid(config.getSubTreeUuids().iterator().next());
1367
            if (anyNode != null){
1368
                Reference oldClassificationRef = anyNode.getClassification().getReference();
1369
                classificationClone.setReference(oldClassificationRef);
1370
            }
1371
        }else if (config.getClassificationReferenceUuid() != null) {
1372
            Reference classificationReference = referenceDao.findByUuid(config.getClassificationReferenceUuid());
1373
            classificationClone.setReference(classificationReference);
1374
        }
1375

    
1376
        //clone taxa and taxon nodes
1377
//      List<Integer> childNodeIds = taxonNodeService.idList(taxonNodeFilter);
1378
//      List<TaxonNode> childNodes = taxonNodeService.loadByIds(childNodeIds, null);
1379
        List<TaxonNode> rootNodes = this.find(config.getSubTreeUuids());
1380
        for (TaxonNode taxonNode : rootNodes) {
1381
            cloneTaxonRecursive(taxonNode, classificationClone.getRootNode(), config);
1382
        }
1383
        classificationDao.saveOrUpdate(classificationClone);
1384
        result.setCdmEntity(classificationClone);
1385
        return result;
1386
    }
1387

    
1388
    private void cloneTaxonRecursive(TaxonNode originalParentNode, TaxonNode parentNodeClone,
1389
            SubtreeCloneConfigurator config){
1390

    
1391
        Taxon originalTaxon = CdmBase.deproxy(originalParentNode.getTaxon());
1392
        TaxonNode childNodeClone;
1393
        if (originalTaxon != null){
1394
            String microReference = null;
1395
            if (config.isReuseTaxa()){
1396
                childNodeClone = parentNodeClone.addChildTaxon(originalTaxon, config.getParentChildReference(), microReference);
1397
            }else{
1398
                Taxon cloneTaxon = originalTaxon.clone(config.isIncludeSynonymsIncludingManAndProParte(),
1399
                        config.isIncludeTaxonRelationshipsExcludingManAndProParte(),
1400
                        config.isIncludeDescriptiveData(), config.isIncludeMedia());
1401

    
1402
                //name
1403
                if (!config.isReuseNames()){
1404
                    cloneTaxon.setName(cloneTaxon.getName().clone());
1405
                    //TODO needs further handling for name relationships etc., see #9349
1406
                    cloneTaxon.getSynonyms().forEach(syn ->
1407
                        syn.setName(syn.getName() == null ? null : syn.getName().clone()));
1408
                }
1409

    
1410
                if (!config.isReuseTaxonSecundum()){
1411
                    cloneTaxon.setSec(config.getTaxonSecundum());
1412
                }
1413

    
1414
                //add relation between taxa
1415
                if (config.getRelationTypeToOldTaxon() != null){
1416
                    TaxonRelationship rel = cloneTaxon.addTaxonRelation(originalParentNode.getTaxon(), config.getRelationTypeToOldTaxon(),
1417
                            config.getRelationshipReference(), microReference);
1418
                    rel.setDoubtful(config.isRelationDoubtful());
1419
                }
1420
                childNodeClone = parentNodeClone.addChildTaxon(cloneTaxon, config.getParentChildReference(), microReference);
1421
            }
1422

    
1423
            //probably necessary as taxon nodes do not cascade
1424
            dao.saveOrUpdate(childNodeClone);
1425

    
1426
        }else{
1427
            childNodeClone = parentNodeClone;
1428
        }
1429
        //add children
1430
        if (config.isDoRecursive()){
1431
            List<TaxonNode> originalChildNodes = originalParentNode.getChildNodes();
1432
            HHH_9751_Util.removeAllNull(originalChildNodes);
1433

    
1434
            for (TaxonNode originalChildNode : originalChildNodes) {
1435
                cloneTaxonRecursive(originalChildNode, childNodeClone, config);
1436
            }
1437
        }
1438
    }
1439

    
1440
    @Override
1441
    public HomotypicGroupDto getHomotypicGroupDto(UUID homotypicGroupUuid, UUID nodeUuid) {
1442

    
1443
        HomotypicalGroup group = homotypicalGroupDao.load(homotypicGroupUuid);
1444
        if (group == null){
1445
            return null;
1446
        }
1447
        return new HomotypicGroupDto(group, nodeUuid);
1448
    }
1449

    
1450
    @Override
1451
    public TaxonNodeDto getTaxonNodeDto(UUID nodeUuid) {
1452
        String queryString = "SELECT new " + SortableTaxonNodeQueryResult.class.getName() + "("
1453
                + "tn.uuid, tn.id, t.titleCache, rank "
1454
                + ") "
1455
                + " FROM TaxonNode tn "
1456
                + "   INNER JOIN tn.taxon AS t "
1457
                + "   INNER JOIN t.name AS name "
1458
                + "   LEFT OUTER JOIN name.rank AS rank "
1459
                + " WHERE t.uuid LIKE :uuid ";
1460

    
1461

    
1462
        Query query =  getSession().createQuery(queryString);
1463

    
1464
        query.setParameter("uuid", nodeUuid.toString());
1465

    
1466

    
1467
        @SuppressWarnings("unchecked")
1468
        List<SortableTaxonNodeQueryResult> result = query.list();
1469
        Collections.sort(result, new SortableTaxonNodeQueryResultComparator());
1470

    
1471
        List<TaxonNodeDto> list = new ArrayList<>();
1472
        for(SortableTaxonNodeQueryResult queryDTO : result){
1473
            list.add(new TaxonNodeDto(queryDTO.getTaxonNodeUuid(), queryDTO.getTaxonNodeId(), queryDTO.getTaxonTitleCache()));
1474
        }
1475
        return list.get(0);
1476
    }
1477

    
1478
    @Override
1479
    public List<TaxonNodeDto> getTaxonNodeDtos(List<UUID> nodeUuids) {
1480
        String queryString = "SELECT new " + SortableTaxonNodeQueryResult.class.getName() + "("
1481
                + "tn.uuid, tn.id, t.titleCache, name.titleCache, rank "
1482
                + ") "
1483
                + " FROM TaxonNode tn "
1484
                + "   INNER JOIN tn.taxon AS t "
1485
                + "   INNER JOIN t.name AS name "
1486
                + "   LEFT OUTER JOIN name.rank AS rank "
1487
                + " WHERE t.uuid IN (:uuid) ";
1488

    
1489

    
1490
        Query query =  getSession().createQuery(queryString);
1491
//        List<String> uuidStrings = nodeUuids.stream().map(e -> e.toString()).collect(Collectors.toList());
1492

    
1493
        query.setParameterList("uuid", nodeUuids);
1494

    
1495

    
1496
        @SuppressWarnings("unchecked")
1497
        List<SortableTaxonNodeQueryResult> result = query.list();
1498
        Collections.sort(result, new SortableTaxonNodeQueryResultComparator());
1499

    
1500
        List<TaxonNodeDto> list = new ArrayList<>();
1501
        for(SortableTaxonNodeQueryResult queryDTO : result){
1502
            TaxonNodeDto nodeDto = new TaxonNodeDto(queryDTO.getTaxonNodeUuid(), queryDTO.getTaxonNodeId(), queryDTO.getNameTitleCache(), queryDTO.getTaxonTitleCache(), queryDTO.getNameRank().getOrderIndex());
1503

    
1504
            list.add(nodeDto);
1505
        }
1506
        return list;
1507
    }
1508
}
(88-88/97)