Project

General

Profile

Download (53 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;
26
import org.apache.logging.log4j.Logger;
27
import org.hibernate.Criteria;
28
import org.hibernate.Hibernate;
29
import org.hibernate.criterion.Projections;
30
import org.hibernate.criterion.Restrictions;
31
import org.hibernate.query.Query;
32
import org.springframework.beans.factory.annotation.Autowired;
33
import org.springframework.beans.factory.annotation.Qualifier;
34
import org.springframework.stereotype.Repository;
35

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

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

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

    
76
    private static final int DEFAULT_SET_SUBTREE_PARTITION_SIZE = 100;
77

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

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

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

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

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

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

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

    
127
			    }
128
			}
129
		}
130

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

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

    
152
        return result;
153
	}
154

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

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

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

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

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

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

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

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

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

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

    
226
        }
227

    
228
        return list;
229
    }
230

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

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

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

    
270

    
271

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

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

    
289

    
290

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
441

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

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

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

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

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

    
461
        Long count = query.uniqueResult();
462

    
463
        return count;
464
    }
465

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

    
478
        StringBuilder hql = new StringBuilder();
479

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
653

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
863
        return result;
864
    }
865

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

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

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

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

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

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

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

    
928
    private <T extends TaxonBase<?>> Set<T> setPublish(boolean publish, String queryStr, Set<UUID> relTypeUuids, IProgressMonitor monitor) {
929
        Set<T> result = new HashSet<>();
930
        Query<Integer> query = getSession().createQuery(queryStr, Integer.class);
931
        query.setParameter("publish", publish);
932
        if (relTypeUuids != null && !relTypeUuids.isEmpty()){
933
            query.setParameterList("relTypeUuid", relTypeUuids);
934
        }
935

    
936
        List<List<Integer>> partitionList = splitIdList(query.list(), DEFAULT_SET_SUBTREE_PARTITION_SIZE);
937
        for (List<Integer> taxonIdList : partitionList){
938
            @SuppressWarnings({ "unchecked", "rawtypes" })
939
            List<T> taxonList = (List)taxonDao.loadList(taxonIdList, null, null);
940
            for (T taxonBase : taxonList){
941
                if (taxonBase != null){
942
                    if (taxonBase.isPublish() != publish){  //to be on the save side
943
                        taxonBase.setPublish(publish);
944
                        result.add(CdmBase.deproxy(taxonBase));
945
                    }
946
                    monitor.worked(1);
947
                    if (monitor.isCanceled()){
948
                        return result;
949
                    }
950
                }
951
            }
952
            commitAndRestartTransaction();
953
        }
954
        return result;
955
    }
956

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

    
972
        return queryStr;
973
    }
974

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

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

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

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

    
1014
        return queryStr;
1015
    }
1016

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

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

    
1036
        return queryStr;
1037
    }
1038

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

    
1059
        }
1060
    }
1061

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

    
1076
        return queryStr;
1077
    }
1078

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

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

    
1085

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

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

    
1112
             if (pattern != null && !pattern.equals("?")){
1113
                 query.setParameter("pattern", pattern);
1114
             }
1115

    
1116
             List<SortableTaxonNodeQueryResult> resultClassifications = query.list();
1117

    
1118
             result.addAll(resultClassifications);
1119
         }
1120

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

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

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

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

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

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

    
1152
        return list;
1153
    }
1154

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

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

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

    
1171
    private String getTaxonNodeDtoQuery() {
1172
	        String queryString = "SELECT new " + SortableTaxonNodeQueryResult.class.getName() + "("
1173
                + "tn.uuid, tn.id, tn.treeIndex, t.uuid, t.titleCache, name.titleCache, rank, p.uuid, index(tn), cl.uuid,  t.publish, tn.status, note "
1174
                + ") "
1175
                + " FROM TaxonNode p "
1176
                + "   INNER JOIN p.childNodes AS tn"
1177
                + "   INNER JOIN tn.taxon AS t "
1178
                + "   INNER JOIN t.name AS name "
1179
                + "   INNER JOIN tn.classification AS cl "
1180
                + "	  LEFT OUTER JOIN tn.statusNote as note "
1181
                + "   LEFT OUTER JOIN name.rank AS rank ";
1182
        return queryString;
1183
    }
1184

    
1185
    public String getTaxonNodeDtoQueryWithoutParent() {
1186
        String queryString = "SELECT new " + SortableTaxonNodeQueryResult.class.getName() + "("
1187
		+	"tn.uuid, tn.id, t.uuid, t.titleCache"// rank "
1188
            + ") "
1189
            + " FROM TaxonNode tn "
1190
            + "   LEFT JOIN tn.taxon AS t "     ;
1191

    
1192
        return queryString;
1193
    }
1194

    
1195

    
1196

    
1197
    @Override
1198
    public List<TaxonNodeDto> getTaxonNodeDtos(List<UUID> nodeUuids) {
1199
        String queryString = getTaxonNodeDtoQuery();
1200
        queryString = queryString + " WHERE tn.uuid IN (:uuid) ";
1201

    
1202
        Query<SortableTaxonNodeQueryResult> query =  getSession().createQuery(queryString, SortableTaxonNodeQueryResult.class);
1203
        query.setParameterList("uuid", nodeUuids);
1204

    
1205
        List<SortableTaxonNodeQueryResult> result = query.list();
1206

    
1207
        List<TaxonNodeDto> list = createNodeDtos(result);
1208

    
1209
        return list;
1210
    }
1211

    
1212
    @Override
1213
    public List<TaxonNodeDto> getTaxonNodeDtosWithoutParent(List<UUID> nodeUuids) {
1214
        String queryString = getTaxonNodeDtoQueryWithoutParent();
1215
        queryString = queryString + " WHERE tn.uuid IN (:uuid) ";
1216

    
1217
        Query<SortableTaxonNodeQueryResult> query =  getSession().createQuery(queryString, SortableTaxonNodeQueryResult.class);
1218
        query.setParameterList("uuid", nodeUuids);
1219

    
1220
        List<SortableTaxonNodeQueryResult> result = query.list();
1221

    
1222
        List<TaxonNodeDto> list = createNodeDtos(result);
1223

    
1224
        return list;
1225
    }
1226
    @Override
1227
    public List<TaxonNodeDto> getTaxonNodeDtosFromTaxon(UUID taxonUuid, String subTreeIndex) {
1228
    	String queryString = getTaxonNodeDtoQuery();
1229
        queryString += " WHERE t.uuid = :uuid ";
1230
        if (subTreeIndex != null) {
1231
        	subTreeIndex += "%";
1232
        	queryString += " AND tn.treeIndex like :subTreeIndex ";
1233
        }
1234
        Query<SortableTaxonNodeQueryResult> query =  getSession().createQuery(queryString, SortableTaxonNodeQueryResult.class);
1235
        query.setParameter("uuid", taxonUuid);
1236
        if (subTreeIndex != null) {
1237
        	query.setParameter("subTreeIndex", subTreeIndex);
1238
        }
1239

    
1240
        List<SortableTaxonNodeQueryResult> result = query.list();
1241
        List<TaxonNodeDto> list = createNodeDtos(result);
1242
        if (list.isEmpty()) {
1243
        	return null;
1244
        }
1245
        return list;
1246
    }
1247

    
1248

    
1249
    @Override
1250
    public List<TaxonNodeDto> createNodeDtos(List<SortableTaxonNodeQueryResult> result) {
1251
        List<TaxonNodeDto> nodeDtos = new ArrayList<>();
1252
        Collections.sort(result, new SortableTaxonNodeQueryResultComparator());
1253
        for(SortableTaxonNodeQueryResult queryDTO : result){
1254
            TaxonNodeDto nodeDto = new TaxonNodeDto(queryDTO.getTaxonNodeUuid(), queryDTO.getTaxonNodeId(), queryDTO.getTaxonUuid(), 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());
1255
            nodeDtos.add(nodeDto);
1256
        }
1257
        return nodeDtos;
1258
    }
1259

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

    
1265
        if(classificationUuid != null){
1266
            queryString += "AND cls.uuid = :classificationUuid";
1267
        }
1268

    
1269
        Query<SortableTaxonNodeQueryResult> query =  getSession().createQuery(queryString, SortableTaxonNodeQueryResult.class);
1270
        query.setParameter("uuid", taxonUUID);
1271

    
1272
        if(classificationUuid != null){
1273
            query.setParameter("classificationUuid", classificationUuid);
1274
        }
1275

    
1276
        List<SortableTaxonNodeQueryResult> result = query.list();
1277
        List<TaxonNodeDto> list = createNodeDtos(result);
1278
        return list;
1279
    }
1280

    
1281
    @Override
1282
    public List<TaxonNodeDto> getUuidAndTitleCache(Integer limit, String pattern, UUID classificationUuid) {
1283
        return getUuidAndTitleCache(limit, pattern, classificationUuid, false);
1284
    }
1285

    
1286
    @Override
1287
    public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(
1288
            Classification classification, Integer limit, String pattern, boolean searchForClassifications) {
1289
        return getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classification, limit, pattern, searchForClassifications, false);
1290
    }
1291

    
1292

    
1293
}
(4-4/6)