Project

General

Profile

Download (53.5 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

    
10
package eu.etaxonomy.cdm.persistence.dao.hibernate.taxon;
11

    
12
import java.math.BigInteger;
13
import java.util.ArrayList;
14
import java.util.Collection;
15
import java.util.Collections;
16
import java.util.Comparator;
17
import java.util.HashMap;
18
import java.util.HashSet;
19
import java.util.Iterator;
20
import java.util.List;
21
import java.util.Map;
22
import java.util.Set;
23
import java.util.UUID;
24

    
25
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
26
import org.hibernate.Criteria;
27
import org.hibernate.Hibernate;
28
import org.hibernate.criterion.Projections;
29
import org.hibernate.criterion.Restrictions;
30
import org.hibernate.query.Query;
31
import org.springframework.beans.factory.annotation.Autowired;
32
import org.springframework.beans.factory.annotation.Qualifier;
33
import org.springframework.stereotype.Repository;
34

    
35
import eu.etaxonomy.cdm.common.CdmUtils;
36
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
37
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
38
import eu.etaxonomy.cdm.model.common.CdmBase;
39
import eu.etaxonomy.cdm.model.common.TreeIndex;
40
import eu.etaxonomy.cdm.model.name.Rank;
41
import eu.etaxonomy.cdm.model.name.TaxonName;
42
import eu.etaxonomy.cdm.model.reference.NamedSource;
43
import eu.etaxonomy.cdm.model.reference.Reference;
44
import eu.etaxonomy.cdm.model.taxon.Classification;
45
import eu.etaxonomy.cdm.model.taxon.SecundumSource;
46
import eu.etaxonomy.cdm.model.taxon.Synonym;
47
import eu.etaxonomy.cdm.model.taxon.Taxon;
48
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
49
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
50
import eu.etaxonomy.cdm.model.taxon.TaxonNodeAgentRelation;
51
import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
52
import eu.etaxonomy.cdm.persistence.dao.common.Restriction;
53
import eu.etaxonomy.cdm.persistence.dao.hibernate.common.AnnotatableDaoBaseImpl;
54
import eu.etaxonomy.cdm.persistence.dao.taxon.IClassificationDao;
55
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
56
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeDao;
57
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonRelationshipDao;
58
import eu.etaxonomy.cdm.persistence.dto.SortableTaxonNodeQueryResult;
59
import eu.etaxonomy.cdm.persistence.dto.SortableTaxonNodeQueryResultComparator;
60
import eu.etaxonomy.cdm.persistence.dto.TaxonNodeDto;
61
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
62
import eu.etaxonomy.cdm.persistence.query.OrderHint;
63

    
64
/**
65
 * @author a.mueller
66
 * @since 16.06.2009
67
 */
68
@Repository
69
@Qualifier("taxonNodeDaoHibernateImpl")
70
public class TaxonNodeDaoHibernateImpl extends AnnotatableDaoBaseImpl<TaxonNode>
71
		implements ITaxonNodeDao {
72

    
73
	private static final Logger logger = LogManager.getLogger(TaxonNodeDaoHibernateImpl.class);
74

    
75
    private static final int DEFAULT_SET_SUBTREE_PARTITION_SIZE = 100;
76

    
77
	@Autowired
78
	private ITaxonDao taxonDao;
79
	@Autowired
80
	private IClassificationDao classificationDao;
81
    @Autowired
82
    private ITaxonRelationshipDao taxonRelDao;
83

    
84
	public TaxonNodeDaoHibernateImpl() {
85
		super(TaxonNode.class);
86
	}
87

    
88
	@Override
89
	public UUID delete(TaxonNode persistentObject, boolean deleteChildren){
90
		Taxon taxon = persistentObject.getTaxon();
91
		taxon = HibernateProxyHelper.deproxy(taxon);
92

    
93
		/*Session session = this.getSession();
94
		Query<TaxonNode> query = session.createQuery("from TaxonNode t where t.taxon = :taxon", TaxonNode.class);
95
		query.setParameter("taxon", taxon);
96
		List result = query.list();*/
97
		if (taxon != null){
98
		    Hibernate.initialize(taxon);
99
		    Hibernate.initialize(taxon.getTaxonNodes());
100
			Set<TaxonNode> nodes = taxon.getTaxonNodes();
101
			//Hibernate.initialize(taxon.getTaxonNodes());
102
			for (TaxonNode node:nodes) {
103
			    node = HibernateProxyHelper.deproxy(node);
104

    
105
			    if (node.equals(persistentObject)){
106
			        if (node.hasChildNodes()){
107
			            Iterator<TaxonNode> childNodes = node.getChildNodes().iterator();
108
			            TaxonNode childNode;
109
			            List<TaxonNode> listForDeletion = new ArrayList<>();
110
	                    while (childNodes.hasNext()){
111
	                        childNode = childNodes.next();
112
	                        listForDeletion.add(childNode);
113
	                        childNodes.remove();
114

    
115
	                    }
116
	                    for (TaxonNode deleteNode:listForDeletion){
117
	                        delete(deleteNode, deleteChildren);
118
	                    }
119
	                }
120

    
121
			        taxon.removeTaxonNode(node, deleteChildren);
122
			        taxonDao.saveOrUpdate(taxon);
123
    				taxon = HibernateProxyHelper.deproxy(taxonDao.findByUuid(taxon.getUuid()), Taxon.class);
124
    				taxonDao.delete(taxon);
125

    
126
			    }
127
			}
128
		}
129

    
130
		UUID result = super.delete(persistentObject);
131
		return result;
132
	}
133

    
134
	@Override
135
	public List<TaxonNode> getTaxonOfAcceptedTaxaByClassification(Classification classification, Integer start, Integer end) {
136
		int classificationId = classification.getId();
137
		String limit = "";
138
		if(start !=null && end != null){
139
		    limit = "LIMIT "+start+"," +end;
140
		}
141
		//FIXME write test
142
        String queryString = "SELECT DISTINCT nodes.*,taxa.titleCache "
143
                + " FROM TaxonNode AS nodes "
144
                + "    LEFT JOIN TaxonBase AS taxa ON nodes.taxon_id = taxa.id "
145
                + " WHERE taxa.DTYPE = 'Taxon' "
146
                + "    AND nodes.classification_id = " + classificationId +
147
                  " ORDER BY taxa.titleCache " + limit;
148
        @SuppressWarnings("unchecked")
149
        List<TaxonNode> result  = getSession().createSQLQuery(queryString).addEntity(TaxonNode.class).list();
150

    
151
        return result;
152
	}
153

    
154
    @Override
155
    public int countTaxonOfAcceptedTaxaByClassification(Classification classification){
156
        int classificationId = classification.getId();
157
        //FIXME write test
158
        String queryString = ""
159
                + " SELECT DISTINCT COUNT('nodes.*') "
160
                + " FROM TaxonNode AS nodes "
161
                + "   LEFT JOIN TaxonBase AS taxa ON nodes.taxon_id = taxa.id "
162
                + " WHERE taxa.DTYPE = 'Taxon' AND nodes.classification_id = " + classificationId;
163
         @SuppressWarnings("unchecked")
164
         List<BigInteger> result = getSession().createSQLQuery(queryString).list();
165
         return result.get(0).intValue ();
166
    }
167

    
168
    @Override
169
    public List<TaxonNodeDto> listChildNodesAsUuidAndTitleCache(TaxonNodeDto parent) {
170
        String queryString =
171
                  " SELECT tn.uuid, tn.id, t.titleCache "
172
                + " FROM TaxonNode tn "
173
                + "    INNER JOIN tn.taxon AS t "
174
                + " WHERE tn.parent.uuid = :parentId";
175

    
176
        Query<Object[]> query =  getSession().createQuery(queryString, Object[].class);
177
        query.setParameter("parentId", parent.getUuid());
178

    
179
        List<Object[]> result = query.list();
180

    
181
        List<TaxonNodeDto> list = new ArrayList<>();
182
        for(Object[] object : result){
183
            list.add(new TaxonNodeDto((UUID) object[0],(Integer) object[1], (String) object[2]));
184
        }
185
        return list;
186
    }
187

    
188
    @Override
189
    public List<TaxonNodeDto> listChildNodesAsTaxonNodeDto(TaxonNodeDto parent) {
190
        String queryString =
191
                 " SELECT tn "
192
               + " FROM TaxonNode tn "
193
               + "    INNER JOIN tn.taxon AS t "
194
               + " WHERE tn.parent.uuid = :parentId";
195
        Query<TaxonNode> query =  getSession().createQuery(queryString, TaxonNode.class);
196
        query.setParameter("parentId", parent.getUuid());
197

    
198
        List<TaxonNode> result = query.list();
199

    
200
        List<TaxonNodeDto> list = new ArrayList<>();
201
        for(TaxonNode object : result){
202
            list.add(new TaxonNodeDto(object));
203
        }
204
        return list;
205
    }
206

    
207
    @Override
208
    public List<TaxonNodeDto> getUuidAndTitleCache(Integer limit, String pattern, UUID classificationUuid, boolean includeDoubtful) {
209

    
210
        Query<SortableTaxonNodeQueryResult> query = createQueryForUuidAndTitleCache(limit, classificationUuid, pattern, includeDoubtful);
211
        List<SortableTaxonNodeQueryResult> result = query.list();
212
        Collections.sort(result, new SortableTaxonNodeQueryResultComparator());
213
        if(logger.isTraceEnabled()){
214
            logger.trace("number of matches:" + result.size());
215
            result.stream().forEach(o -> logger.trace("uuid: " + o.getTaxonNodeUuid() + " titleCache:" + o.getTaxonTitleCache() + " rank: " + o.getNameRank()));
216
        }
217
        List<TaxonNodeDto> list = new ArrayList<>();
218
//        int index = limit;
219
        for(SortableTaxonNodeQueryResult stnqr : result){
220
//            if (index > 0){
221
                list.add(new TaxonNodeDto(stnqr.getTaxonNodeUuid(),stnqr.getTaxonNodeId(), stnqr.getTaxonTitleCache()));
222
//                index --;
223
//            }
224

    
225
        }
226

    
227
        return list;
228
    }
229

    
230
    private Query<SortableTaxonNodeQueryResult> createQueryForUuidAndTitleCache(Integer limit, UUID classificationUuid, String pattern, boolean includeDoubtful){
231
        String doubtfulPattern = "";
232
        String queryString = "SELECT new " + SortableTaxonNodeQueryResult.class.getName() + "("
233
                + " node.uuid, node.id, node.treeIndex, t.uuid, t.titleCache, rank, parent.uuid"
234
                + ") "
235
                + " FROM TaxonNode AS node "
236
                + "   JOIN node.taxon as t " // FIXME why not inner join here?
237
                + "   INNER JOIN t.name AS name "
238
                + "   LEFT OUTER JOIN node.parent as parent"
239
                + "   LEFT OUTER JOIN name.rank AS rank "
240
                + " WHERE ";
241

    
242
      if (classificationUuid != null){
243
          queryString = queryString + " node.classification.uuid like :classificationUuid " ;
244
      }
245
      if (pattern == null){
246
          pattern = "*";
247
      }
248

    
249
      if (pattern.equals("?")){
250
          limit = null;
251
      } else{
252
          if (!pattern.endsWith("*")){
253
              pattern += "%";
254
          }
255
          pattern = pattern.replace("*", "%");
256
          pattern = pattern.replace("?", "%");
257
          if (classificationUuid != null){
258
              queryString = queryString + " AND ";
259
          }
260
          queryString = queryString + " (t.titleCache LIKE (:pattern) " ;
261
          doubtfulPattern = "?" + pattern;
262
          if (includeDoubtful){
263
              queryString = queryString + " OR t.titleCache LIKE (:doubtfulPattern))";
264
          }else{
265
              queryString = queryString + ")";
266
          }
267
      }
268

    
269

    
270

    
271
      Query<SortableTaxonNodeQueryResult> query =  getSession().createQuery(queryString, SortableTaxonNodeQueryResult.class);
272
      if (pattern != null){
273
          query.setParameter("pattern", pattern);
274
      }
275
      if (includeDoubtful){
276
          query.setParameter("doubtfulPattern", doubtfulPattern);
277
      }
278

    
279
      if(classificationUuid != null){
280
          query.setParameter("classificationUuid", classificationUuid);
281
      }
282
      if (limit != null){
283
          query.setMaxResults(limit);
284
      }
285
      return query;
286
    }
287

    
288

    
289

    
290
    @Override
291
    public TaxonNodeDto getParentUuidAndTitleCache(TaxonNodeDto child) {
292
        String queryString = ""
293
                + " SELECT tn.parent.uuid, tn.parent.id, tn.parent.taxon.titleCache, "
294
                + "                  tn.parent.classification.titleCache "
295
                + " FROM TaxonNode tn"
296
                + "    LEFT OUTER JOIN tn.parent.taxon"
297
                + " WHERE tn.id = :childId";
298
        Query<Object[]> query =  getSession().createQuery(queryString, Object[].class);
299
        query.setParameter("childId", child.getId());
300
        List<TaxonNodeDto> list = new ArrayList<>();
301

    
302
        List<Object[]> result = query.list();
303

    
304
        for(Object[] object : result){
305
            UUID uuid = (UUID) object[0];
306
            Integer id = (Integer) object[1];
307
            String taxonTitleCache = (String) object[2];
308
            String classificationTitleCache = (String) object[3];
309
            if(taxonTitleCache!=null){
310
                list.add(new TaxonNodeDto(uuid,id, taxonTitleCache));
311
            }
312
            else{
313
                list.add(new TaxonNodeDto(uuid,id, classificationTitleCache));
314
            }
315
        }
316
        if(list.size()==1){
317
            return list.iterator().next();
318
        }
319
        return null;
320
    }
321
    @Override
322
    public List<TaxonNode> listChildrenOf(TaxonNode node, Integer pageSize, Integer pageIndex,
323
            boolean recursive, boolean includeUnpublished, List<String> propertyPaths, Comparator<TaxonNode> comparator){
324
        return listChildrenOfRecursive(node,new ArrayList<>(), pageSize, pageIndex, recursive, includeUnpublished, propertyPaths, comparator);
325
    }
326

    
327
    private List<TaxonNode> listChildrenOfRecursive(TaxonNode node, List<TaxonNode> previousResult, Integer pageSize, Integer pageIndex,
328
            boolean recursive, boolean includeUnpublished, List<String> propertyPaths, Comparator<TaxonNode> comparator){
329

    
330
        if (recursive == true && comparator == null ){
331
    		Criteria crit = childrenOfCriteria(node, includeUnpublished);
332

    
333
    		this.addPageSizeAndNumber(crit, pageSize, pageIndex);
334
    		@SuppressWarnings("unchecked")
335
            List<TaxonNode> results = crit.list();
336
    		results.remove(node);
337
    		defaultBeanInitializer.initializeAll(results, propertyPaths);
338
    		return results;
339

    
340
    	} else if (recursive == true){
341
    	    List<TaxonNode> children = node.getChildNodes();
342
    	    Collections.sort(children, comparator);
343
    	    for (TaxonNode child: children){
344
    	        if (!previousResult.contains(child)){
345
    	            previousResult.add(child);
346
    	        }
347
    	        if (child.hasChildNodes()){
348
    	            previousResult = listChildrenOfRecursive(child, previousResult, pageSize, pageIndex,
349
    	                    recursive, includeUnpublished, propertyPaths, comparator);
350
    	        }
351
    	    }
352
    	    return previousResult;
353

    
354
        } else{
355
    		return classificationDao.listChildrenOf(node.getTaxon(), node.getClassification(), null,
356
    		       includeUnpublished, pageSize, pageIndex, propertyPaths);
357
    	}
358
    }
359

    
360
    @Override
361
	public Long countChildrenOf(TaxonNode node, Classification classification,
362
			boolean recursive, boolean includeUnpublished) {
363

    
364
		if (recursive == true){
365
			Criteria crit = childrenOfCriteria(node, includeUnpublished);
366
    		crit.setProjection(Projections.rowCount());
367
    		return ((Integer)crit.uniqueResult().hashCode()).longValue();
368
		}else{
369
			return classificationDao.countChildrenOf(
370
			        node.getTaxon(), classification, null, includeUnpublished);
371
		}
372
	}
373

    
374
    private Criteria childrenOfCriteria(TaxonNode node, boolean includeUnpublished) {
375
        Criteria crit = getSession().createCriteria(TaxonNode.class);
376
        crit.add( Restrictions.like("treeIndex", node.treeIndex()+ "%") );
377
        if (!includeUnpublished){
378
            crit.createCriteria("taxon").add( Restrictions.eq("publish", Boolean.TRUE));
379
        }
380
        return crit;
381
    }
382

    
383
    @Override
384
    public List<TaxonNodeAgentRelation> listTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid,
385
            UUID agentUuid, UUID rankUuid, UUID relTypeUuid, Integer start, Integer limit,
386
            List<String> propertyPaths) {
387

    
388
        StringBuilder hql = prepareListTaxonNodeAgentRelations(taxonUuid, classificationUuid,
389
                agentUuid, rankUuid, relTypeUuid, false);
390

    
391
        Query<TaxonNodeAgentRelation> query =  getSession().createQuery(hql.toString(), TaxonNodeAgentRelation.class);
392
        if(limit != null) {
393
            query.setMaxResults(limit);
394
            if(start != null) {
395
                query.setFirstResult(start);
396
            }
397
        }
398

    
399
        setParamsForListTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid, query);
400

    
401
        List<TaxonNodeAgentRelation> records = query.list();
402

    
403
        if(propertyPaths != null) {
404
            defaultBeanInitializer.initializeAll(records, propertyPaths);
405
        }
406
        return records;
407
    }
408

    
409
    @Override
410
    public <S extends TaxonNode> List<S> list(Class<S> type, List<Restriction<?>> restrictions, Integer limit,
411
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
412
        // TODO Auto-generated method stub
413
        return list(type, restrictions, limit, start, orderHints, propertyPaths, INCLUDE_UNPUBLISHED);
414
    }
415

    
416
    @Override
417
    public <S extends TaxonNode> List<S> list(Class<S> type, List<Restriction<?>> restrictions, Integer limit,
418
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths, boolean includePublished) {
419

    
420
        Criteria criteria = createCriteria(type, restrictions, false);
421

    
422
        if(!includePublished){
423
            criteria.add(Restrictions.eq("taxon.publish", true));
424
        }
425

    
426
        addLimitAndStart(criteria, limit, start);
427
        addOrder(criteria, orderHints);
428

    
429
        @SuppressWarnings("unchecked")
430
        List<S> result = criteria.list();
431
        defaultBeanInitializer.initializeAll(result, propertyPaths);
432
        return result;
433
    }
434

    
435
    @Override
436
    public long count(Class<? extends TaxonNode> type, List<Restriction<?>> restrictions) {
437
        return count(type, restrictions, INCLUDE_UNPUBLISHED);
438
    }
439

    
440

    
441
    @Override
442
    public long count(Class<? extends TaxonNode> type, List<Restriction<?>> restrictions, boolean includePublished) {
443

    
444
        Criteria criteria = createCriteria(type, restrictions, false);
445
        if(!includePublished){
446
            criteria.add(Restrictions.eq("taxon.publish", true));
447
        }
448
        criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
449
        return (Long) criteria.uniqueResult();
450
    }
451

    
452
    @Override
453
    public long countTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid, UUID agentUuid, UUID rankUuid, UUID relTypeUuid) {
454

    
455
        StringBuilder hql = prepareListTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid, true);
456
        Query<Long> query =  getSession().createQuery(hql.toString(), Long.class);
457

    
458
        setParamsForListTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid, query);
459

    
460
        Long count = query.uniqueResult();
461

    
462
        return count;
463
    }
464

    
465
    /**
466
     * @param taxonUuid
467
     * @param classificationUuid
468
     * @param agentUuid
469
     * @param relTypeUuid TODO
470
     * @param doCount TODO
471
     * @param rankId
472
     *     limit to taxa having this rank, only applies if <code>taxonUuid = null</code>
473
     * @return
474
     */
475
    private StringBuilder prepareListTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid, UUID agentUuid, UUID rankUuid, UUID relTypeUuid, boolean doCount) {
476

    
477
        StringBuilder hql = new StringBuilder();
478

    
479
        String join_fetch_mode = doCount ? "JOIN" : "JOIN FETCH";
480

    
481
        if(doCount) {
482
            hql.append("SELECT COUNT(tnar)");
483
        } else {
484
            hql.append("SELECT tnar");
485
        }
486

    
487
        hql.append(" FROM TaxonNodeAgentRelation AS tnar ");
488
        if(taxonUuid != null) {
489
            // taxonUuid is search filter, do not fetch it
490
            hql.append(" JOIN tnar.taxonNode AS tn "
491
                    + "  JOIN tn.taxon AS t ");
492
        } else {
493
            hql.append(join_fetch_mode)
494
                .append(" tnar.taxonNode AS tn ")
495
                .append(join_fetch_mode).append(" tn.taxon AS t ");
496
            if(rankUuid != null) {
497
                hql.append(" join t.name as n ");
498
            }
499
        }
500
        hql.append(" JOIN tn.classification AS c ");
501
        if(agentUuid != null) {
502
            // agentUuid is search filter, do not fetch it
503
//            hql.append(" join tnar.agent as a ");
504
            hql.append(join_fetch_mode).append(" tnar.agent AS a ");
505
        } else {
506
            hql.append(join_fetch_mode).append(" tnar.agent AS a ");
507
        }
508

    
509
        hql.append(" WHERE (1 = 1) ");
510

    
511
        if(relTypeUuid != null) {
512
            hql.append(" AND tnar.type.uuid = :relTypeUuid ");
513
        }
514

    
515
        if(taxonUuid != null) {
516
            hql.append(" AND t.uuid = :taxonUuid ");
517
        } else {
518
            if(rankUuid != null) {
519
                hql.append(" AND n.rank.uuid = :rankUuid ");
520
            }
521
        }
522
        if(classificationUuid != null) {
523
            hql.append(" AND c.uuid = :classificationUuid ");
524
        }
525
        if(agentUuid != null) {
526
            hql.append(" AND a.uuid = :agentUuid ");
527
        }
528

    
529
        hql.append(" ORDER BY a.titleCache");
530
        return hql;
531
    }
532

    
533
    private void setParamsForListTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid, UUID agentUuid,
534
            UUID rankUuid, UUID relTypeUuid, Query<?> query) {
535

    
536
        if(taxonUuid != null) {
537
            query.setParameter("taxonUuid", taxonUuid);
538
        } else {
539
            if(rankUuid != null) {
540
                query.setParameter("rankUuid", rankUuid);
541
            }
542
        }
543
        if(classificationUuid != null) {
544
            query.setParameter("classificationUuid", classificationUuid);
545
        }
546
        if(agentUuid != null) {
547
            query.setParameter("agentUuid", agentUuid);
548
        }
549
        if(relTypeUuid != null) {
550
            query.setParameter("relTypeUuid", relTypeUuid);
551
        }
552
    }
553

    
554
    @Override
555
    public Map<TreeIndex, Integer> rankOrderIndexForTreeIndex(List<TreeIndex> treeIndexes,
556
            Integer minRankOrderIndex,
557
            Integer maxRankOrderIndex) {
558

    
559
        Map<TreeIndex, Integer> result = new HashMap<>();
560
        if (treeIndexes == null || treeIndexes.isEmpty()){
561
            return result;
562
        }
563

    
564
        String hql = " SELECT tn.treeIndex, r.orderIndex "
565
                + " FROM TaxonNode tn "
566
                + "     JOIN tn.taxon t "
567
                + "     JOIN t.name n "
568
                + "      JOIN n.rank r "
569
                + " WHERE tn.treeIndex IN (:treeIndexes) ";
570
        if (minRankOrderIndex != null){
571
            hql += " AND r.orderIndex <= :minOrderIndex";
572
        }
573
        if (maxRankOrderIndex != null){
574
            hql += " AND r.orderIndex >= :maxOrderIndex";
575
        }
576

    
577
        Query<Object[]> query =  getSession().createQuery(hql, Object[].class);
578
        query.setParameterList("treeIndexes", TreeIndex.toString(treeIndexes));
579
        if (minRankOrderIndex != null){
580
            query.setParameter("minOrderIndex", minRankOrderIndex);
581
        }
582
        if (maxRankOrderIndex != null){
583
            query.setParameter("maxOrderIndex", maxRankOrderIndex);
584
        }
585

    
586
        @SuppressWarnings("unchecked")
587
        List<Object[]> list = query.list();
588
        for (Object[] o : list){
589
            result.put(TreeIndex.NewInstance((String)o[0]), (Integer)o[1]);
590
        }
591
        return result;
592
    }
593

    
594
    @Override
595
    public Map<TreeIndex, UuidAndTitleCache<?>> taxonUuidsForTreeIndexes(Collection<TreeIndex> treeIndexes) {
596
        Map<TreeIndex, UuidAndTitleCache<?>> result = new HashMap<>();
597
        if (treeIndexes == null || treeIndexes.isEmpty()){
598
            return result;
599
        }
600

    
601
        String hql =
602
                  " SELECT tn.treeIndex, t.uuid, tnb.titleCache "
603
                + " FROM TaxonNode tn JOIN tn.taxon t Join t.name tnb "
604
                + " WHERE tn.treeIndex IN (:treeIndexes) ";
605
        Query<Object[]> query =  getSession().createQuery(hql, Object[].class);
606
        query.setParameterList("treeIndexes", TreeIndex.toString(treeIndexes));
607

    
608
        @SuppressWarnings("unchecked")
609
        List<Object[]> list = query.list();
610
        for (Object[] o : list){
611
            result.put(TreeIndex.NewInstance((String)o[0]), new UuidAndTitleCache<>((UUID)o[1], null, (String)o[2]));
612
        }
613
        return result;
614
    }
615

    
616
    @Override
617
    public List<TaxonNodeDto> getParentTaxonNodeDtoForRank(
618
            Classification classification, Rank rank, TaxonBase<?> taxonBase) {
619

    
620
        Taxon taxon = null;
621
        if (taxonBase instanceof Taxon) {
622
            taxon = CdmBase.deproxy(taxonBase, Taxon.class);
623
        }else {
624
            taxon = CdmBase.deproxy(((Synonym)taxonBase).getAcceptedTaxon());
625
        }
626
        TaxonNode node = null;
627
        if (taxon != null) {
628
            node = taxon.getTaxonNode(classification);
629
        }
630
        List<TaxonNodeDto> result = new ArrayList<>();
631
        if (node != null) {
632
            String treeIndex = node.treeIndex();
633
            List<Integer> ancestorNodeIds = TreeIndex.NewInstance(treeIndex).parentNodeIds(false);
634

    
635
            Criteria nodeCrit = getSession().createCriteria(TaxonNode.class);
636
            Criteria taxonCrit = nodeCrit.createCriteria("taxon");
637
            Criteria nameCrit = taxonCrit.createCriteria("name");
638
            nodeCrit.add(Restrictions.in("id", ancestorNodeIds));
639
            nodeCrit.add(Restrictions.eq("classification", classification));
640
            nameCrit.add(Restrictions.eq("rank", rank));
641

    
642
            @SuppressWarnings("unchecked")
643
            List<TaxonNode> list = nodeCrit.list();
644
            for (TaxonNode rankNode : list){
645
                TaxonNodeDto dto = new TaxonNodeDto(rankNode);
646
                result.add(dto);
647
            }
648
        }
649
        return result;
650
    }
651

    
652

    
653
    @Override
654
    public List<TaxonNodeDto> getParentTaxonNodeDtoForRank(
655
            Classification classification, Rank rank, TaxonName name) {
656

    
657
    	Set<TaxonBase> taxa = name.getTaxonBases();
658
    	List<TaxonNodeDto> result = new ArrayList<>();
659
    	for (TaxonBase<?> taxonBase:taxa) {
660
    	    List<TaxonNodeDto> tmpList = getParentTaxonNodeDtoForRank(classification, rank, taxonBase);
661
    	    for (TaxonNodeDto tmpDto : tmpList){
662
    	        boolean exists = false; //an equal method does not yet exist for TaxonNodeDto therefore this workaround
663
    	        for (TaxonNodeDto dto: result){
664
    	            if (dto.getTreeIndex().equals(tmpDto.getTreeIndex())){
665
    	                exists = true;
666
    	            }
667
    	        }
668
    	        if (!exists){
669
    	            result.add(tmpDto);
670
    	        }
671
    	    }
672
    	}
673
    	return result;
674
    }
675

    
676
    @Override
677
    public int countSecundumForSubtreeAcceptedTaxa(TreeIndex subTreeIndex, Reference newSec,
678
            boolean overwriteExisting, boolean includeSharedTaxa, boolean emptySecundumDetail) {
679
        String queryStr = forSubtreeAcceptedQueryStr(includeSharedTaxa, subTreeIndex, false, SelectMode.COUNT);
680
        if (!overwriteExisting){
681
            queryStr += " AND t.secSource.citation IS NULL ";
682
        }
683
        return countResult(queryStr);
684
    }
685

    
686
    private int countResult(String queryStr) {
687
        Query<Long> query = getSession().createQuery(queryStr, Long.class);
688
        return query.uniqueResult().intValue();
689
    }
690

    
691
    @Override
692
    public int countSecundumForSubtreeSynonyms(TreeIndex subTreeIndex, Reference newSec,
693
            boolean overwriteExisting, boolean includeSharedTaxa, boolean emptySecundumDetail) {
694
        String queryStr = forSubtreeSynonymQueryStr(includeSharedTaxa, subTreeIndex, false, SelectMode.COUNT);
695
        if (!overwriteExisting){
696
            queryStr += " AND syn.secSource.citation IS NULL ";
697
        }
698
        return countResult(queryStr);
699
    }
700

    
701
    @Override
702
    public int countSecundumForSubtreeRelations(TreeIndex subTreeIndex, Reference newSec,
703
            boolean overwriteExisting, boolean includeSharedTaxa, boolean emptySecundumDetail) {
704
        String queryStr = forSubtreeRelationQueryStr(includeSharedTaxa, overwriteExisting, subTreeIndex, SelectMode.COUNT);
705
        return countResult(queryStr);
706
    }
707

    
708
    //#3465
709
    @Override
710
    public Set<CdmBase> setSecundumForSubtreeAcceptedTaxa(TreeIndex subTreeIndex, Reference newSec,
711
            boolean overwriteExisting, boolean includeSharedTaxa, boolean emptyDetail, IProgressMonitor monitor) {
712
        //for some reason this does not work, maybe because the listeners are not activated,
713
        //but also the first taxon for some reason does not get updated in terms of secundum, but only by the update listener
714
//        String where = "SELECT t.id FROM TaxonNode tn JOIN tn.taxon t " +
715
//                " WHERE tn.treeIndex like '%s%%' ORDER BY t.id";
716
//        where = String.format(where, subTreeIndex.toString());
717
//        Query query1 = getSession().createQuery(where);
718
//        List l = query1.list();
719
//
720
//        String hql = "UPDATE Taxon SET sec = :newSec, publish=false WHERE id IN (" + where + ")";
721
//        Query query = getSession().createQuery(hql);
722
//        query.setParameter("newSec", newSec);
723
//        int n = query.executeUpdate();
724

    
725
        String queryStr = forSubtreeAcceptedQueryStr(includeSharedTaxa, subTreeIndex, false, SelectMode.ID);
726
        if (!overwriteExisting){
727
            queryStr += " AND t.secSource.citation IS NULL ";
728
        }
729
        return setSecundum(newSec, emptyDetail, queryStr, monitor);
730
    }
731

    
732
    @Override
733
    public Set<CdmBase> setSecundumForSubtreeSynonyms(TreeIndex subTreeIndex, Reference newSec,
734
            boolean overwriteExisting, boolean includeSharedTaxa, boolean emptyDetail, IProgressMonitor monitor) {
735

    
736
        String queryStr = forSubtreeSynonymQueryStr(includeSharedTaxa, subTreeIndex, false, SelectMode.ID);
737
        if (!overwriteExisting){
738
            queryStr += " AND syn.secSource.citation IS NULL ";
739
        }
740
        return setSecundum(newSec, emptyDetail, queryStr, monitor);
741
    }
742

    
743
    private <T extends TaxonBase<?>> Set<CdmBase> setSecundum(Reference newSec, boolean emptyDetail, String queryStr, IProgressMonitor monitor) {
744
        Set<CdmBase> result = new HashSet<>();
745
        Query<Integer> query = getSession().createQuery(queryStr, Integer.class);
746
        List<List<Integer>> partitionList = splitIdList(query.list(), DEFAULT_SET_SUBTREE_PARTITION_SIZE);
747
        for (List<Integer> taxonIdList : partitionList){
748
            @SuppressWarnings({ "unchecked", "rawtypes" })
749
            List<T> taxonList = (List)taxonDao.loadList(taxonIdList, null, null);
750
            for (T taxonBase : taxonList){
751
                if (taxonBase != null){
752
                    taxonBase = CdmBase.deproxy(taxonBase);
753
                    SecundumSource secSourceBefore = taxonBase.getSecSource();
754
                    Reference refBefore = taxonBase.getSec();
755
                    String refDetailBefore = taxonBase.getSecMicroReference();
756
                    if (newSec == null && taxonBase.getSec() !=null
757
                            || newSec != null && (taxonBase.getSec() == null || !newSec.equals(taxonBase.getSec()) )){
758
                        taxonBase.setSec(newSec);
759
                    }
760
                    if (emptyDetail){
761
                        if (taxonBase.getSecMicroReference() != null){
762
                            taxonBase.setSecMicroReference(null);
763
                        }
764
                    }
765
                    //compute updated objects
766
                    SecundumSource secSourceAfter = taxonBase.getSecSource();
767

    
768
                    if (!CdmUtils.nullSafeEqual(secSourceBefore, secSourceAfter)){
769
                        result.add(taxonBase);
770
                        //FIXME #9627 remove if fixed
771
                        result.add(taxonBase);
772
                        //EMXIF
773
                    }else if (secSourceBefore != null && secSourceBefore.equals(secSourceAfter)
774
                            && (!CdmUtils.nullSafeEqual(refBefore, secSourceAfter.getCitation())
775
                                 || !CdmUtils.nullSafeEqual(refDetailBefore, secSourceAfter.getCitationMicroReference()))
776
                            ){
777
                        result.add(secSourceBefore);
778
                        //FIXME #9627 remove if fixed
779
                        result.add(taxonBase);
780
                        //EMXIF
781
                    }
782

    
783
                    monitor.worked(1);
784
                    if (monitor.isCanceled()){
785
                        return result;
786
                    }
787
                }
788
            }
789
            commitAndRestartTransaction(newSec);
790
            monitor.worked(taxonIdList.size());
791
        }
792
        return result;
793
    }
794

    
795
    private void commitAndRestartTransaction(CdmBase... cdmBaseToUpdate) {
796
        getSession().getTransaction().commit();
797
        getSession().clear();
798
        getSession().beginTransaction();
799
        for (CdmBase cdmBase : cdmBaseToUpdate){
800
            if (cdmBase != null){
801
                getSession().update(cdmBase);
802
            }
803
        }
804
    }
805

    
806
    @Override
807
    public Set<CdmBase> setSecundumForSubtreeRelations(TreeIndex subTreeIndex, Reference newRef,
808
            Set<UUID> relationTypes,  boolean overwriteExisting, boolean includeSharedTaxa, boolean emptyDetail, IProgressMonitor monitor) {
809

    
810
        String queryStr = forSubtreeRelationQueryStr(includeSharedTaxa, overwriteExisting, subTreeIndex, SelectMode.ID);
811

    
812
        Set<CdmBase> result = new HashSet<>();
813
        Query<Integer> query = getSession().createQuery(queryStr, Integer.class);
814

    
815
        List<List<Integer>> partitionList = splitIdList(query.list(), DEFAULT_SET_SUBTREE_PARTITION_SIZE);
816
        for (List<Integer> relIdList : partitionList){
817
            List<TaxonRelationship> relList = taxonRelDao.loadList(relIdList, null, null);
818
            for (TaxonRelationship rel : relList){
819
                if (rel != null){
820
                    rel = CdmBase.deproxy(rel);
821

    
822
                    NamedSource sourceBefore = rel.getSource();
823
                    Reference refBefore = rel.getCitation();
824
                    String refDetailBefore = rel.getCitationMicroReference();
825
                    if (newRef == null && rel.getCitation() !=null
826
                            || newRef != null && (rel.getCitation() == null || !newRef.equals(rel.getCitation()) )){
827
                        rel.setCitation(newRef);
828
                    }
829
                    if (emptyDetail){
830
                        if (rel.getCitationMicroReference() != null){
831
                            rel.setCitationMicroReference(null);
832
                        }
833
                    }
834
                    //compute updated objects
835
                    NamedSource sourceAfter = rel.getSource();
836
                    if (!CdmUtils.nullSafeEqual(sourceBefore, sourceAfter)){
837
                        result.add(rel);
838
                        //FIXME #9627 remove if fixed
839
                        result.add(rel.getToTaxon());
840
                        //EMXIF
841

    
842
                    }else if (sourceBefore != null && sourceBefore.equals(sourceAfter)
843
                            && (!CdmUtils.nullSafeEqual(refBefore, sourceAfter.getCitation())
844
                                 || !CdmUtils.nullSafeEqual(refDetailBefore,sourceAfter.getCitationMicroReference()))
845
                            ){
846
                        result.add(sourceBefore);
847
                        //FIXME #9627 remove if fixed
848
                        result.add(rel.getToTaxon());
849
                        //EMXIF
850
                    }
851

    
852
                    monitor.worked(1);
853
                    if (monitor.isCanceled()){
854
                        return result;
855
                    }
856
                }
857
            }
858
            commitAndRestartTransaction();
859
            monitor.worked(relList.size());
860
        }
861

    
862
        return result;
863
    }
864

    
865
    private List<List<Integer>> splitIdList(List<Integer> idList, Integer size){
866
        List<List<Integer>> result = new ArrayList<>();
867
        for (int i = 0; (i*size)<idList.size(); i++) {
868
            int upper = Math.min((i+1)*size, idList.size());
869
            result.add(idList.subList(i*size, upper));
870
        }
871
        return result;
872
    }
873

    
874
    @Override
875
    public int countPublishForSubtreeAcceptedTaxa(TreeIndex subTreeIndex, boolean publish, boolean includeSharedTaxa, boolean includeHybrids) {
876
        String queryStr = forSubtreeAcceptedQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.COUNT);
877
        queryStr += " AND t.publish != :publish ";
878
        Query<Long> query = getSession().createQuery(queryStr, Long.class);
879
        query.setParameter("publish", publish);
880
        return query.uniqueResult().intValue();
881
    }
882

    
883
    @Override
884
    public int countPublishForSubtreeSynonyms(TreeIndex subTreeIndex, boolean publish, boolean includeSharedTaxa, boolean includeHybrids) {
885
        String queryStr = forSubtreeSynonymQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.COUNT);
886
        queryStr += " AND syn.publish != :publish ";
887
        Query<Long> query = getSession().createQuery(queryStr, Long.class);
888
        query.setParameter("publish", publish);
889
        return query.uniqueResult().intValue();
890
    }
891

    
892
    @Override
893
    public Set<TaxonBase> setPublishForSubtreeAcceptedTaxa(TreeIndex subTreeIndex, boolean publish,
894
            boolean includeSharedTaxa, boolean includeHybrids, IProgressMonitor monitor) {
895
        String queryStr = forSubtreeAcceptedQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.ID);
896
        queryStr += " AND t.publish != :publish ";
897
        return setPublish(publish, queryStr, null, monitor);
898
    }
899

    
900
    @Override
901
    public Set<TaxonBase> setPublishForSubtreeSynonyms(TreeIndex subTreeIndex, boolean publish,
902
            boolean includeSharedTaxa, boolean includeHybrids, IProgressMonitor monitor) {
903
        String queryStr = forSubtreeSynonymQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.ID);
904
        queryStr += " AND syn.publish != :publish ";
905
        return setPublish(publish, queryStr, null, monitor);
906
    }
907

    
908
    @Override
909
    public int countPublishForSubtreeRelatedTaxa(TreeIndex subTreeIndex, boolean publish, boolean includeSharedTaxa, boolean includeHybrids) {
910
        String queryStr = forSubtreeRelatedTaxaQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.COUNT);
911
        queryStr += " AND relTax.publish != :publish ";
912
        Query<Long> query = getSession().createQuery(queryStr, Long.class);
913
        query.setParameter("publish", publish);
914
        return query.uniqueResult().intValue();
915
    }
916

    
917
    @Override
918
    public Set<TaxonBase> setPublishForSubtreeRelatedTaxa(TreeIndex subTreeIndex, boolean publish,
919
            Set<UUID> relationTypes, boolean includeSharedTaxa, boolean includeHybrids,
920
            IProgressMonitor monitor) {
921
        String queryStr = forSubtreeRelatedTaxaQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.ID);
922
        queryStr += " AND relTax.publish != :publish ";
923
        queryStr += " AND rel.type.uuid IN (:relTypeUuid)";
924
        return setPublish(publish, queryStr, relationTypes, monitor);
925
    }
926

    
927
    private <T extends TaxonBase<?>> Set<T> setPublish(boolean publish, String queryStr, Set<UUID> relTypeUuids, IProgressMonitor monitor) {
928
        Set<T> result = new HashSet<>();
929
        Query<Integer> query = getSession().createQuery(queryStr, Integer.class);
930
        query.setParameter("publish", publish);
931
        if (relTypeUuids != null && !relTypeUuids.isEmpty()){
932
            query.setParameterList("relTypeUuid", relTypeUuids);
933
        }
934
        @SuppressWarnings("unchecked")
935
        List<List<Integer>> partitionList = splitIdList(query.list(), DEFAULT_SET_SUBTREE_PARTITION_SIZE);
936
        for (List<Integer> taxonIdList : partitionList){
937
            @SuppressWarnings({ "unchecked", "rawtypes" })
938
            List<T> taxonList = (List)taxonDao.loadList(taxonIdList, null, null);
939
            for (T taxonBase : taxonList){
940
                if (taxonBase != null){
941
                    if (taxonBase.isPublish() != publish){  //to be on the save side
942
                        taxonBase.setPublish(publish);
943
                        result.add(CdmBase.deproxy(taxonBase));
944
                    }
945
                    monitor.worked(1);
946
                    if (monitor.isCanceled()){
947
                        return result;
948
                    }
949
                }
950
            }
951
            commitAndRestartTransaction();
952
        }
953
        return result;
954
    }
955

    
956
    private String forSubtreeSynonymQueryStr(boolean includeSharedTaxa, TreeIndex subTreeIndex, boolean excludeHybrids, SelectMode mode) {
957
        String queryStr = "SELECT " + mode.hql("syn")
958
                + " FROM TaxonNode tn "
959
                + "   JOIN tn.taxon t "
960
                + "   JOIN t.synonyms syn  "
961
                + "   LEFT JOIN syn.name n "
962
                + "   LEFT JOIN syn.secSource ss ";
963
        String whereStr = " tn.treeIndex LIKE '%1$s%%' ";
964
        if (!includeSharedTaxa){
965
            whereStr += " AND NOT EXISTS ("
966
                    + "FROM TaxonNode tn2 WHERE tn2.taxon = t AND tn2.treeIndex not like '%1$s%%')  ";
967
        }
968
        whereStr = handleExcludeHybrids(whereStr, excludeHybrids, "syn");
969
        queryStr += " WHERE " + String.format(whereStr, subTreeIndex.toString());
970

    
971
        return queryStr;
972
    }
973

    
974
    private String handleExcludeHybrids(String whereStr, boolean excludeHybrids, String t) {
975
        if(excludeHybrids){
976

    
977
            String hybridWhere =  " AND (n is NULL OR "
978
                    + " (n.monomHybrid=0 AND n.binomHybrid=0 "
979
                    + "   AND n.trinomHybrid=0 AND n.hybridFormula=0 )) ";
980

    
981
            whereStr += hybridWhere; //String.format(hybridWhere, t);
982
        }
983
        return whereStr;
984
    }
985

    
986
    private String forSubtreeRelatedTaxaQueryStr(boolean includeSharedTaxa, TreeIndex subTreeIndex,
987
            boolean excludeHybrids, SelectMode mode) {
988
        String queryStr = "SELECT " + mode.hql("relTax")
989
                + " FROM TaxonNode tn "
990
                + "   JOIN tn.taxon t "
991
                + "   JOIN t.relationsToThisTaxon rel"
992
                + "   JOIN rel.relatedFrom relTax "
993
                + "   LEFT JOIN relTax.name n ";
994
        String whereStr =" tn.treeIndex LIKE '%1$s%%' ";
995
        if (!includeSharedTaxa){
996
            //toTaxon should only be used in the given subtree
997
            whereStr += " AND NOT EXISTS ("
998
                    + "FROM TaxonNode tn2 WHERE tn2.taxon = t AND tn2.treeIndex not like '%1$s%%')  ";
999
            //from taxon should not be used in another classification
1000
            whereStr += " AND NOT EXISTS ("
1001
                    + "FROM TaxonNode tn3 WHERE tn3.taxon = relTax AND tn3.treeIndex not like '%1$s%%')  ";
1002
            //fromTaxon should not be related as e.g. pro parte synonym or misapplication to
1003
            //another taxon which is not part of the subtree
1004
            //TODO and has not the publish state
1005
            whereStr += " AND NOT EXISTS ("
1006
                    + "FROM TaxonNode tn4 JOIN tn4.taxon t2 JOIN t2.relationsToThisTaxon rel2  "
1007
                    + "   WHERE rel2.relatedFrom = relTax AND tn4.treeIndex not like '%1$s%%' "
1008
                    + "         AND tn4.taxon.publish != :publish ) ";
1009
        }
1010
        whereStr = handleExcludeHybrids(whereStr, excludeHybrids, "relTax");
1011
        queryStr += " WHERE " + String.format(whereStr, subTreeIndex.toString());
1012

    
1013
        return queryStr;
1014
    }
1015

    
1016
    private String forSubtreeRelationQueryStr(boolean includeSharedTaxa, boolean overwriteExisting,
1017
            TreeIndex subTreeIndex, SelectMode mode) {
1018

    
1019
        String queryStr = "SELECT " + mode.hql("rel")
1020
                + " FROM TaxonNode tn "
1021
                + "   JOIN tn.taxon t "
1022
                + "   JOIN t.relationsToThisTaxon rel "
1023
                + "   LEFT JOIN rel.source src ";
1024
        String whereStr =" tn.treeIndex LIKE '%1$s%%' ";
1025
        if (!includeSharedTaxa){
1026
            //toTaxon should only be used in the given subtree
1027
            whereStr += " AND NOT EXISTS ("
1028
                    + "FROM TaxonNode tn2 WHERE tn2.taxon = t AND tn2.treeIndex not like '%1$s%%')  ";
1029
        }
1030
        queryStr += " WHERE " + String.format(whereStr, subTreeIndex.toString());
1031
        if (!overwriteExisting){
1032
            queryStr += " AND (rel.source IS NULL OR src.citation IS NULL) ";
1033
        }
1034

    
1035
        return queryStr;
1036
    }
1037

    
1038
    private enum SelectMode{
1039
        COUNT(" count(*) "),
1040
        ID ("id "),
1041
        UUID("uuid "),
1042
        FULL("");
1043
        private String hql;
1044
        SelectMode(String hql){
1045
            this.hql = hql;
1046
        }
1047
        public String hql(String prefix){
1048
            switch (this){
1049
            case ID:
1050
            case UUID:
1051
                return CdmUtils.Nz(prefix)+"." + hql;
1052
            case FULL:
1053
                return CdmUtils.Nz(prefix) + hql;
1054
            case COUNT:
1055
            default: return hql;
1056
            }
1057

    
1058
        }
1059
    }
1060

    
1061
    private String forSubtreeAcceptedQueryStr(boolean includeSharedTaxa, TreeIndex subTreeIndex, boolean excludeHybrids, SelectMode mode) {
1062
        String queryStr = "SELECT " + mode.hql("t")
1063
                + " FROM TaxonNode tn "
1064
                + "   JOIN tn.taxon t "
1065
                + "   LEFT JOIN t.name n "
1066
                + "   LEFT JOIN t.secSource ss ";
1067
        String whereStr = " tn.treeIndex like '%1$s%%' ";
1068
        if (!includeSharedTaxa){
1069
            whereStr += " AND NOT EXISTS ("
1070
                    + "FROM TaxonNode tn2 WHERE tn2.taxon = t AND tn2.treeIndex not like '%1$s%%')  ";
1071
        }
1072
        whereStr = handleExcludeHybrids(whereStr, excludeHybrids, "t");
1073
        queryStr += " WHERE " + String.format(whereStr, subTreeIndex.toString());
1074

    
1075
        return queryStr;
1076
    }
1077

    
1078
    @Override
1079
    public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(Classification classification, Integer limit, String pattern, boolean searchForClassifications, boolean includeDoubtful) {
1080

    
1081
         Query<SortableTaxonNodeQueryResult> query = createQueryForUuidAndTitleCache(limit, classification.getUuid(), pattern, includeDoubtful);
1082
         List<SortableTaxonNodeQueryResult> result = query.list();
1083

    
1084

    
1085
         if (searchForClassifications){
1086
             String queryString = "SELECT new " + SortableTaxonNodeQueryResult.class.getName() + "("
1087
                     + " node.uuid, node.id, node.classification.titleCache, parent.uuid"
1088
                     + ") "
1089
                     + " FROM TaxonNode AS node "
1090
                     + " LEFT OUTER JOIN node.parent as parent"
1091
                     + " WHERE node.classification.id = " + classification.getId() +
1092
                          " AND node.taxon IS NULL";
1093
             if (pattern != null){
1094
                 if (pattern.equals("?")){
1095
                     limit = null;
1096
                 } else{
1097
                     if (!pattern.endsWith("*")){
1098
                         pattern += "%";
1099
                     }
1100
                     pattern = pattern.replace("*", "%");
1101
                     pattern = pattern.replace("?", "%");
1102
                     queryString = queryString + " AND node.classification.titleCache LIKE (:pattern) " ;
1103
                 }
1104
             }
1105
             query = getSession().createQuery(queryString, SortableTaxonNodeQueryResult.class);
1106

    
1107
             if (limit != null){
1108
                 query.setMaxResults(limit);
1109
             }
1110

    
1111
             if (pattern != null && !pattern.equals("?")){
1112
                 query.setParameter("pattern", pattern);
1113
             }
1114
             @SuppressWarnings("unchecked")
1115
             List<SortableTaxonNodeQueryResult> resultClassifications = query.list();
1116

    
1117
             result.addAll(resultClassifications);
1118
         }
1119

    
1120
         if(result.size() == 0){
1121
             return null;
1122
         }else{
1123
             List<UuidAndTitleCache<TaxonNode>> list = new ArrayList<>(result.size());
1124
             Collections.sort(result, new SortableTaxonNodeQueryResultComparator());
1125
             for (SortableTaxonNodeQueryResult resultDTO : result){
1126
                 list.add(new UuidAndTitleCache<>(TaxonNode.class, resultDTO.getTaxonNodeUuid(), resultDTO.getTaxonNodeId(), resultDTO.getTaxonTitleCache()));
1127
             }
1128
             return list;
1129
         }
1130
    }
1131

    
1132
    @Override
1133
    public List<TaxonNodeDto> getTaxonNodeDto(Integer limit, String pattern, UUID classificationUuid) {
1134
        String queryString = getTaxonNodeDtoQuery();
1135
        queryString += "  INNER JOIN tn.classification AS cls " + " WHERE t.titleCache LIKE :pattern ";
1136

    
1137
        if(classificationUuid != null){
1138
            queryString += "AND cls.uuid = :classificationUuid";
1139
        }
1140

    
1141
        Query<SortableTaxonNodeQueryResult> query =  getSession().createQuery(queryString, SortableTaxonNodeQueryResult.class);
1142

    
1143
        query.setParameter("pattern", pattern.toLowerCase()+"%");
1144
        if(classificationUuid != null){
1145
            query.setParameter("classificationUuid", classificationUuid);
1146
        }
1147

    
1148
        List<SortableTaxonNodeQueryResult> result = query.list();
1149
        List<TaxonNodeDto> list = createNodeDtos(result);
1150

    
1151
        return list;
1152
    }
1153

    
1154
    @Override
1155
    public TaxonNodeDto getTaxonNodeDto(UUID nodeUuid) {
1156

    
1157
        String queryString = getTaxonNodeDtoQuery();
1158
        queryString += " WHERE tn.uuid = :uuid ";
1159
        Query<SortableTaxonNodeQueryResult> query =  getSession().createNativeQuery(queryString, SortableTaxonNodeQueryResult.class);
1160
        query.setParameter("uuid", nodeUuid);
1161

    
1162
        List<SortableTaxonNodeQueryResult> result = query.list();
1163
        List<TaxonNodeDto> list = createNodeDtos(result);
1164
        if (list.isEmpty()) {
1165
        	return null;
1166
        }
1167
        return list.get(0);
1168
    }
1169

    
1170
    private String getTaxonNodeDtoQuery() {
1171
	        String queryString = "SELECT new " + SortableTaxonNodeQueryResult.class.getName() + "("
1172
                + "tn.uuid, tn.id, tn.treeIndex, t.uuid, t.titleCache, name.titleCache, rank, p.uuid, index(tn), cl.uuid,  t.publish, tn.status, note "
1173
                + ") "
1174
                + " FROM TaxonNode p "
1175
                + "   INNER JOIN p.childNodes AS tn"
1176
                + "   INNER JOIN tn.taxon AS t "               
1177
                + "   INNER JOIN t.name AS name "
1178
                + "   INNER JOIN tn.classification AS cl "
1179
                + "	  LEFT OUTER JOIN tn.statusNote as note "
1180
                + "   LEFT OUTER JOIN name.rank AS rank ";
1181
        return queryString;
1182
    }
1183
    
1184
    public String getTaxonNodeDtoQueryWithoutParent() {
1185
        String queryString = "SELECT new " + SortableTaxonNodeQueryResult.class.getName() + "("
1186
        	+	"tn.uuid, tn.id, t.titleCache"// rank "
1187
                
1188
        //    + "tn.uuid, tn.id, t.uuid, t.titleCache, name.titleCache, rank, cl.uuid,  t.publish, tn.status, note "
1189
            + ") "
1190
            + " FROM TaxonNode tn "
1191
            + "   LEFT JOIN tn.taxon AS t "     ;          
1192
//            + "   LEFT JOIN t.name AS name "
1193
//            + "   INNER JOIN tn.classification AS cl ";
1194
//            + "	  LEFT OUTER JOIN tn.statusNote as note ";
1195
      //      + "   LEFT OUTER JOIN name.rank AS rank ";
1196
        return queryString;
1197
    }
1198
    
1199
    
1200

    
1201
    @Override
1202
    public List<TaxonNodeDto> getTaxonNodeDtos(List<UUID> nodeUuids) {
1203
        String queryString = getTaxonNodeDtoQuery();
1204
        queryString = queryString + " WHERE tn.uuid IN (:uuid) ";
1205

    
1206
        Query<SortableTaxonNodeQueryResult> query =  getSession().createQuery(queryString, SortableTaxonNodeQueryResult.class);
1207
        query.setParameterList("uuid", nodeUuids);
1208

    
1209
        List<SortableTaxonNodeQueryResult> result = query.list();
1210

    
1211
        List<TaxonNodeDto> list = createNodeDtos(result);
1212

    
1213
        return list;
1214
    }
1215
    
1216
    @Override
1217
    public List<TaxonNodeDto> getTaxonNodeDtosWithoutParent(List<UUID> nodeUuids) {
1218
        String queryString = getTaxonNodeDtoQueryWithoutParent();
1219
        queryString = queryString + " WHERE tn.uuid IN (:uuid) ";
1220

    
1221
        Query<SortableTaxonNodeQueryResult> query =  getSession().createQuery(queryString, SortableTaxonNodeQueryResult.class);
1222
        query.setParameterList("uuid", nodeUuids);
1223

    
1224
        List<SortableTaxonNodeQueryResult> result = query.list();
1225

    
1226
        List<TaxonNodeDto> list = createNodeDtos(result);
1227

    
1228
        return list;
1229
    }
1230
    @Override
1231
    public List<TaxonNodeDto> getTaxonNodeDtosFromTaxon(UUID taxonUuid, String subTreeIndex) {
1232
    	String queryString = getTaxonNodeDtoQuery();
1233
        queryString += " WHERE t.uuid = :uuid ";
1234
        if (subTreeIndex != null) {
1235
        	subTreeIndex += "%";
1236
        	queryString += " AND tn.treeIndex like :subTreeIndex ";
1237
        }
1238
        Query<SortableTaxonNodeQueryResult> query =  getSession().createQuery(queryString, SortableTaxonNodeQueryResult.class);
1239
        query.setParameter("uuid", taxonUuid);
1240
        if (subTreeIndex != null) {
1241
        	query.setParameter("subTreeIndex", subTreeIndex);
1242
        }
1243
        
1244
        List<SortableTaxonNodeQueryResult> result = query.list();
1245
        List<TaxonNodeDto> list = createNodeDtos(result);
1246
        if (list.isEmpty()) {
1247
        	return null;
1248
        }
1249
        return list;
1250
    }
1251

    
1252

    
1253
    @Override
1254
    public List<TaxonNodeDto> createNodeDtos(List<SortableTaxonNodeQueryResult> result) {
1255
        List<TaxonNodeDto> nodeDtos = new ArrayList<>();
1256
        Collections.sort(result, new SortableTaxonNodeQueryResultComparator());
1257
        for(SortableTaxonNodeQueryResult queryDTO : result){
1258
            TaxonNodeDto nodeDto = new TaxonNodeDto(queryDTO.getTaxonNodeUuid(), queryDTO.getTaxonNodeId(), queryDTO.getTreeIndex(), queryDTO.getNameTitleCache(), queryDTO.getTaxonTitleCache(), queryDTO.getNameRank()!= null? queryDTO.getNameRank().getOrderIndex(): null, queryDTO.getParentNodeUuid(), queryDTO.getSortIndex(), queryDTO.getClassificationUuid(), queryDTO.isTaxonIsPublish(), queryDTO.getStatus(), queryDTO.getStatusNote());
1259
            nodeDtos.add(nodeDto);
1260
        }
1261
        return nodeDtos;
1262
    }
1263

    
1264
    @Override
1265
    public List<TaxonNodeDto> getTaxonNodeForTaxonInClassificationDto(UUID taxonUUID, UUID classificationUuid) {
1266
        String queryString = getTaxonNodeDtoQuery();
1267
        queryString = queryString + "   INNER JOIN tn.classification AS cls "  + " WHERE t.uuid = :uuid ";
1268

    
1269
        if(classificationUuid != null){
1270
            queryString += "AND cls.uuid = :classificationUuid";
1271
        }
1272

    
1273
        Query<SortableTaxonNodeQueryResult> query =  getSession().createQuery(queryString, SortableTaxonNodeQueryResult.class);
1274
        query.setParameter("uuid", taxonUUID);
1275

    
1276
        if(classificationUuid != null){
1277
            query.setParameter("classificationUuid", classificationUuid);
1278
        }
1279

    
1280
        List<SortableTaxonNodeQueryResult> result = query.list();
1281
        List<TaxonNodeDto> list = createNodeDtos(result);
1282
        return list;
1283
    }
1284

    
1285
    @Override
1286
    public List<TaxonNodeDto> getUuidAndTitleCache(Integer limit, String pattern, UUID classificationUuid) {
1287
        return getUuidAndTitleCache(limit, pattern, classificationUuid, false);
1288
    }
1289

    
1290
    @Override
1291
    public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(
1292
            Classification classification, Integer limit, String pattern, boolean searchForClassifications) {
1293
        return getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classification, limit, pattern, searchForClassifications, false);
1294
    }
1295

    
1296
	
1297
}
(4-4/6)