Project

General

Profile

Download (52.2 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.log4j.Logger;
26
import org.hibernate.Criteria;
27
import org.hibernate.Hibernate;
28
import org.hibernate.Query;
29
import org.hibernate.criterion.Projections;
30
import org.hibernate.criterion.Restrictions;
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.AnnotatableDaoImpl;
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 AnnotatableDaoImpl<TaxonNode>
71
		implements ITaxonNodeDao {
72

    
73
	private static final Logger logger = Logger.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 query = session.createQuery("from TaxonNode t where t.taxon = :taxon");
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 query =  getSession().createQuery(queryString);
177
        query.setParameter("parentId", parent.getUuid());
178

    
179
        @SuppressWarnings("unchecked")
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 query =  getSession().createQuery(queryString);
197
        query.setParameter("parentId", parent.getUuid());
198

    
199
        @SuppressWarnings("unchecked")
200
        List<TaxonNode> result = query.list();
201

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

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

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

    
228
        }
229

    
230
        return list;
231
    }
232

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

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

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

    
272

    
273

    
274
      Query query =  getSession().createQuery(queryString);
275
      if (pattern != null){
276
          query.setParameter("pattern", pattern);
277
      }
278
      if (includeDoubtful){
279
          query.setParameter("doubtfulPattern", doubtfulPattern);
280
      }
281

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

    
291

    
292

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

    
305
        @SuppressWarnings("unchecked")
306
        List<Object[]> result = query.list();
307

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

    
331
    private List<TaxonNode> listChildrenOfRecursive(TaxonNode node, List<TaxonNode> previousResult, Integer pageSize, Integer pageIndex,
332
            boolean recursive, boolean includeUnpublished, List<String> propertyPaths, Comparator<TaxonNode> comparator){
333

    
334
        if (recursive == true && comparator == null ){
335
    		Criteria crit = childrenOfCriteria(node, includeUnpublished);
336

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

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

    
358
        } else{
359
    		return classificationDao.listChildrenOf(node.getTaxon(), node.getClassification(), null,
360
    		       includeUnpublished, pageSize, pageIndex, propertyPaths);
361
    	}
362
    }
363

    
364
    @Override
365
	public Long countChildrenOf(TaxonNode node, Classification classification,
366
			boolean recursive, boolean includeUnpublished) {
367

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

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

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

    
392
        StringBuilder hql = prepareListTaxonNodeAgentRelations(taxonUuid, classificationUuid,
393
                agentUuid, rankUuid, relTypeUuid, false);
394

    
395
        Query query =  getSession().createQuery(hql.toString());
396

    
397
        if(limit != null) {
398
            query.setMaxResults(limit);
399
            if(start != null) {
400
                query.setFirstResult(start);
401
            }
402
        }
403

    
404
        setParamsForListTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid, query);
405

    
406
        @SuppressWarnings("unchecked")
407
        List<TaxonNodeAgentRelation> records = query.list();
408

    
409
        if(propertyPaths != null) {
410
            defaultBeanInitializer.initializeAll(records, propertyPaths);
411
        }
412
        return records;
413
    }
414

    
415
    @Override
416
    public <S extends TaxonNode> List<S> list(Class<S> type, List<Restriction<?>> restrictions, Integer limit,
417
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
418
        // TODO Auto-generated method stub
419
        return list(type, restrictions, limit, start, orderHints, propertyPaths, INCLUDE_UNPUBLISHED);
420
    }
421

    
422
    @Override
423
    public <S extends TaxonNode> List<S> list(Class<S> type, List<Restriction<?>> restrictions, Integer limit,
424
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths, boolean includePublished) {
425

    
426
        Criteria criteria = createCriteria(type, restrictions, false);
427

    
428
        if(!includePublished){
429
            criteria.add(Restrictions.eq("taxon.publish", true));
430
        }
431

    
432
        addLimitAndStart(criteria, limit, start);
433
        addOrder(criteria, orderHints);
434

    
435
        @SuppressWarnings("unchecked")
436
        List<S> result = criteria.list();
437
        defaultBeanInitializer.initializeAll(result, propertyPaths);
438
        return result;
439
    }
440

    
441
    @Override
442
    public long count(Class<? extends TaxonNode> type, List<Restriction<?>> restrictions) {
443
        return count(type, restrictions, INCLUDE_UNPUBLISHED);
444
    }
445

    
446

    
447
    @Override
448
    public long count(Class<? extends TaxonNode> type, List<Restriction<?>> restrictions, boolean includePublished) {
449

    
450
        Criteria criteria = createCriteria(type, restrictions, false);
451
        if(!includePublished){
452
            criteria.add(Restrictions.eq("taxon.publish", true));
453
        }
454
        criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
455
        return (Long) criteria.uniqueResult();
456
    }
457

    
458
    @Override
459
    public long countTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid, UUID agentUuid, UUID rankUuid, UUID relTypeUuid) {
460

    
461
        StringBuilder hql = prepareListTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid, true);
462
        Query query =  getSession().createQuery(hql.toString());
463

    
464
        setParamsForListTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid, query);
465

    
466
        Long count = Long.parseLong(query.uniqueResult().toString());
467

    
468
        return count;
469
    }
470

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

    
483
        StringBuilder hql = new StringBuilder();
484

    
485
        String join_fetch_mode = doCount ? "JOIN" : "JOIN FETCH";
486

    
487
        if(doCount) {
488
            hql.append("SELECT COUNT(tnar)");
489
        } else {
490
            hql.append("SELECT tnar");
491
        }
492

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

    
515
        hql.append(" WHERE (1 = 1) ");
516

    
517
        if(relTypeUuid != null) {
518
            hql.append(" AND tnar.type.uuid = :relTypeUuid ");
519
        }
520

    
521
        if(taxonUuid != null) {
522
            hql.append(" AND t.uuid = :taxonUuid ");
523
        } else {
524
            if(rankUuid != null) {
525
                hql.append(" AND n.rank.uuid = :rankUuid ");
526
            }
527
        }
528
        if(classificationUuid != null) {
529
            hql.append(" AND c.uuid = :classificationUuid ");
530
        }
531
        if(agentUuid != null) {
532
            hql.append(" AND a.uuid = :agentUuid ");
533
        }
534

    
535
        hql.append(" ORDER BY a.titleCache");
536
        return hql;
537
    }
538

    
539
    /**
540
     * @param taxonUuid
541
     * @param classificationUuid
542
     * @param agentUuid
543
     * @param relTypeUuid TODO
544
     * @param query
545
     * @param rankId TODO
546
     */
547
    private void setParamsForListTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid, UUID agentUuid,
548
            UUID rankUuid, UUID relTypeUuid, Query query) {
549

    
550
        if(taxonUuid != null) {
551
            query.setParameter("taxonUuid", taxonUuid);
552
        } else {
553
            if(rankUuid != null) {
554
                query.setParameter("rankUuid", rankUuid);
555
            }
556
        }
557
        if(classificationUuid != null) {
558
            query.setParameter("classificationUuid", classificationUuid);
559
        }
560
        if(agentUuid != null) {
561
            query.setParameter("agentUuid", agentUuid);
562
        }
563
        if(relTypeUuid != null) {
564
            query.setParameter("relTypeUuid", relTypeUuid);
565
        }
566
    }
567

    
568
    @Override
569
    public Map<TreeIndex, Integer> rankOrderIndexForTreeIndex(List<TreeIndex> treeIndexes,
570
            Integer minRankOrderIndex,
571
            Integer maxRankOrderIndex) {
572

    
573
        Map<TreeIndex, Integer> result = new HashMap<>();
574
        if (treeIndexes == null || treeIndexes.isEmpty()){
575
            return result;
576
        }
577

    
578
        String hql = " SELECT tn.treeIndex, r.orderIndex "
579
                + " FROM TaxonNode tn "
580
                + "     JOIN tn.taxon t "
581
                + "     JOIN t.name n "
582
                + "      JOIN n.rank r "
583
                + " WHERE tn.treeIndex IN (:treeIndexes) ";
584
        if (minRankOrderIndex != null){
585
            hql += " AND r.orderIndex <= :minOrderIndex";
586
        }
587
        if (maxRankOrderIndex != null){
588
            hql += " AND r.orderIndex >= :maxOrderIndex";
589
        }
590

    
591
        Query query =  getSession().createQuery(hql);
592
        query.setParameterList("treeIndexes", TreeIndex.toString(treeIndexes));
593
        if (minRankOrderIndex != null){
594
            query.setParameter("minOrderIndex", minRankOrderIndex);
595
        }
596
        if (maxRankOrderIndex != null){
597
            query.setParameter("maxOrderIndex", maxRankOrderIndex);
598
        }
599

    
600
        @SuppressWarnings("unchecked")
601
        List<Object[]> list = query.list();
602
        for (Object[] o : list){
603
            result.put(TreeIndex.NewInstance((String)o[0]), (Integer)o[1]);
604
        }
605
        return result;
606
    }
607

    
608
    @Override
609
    public Map<TreeIndex, UuidAndTitleCache<?>> taxonUuidsForTreeIndexes(Collection<TreeIndex> treeIndexes) {
610
        Map<TreeIndex, UuidAndTitleCache<?>> result = new HashMap<>();
611
        if (treeIndexes == null || treeIndexes.isEmpty()){
612
            return result;
613
        }
614

    
615
        String hql =
616
                  " SELECT tn.treeIndex, t.uuid, tnb.titleCache "
617
                + " FROM TaxonNode tn JOIN tn.taxon t Join t.name tnb "
618
                + " WHERE tn.treeIndex IN (:treeIndexes) ";
619
        Query query =  getSession().createQuery(hql);
620
        query.setParameterList("treeIndexes", TreeIndex.toString(treeIndexes));
621

    
622
        @SuppressWarnings("unchecked")
623
        List<Object[]> list = query.list();
624
        for (Object[] o : list){
625
            result.put(TreeIndex.NewInstance((String)o[0]), new UuidAndTitleCache<>((UUID)o[1], null, (String)o[2]));
626
        }
627
        return result;
628
    }
629

    
630
    @Override
631
    public List<TaxonNodeDto> getParentTaxonNodeDtoForRank(
632
            Classification classification, Rank rank, TaxonBase<?> taxonBase) {
633

    
634
        Taxon taxon = null;
635
        if (taxonBase instanceof Taxon) {
636
            taxon = CdmBase.deproxy(taxonBase, Taxon.class);
637
        }else {
638
            taxon = CdmBase.deproxy(((Synonym)taxonBase).getAcceptedTaxon());
639
        }
640
        TaxonNode node = null;
641
        if (taxon != null) {
642
            node = taxon.getTaxonNode(classification);
643
        }
644
        List<TaxonNodeDto> result = new ArrayList<>();
645
        if (node != null) {
646
            String treeIndex = node.treeIndex();
647
            List<Integer> ancestorNodeIds = TreeIndex.NewInstance(treeIndex).parentNodeIds(false);
648

    
649
            Criteria nodeCrit = getSession().createCriteria(TaxonNode.class);
650
            Criteria taxonCrit = nodeCrit.createCriteria("taxon");
651
            Criteria nameCrit = taxonCrit.createCriteria("name");
652
            nodeCrit.add(Restrictions.in("id", ancestorNodeIds));
653
            nodeCrit.add(Restrictions.eq("classification", classification));
654
            nameCrit.add(Restrictions.eq("rank", rank));
655

    
656
            @SuppressWarnings("unchecked")
657
            List<TaxonNode> list = nodeCrit.list();
658
            for (TaxonNode rankNode : list){
659
                TaxonNodeDto dto = new TaxonNodeDto(rankNode);
660
                result.add(dto);
661
            }
662
        }
663
        return result;
664
    }
665

    
666

    
667
    @Override
668
    public List<TaxonNodeDto> getParentTaxonNodeDtoForRank(
669
            Classification classification, Rank rank, TaxonName name) {
670

    
671
    	Set<TaxonBase> taxa = name.getTaxonBases();
672
    	List<TaxonNodeDto> result = new ArrayList<>();
673
    	for (TaxonBase<?> taxonBase:taxa) {
674
    	    List<TaxonNodeDto> tmpList = getParentTaxonNodeDtoForRank(classification, rank, taxonBase);
675
    	    for (TaxonNodeDto tmpDto : tmpList){
676
    	        boolean exists = false; //an equal method does not yet exist for TaxonNodeDto therefore this workaround
677
    	        for (TaxonNodeDto dto: result){
678
    	            if (dto.getTreeIndex().equals(tmpDto.getTreeIndex())){
679
    	                exists = true;
680
    	            }
681
    	        }
682
    	        if (!exists){
683
    	            result.add(tmpDto);
684
    	        }
685
    	    }
686
    	}
687
    	return result;
688
    }
689

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

    
700
    private int countResult(String queryStr) {
701
        Query query = getSession().createQuery(queryStr);
702
        return ((Long)query.uniqueResult()).intValue();
703
    }
704

    
705
    @Override
706
    public int countSecundumForSubtreeSynonyms(TreeIndex subTreeIndex, Reference newSec,
707
            boolean overwriteExisting, boolean includeSharedTaxa, boolean emptySecundumDetail) {
708
        String queryStr = forSubtreeSynonymQueryStr(includeSharedTaxa, subTreeIndex, false, SelectMode.COUNT);
709
        if (!overwriteExisting){
710
            queryStr += " AND syn.secSource.citation IS NULL ";
711
        }
712
        return countResult(queryStr);
713
    }
714

    
715
    @Override
716
    public int countSecundumForSubtreeRelations(TreeIndex subTreeIndex, Reference newSec,
717
            boolean overwriteExisting, boolean includeSharedTaxa, boolean emptySecundumDetail) {
718
        String queryStr = forSubtreeRelationQueryStr(includeSharedTaxa, overwriteExisting, subTreeIndex, SelectMode.COUNT);
719
        return countResult(queryStr);
720
    }
721

    
722
    //#3465
723
    @Override
724
    public Set<CdmBase> setSecundumForSubtreeAcceptedTaxa(TreeIndex subTreeIndex, Reference newSec,
725
            boolean overwriteExisting, boolean includeSharedTaxa, boolean emptyDetail, IProgressMonitor monitor) {
726
        //for some reason this does not work, maybe because the listeners are not activated,
727
        //but also the first taxon for some reason does not get updated in terms of secundum, but only by the update listener
728
//        String where = "SELECT t.id FROM TaxonNode tn JOIN tn.taxon t " +
729
//                " WHERE tn.treeIndex like '%s%%' ORDER BY t.id";
730
//        where = String.format(where, subTreeIndex.toString());
731
//        Query query1 = getSession().createQuery(where);
732
//        List l = query1.list();
733
//
734
//        String hql = "UPDATE Taxon SET sec = :newSec, publish=false WHERE id IN (" + where + ")";
735
//        Query query = getSession().createQuery(hql);
736
//        query.setParameter("newSec", newSec);
737
//        int n = query.executeUpdate();
738

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

    
746
    @Override
747
    public Set<CdmBase> setSecundumForSubtreeSynonyms(TreeIndex subTreeIndex, Reference newSec,
748
            boolean overwriteExisting, boolean includeSharedTaxa, boolean emptyDetail, IProgressMonitor monitor) {
749

    
750
        String queryStr = forSubtreeSynonymQueryStr(includeSharedTaxa, subTreeIndex, false, SelectMode.ID);
751
        if (!overwriteExisting){
752
            queryStr += " AND syn.secSource.citation IS NULL ";
753
        }
754
        return setSecundum(newSec, emptyDetail, queryStr, monitor);
755
    }
756

    
757
    private <T extends TaxonBase<?>> Set<CdmBase> setSecundum(Reference newSec, boolean emptyDetail, String queryStr, IProgressMonitor monitor) {
758
        Set<CdmBase> result = new HashSet<>();
759
        Query query = getSession().createQuery(queryStr);
760
        @SuppressWarnings("unchecked")
761
        List<List<Integer>> partitionList = splitIdList(query.list(), DEFAULT_SET_SUBTREE_PARTITION_SIZE);
762
        for (List<Integer> taxonIdList : partitionList){
763
            @SuppressWarnings({ "unchecked", "rawtypes" })
764
            List<T> taxonList = (List)taxonDao.loadList(taxonIdList, null, null);
765
            for (T taxonBase : taxonList){
766
                if (taxonBase != null){
767
                    taxonBase = CdmBase.deproxy(taxonBase);
768
                    SecundumSource secSourceBefore = taxonBase.getSecSource();
769
                    Reference refBefore = taxonBase.getSec();
770
                    String refDetailBefore = taxonBase.getSecMicroReference();
771
                    if (newSec == null && taxonBase.getSec() !=null
772
                            || newSec != null && (taxonBase.getSec() == null || !newSec.equals(taxonBase.getSec()) )){
773
                        taxonBase.setSec(newSec);
774
                    }
775
                    if (emptyDetail){
776
                        if (taxonBase.getSecMicroReference() != null){
777
                            taxonBase.setSecMicroReference(null);
778
                        }
779
                    }
780
                    //compute updated objects
781
                    SecundumSource secSourceAfter = taxonBase.getSecSource();
782

    
783
                    if (!CdmUtils.nullSafeEqual(secSourceBefore, secSourceAfter)){
784
                        result.add(taxonBase);
785
                        //FIXME #9627 remove if fixed
786
                        result.add(taxonBase);
787
                        //EMXIF
788
                    }else if (secSourceBefore != null && secSourceBefore.equals(secSourceAfter)
789
                            && (!CdmUtils.nullSafeEqual(refBefore, secSourceAfter.getCitation())
790
                                 || !CdmUtils.nullSafeEqual(refDetailBefore, secSourceAfter.getCitationMicroReference()))
791
                            ){
792
                        result.add(secSourceBefore);
793
                        //FIXME #9627 remove if fixed
794
                        result.add(taxonBase);
795
                        //EMXIF
796
                    }
797

    
798
                    monitor.worked(1);
799
                    if (monitor.isCanceled()){
800
                        return result;
801
                    }
802
                }
803
            }
804
            commitAndRestartTransaction(newSec);
805
            monitor.worked(taxonIdList.size());
806
        }
807
        return result;
808
    }
809

    
810
    private void commitAndRestartTransaction(CdmBase... cdmBaseToUpdate) {
811
        getSession().getTransaction().commit();
812
        getSession().clear();
813
        getSession().beginTransaction();
814
        for (CdmBase cdmBase : cdmBaseToUpdate){
815
            if (cdmBase != null){
816
                getSession().update(cdmBase);
817
            }
818
        }
819
    }
820

    
821
    @Override
822
    public Set<CdmBase> setSecundumForSubtreeRelations(TreeIndex subTreeIndex, Reference newRef,
823
            Set<UUID> relationTypes,  boolean overwriteExisting, boolean includeSharedTaxa, boolean emptyDetail, IProgressMonitor monitor) {
824

    
825
        String queryStr = forSubtreeRelationQueryStr(includeSharedTaxa, overwriteExisting, subTreeIndex, SelectMode.ID);
826

    
827
        Set<CdmBase> result = new HashSet<>();
828
        Query query = getSession().createQuery(queryStr);
829
        @SuppressWarnings("unchecked")
830
        List<List<Integer>> partitionList = splitIdList(query.list(), DEFAULT_SET_SUBTREE_PARTITION_SIZE);
831
        for (List<Integer> relIdList : partitionList){
832
            List<TaxonRelationship> relList = taxonRelDao.loadList(relIdList, null, null);
833
            for (TaxonRelationship rel : relList){
834
                if (rel != null){
835
                    rel = CdmBase.deproxy(rel);
836

    
837
                    NamedSource sourceBefore = rel.getSource();
838
                    Reference refBefore = rel.getCitation();
839
                    String refDetailBefore = rel.getCitationMicroReference();
840
                    if (newRef == null && rel.getCitation() !=null
841
                            || newRef != null && (rel.getCitation() == null || !newRef.equals(rel.getCitation()) )){
842
                        rel.setCitation(newRef);
843
                    }
844
                    if (emptyDetail){
845
                        if (rel.getCitationMicroReference() != null){
846
                            rel.setCitationMicroReference(null);
847
                        }
848
                    }
849
                    //compute updated objects
850
                    NamedSource sourceAfter = rel.getSource();
851
                    if (!CdmUtils.nullSafeEqual(sourceBefore, sourceAfter)){
852
                        result.add(rel);
853
                        //FIXME #9627 remove if fixed
854
                        result.add(rel.getToTaxon());
855
                        //EMXIF
856

    
857
                    }else if (sourceBefore != null && sourceBefore.equals(sourceAfter)
858
                            && (!CdmUtils.nullSafeEqual(refBefore, sourceAfter.getCitation())
859
                                 || !CdmUtils.nullSafeEqual(refDetailBefore,sourceAfter.getCitationMicroReference()))
860
                            ){
861
                        result.add(sourceBefore);
862
                        //FIXME #9627 remove if fixed
863
                        result.add(rel.getToTaxon());
864
                        //EMXIF
865
                    }
866

    
867
                    monitor.worked(1);
868
                    if (monitor.isCanceled()){
869
                        return result;
870
                    }
871
                }
872
            }
873
            commitAndRestartTransaction();
874
            monitor.worked(relList.size());
875
        }
876

    
877
        return result;
878
    }
879

    
880
    private List<List<Integer>> splitIdList(List<Integer> idList, Integer size){
881
        List<List<Integer>> result = new ArrayList<>();
882
        for (int i = 0; (i*size)<idList.size(); i++) {
883
            int upper = Math.min((i+1)*size, idList.size());
884
            result.add(idList.subList(i*size, upper));
885
        }
886
        return result;
887
    }
888

    
889
    @Override
890
    public int countPublishForSubtreeAcceptedTaxa(TreeIndex subTreeIndex, boolean publish, boolean includeSharedTaxa, boolean includeHybrids) {
891
        String queryStr = forSubtreeAcceptedQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.COUNT);
892
        queryStr += " AND t.publish != :publish ";
893
        Query query = getSession().createQuery(queryStr);
894
        query.setBoolean("publish", publish);
895
        return ((Long)query.uniqueResult()).intValue();
896
    }
897

    
898
    @Override
899
    public int countPublishForSubtreeSynonyms(TreeIndex subTreeIndex, boolean publish, boolean includeSharedTaxa, boolean includeHybrids) {
900
        String queryStr = forSubtreeSynonymQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.COUNT);
901
        queryStr += " AND syn.publish != :publish ";
902
        Query query = getSession().createQuery(queryStr);
903
        query.setBoolean("publish", publish);
904
        return ((Long)query.uniqueResult()).intValue();
905
    }
906

    
907
    @Override
908
    public Set<TaxonBase> setPublishForSubtreeAcceptedTaxa(TreeIndex subTreeIndex, boolean publish,
909
            boolean includeSharedTaxa, boolean includeHybrids, IProgressMonitor monitor) {
910
        String queryStr = forSubtreeAcceptedQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.ID);
911
        queryStr += " AND t.publish != :publish ";
912
        return setPublish(publish, queryStr, null, monitor);
913
    }
914

    
915
    @Override
916
    public Set<TaxonBase> setPublishForSubtreeSynonyms(TreeIndex subTreeIndex, boolean publish,
917
            boolean includeSharedTaxa, boolean includeHybrids, IProgressMonitor monitor) {
918
        String queryStr = forSubtreeSynonymQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.ID);
919
        queryStr += " AND syn.publish != :publish ";
920
        return setPublish(publish, queryStr, null, monitor);
921
    }
922

    
923
    @Override
924
    public int countPublishForSubtreeRelatedTaxa(TreeIndex subTreeIndex, boolean publish, boolean includeSharedTaxa, boolean includeHybrids) {
925
        String queryStr = forSubtreeRelatedTaxaQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.COUNT);
926
        queryStr += " AND relTax.publish != :publish ";
927
        Query query = getSession().createQuery(queryStr);
928
        query.setBoolean("publish", publish);
929
        return ((Long)query.uniqueResult()).intValue();
930
    }
931

    
932
    @Override
933
    public Set<TaxonBase> setPublishForSubtreeRelatedTaxa(TreeIndex subTreeIndex, boolean publish,
934
            Set<UUID> relationTypes, boolean includeSharedTaxa, boolean includeHybrids,
935
            IProgressMonitor monitor) {
936
        String queryStr = forSubtreeRelatedTaxaQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.ID);
937
        queryStr += " AND relTax.publish != :publish ";
938
        queryStr += " AND rel.type.uuid IN (:relTypeUuid)";
939
        return setPublish(publish, queryStr, relationTypes, monitor);
940
    }
941

    
942
    private <T extends TaxonBase<?>> Set<T> setPublish(boolean publish, String queryStr, Set<UUID> relTypeUuids, IProgressMonitor monitor) {
943
        Set<T> result = new HashSet<>();
944
        Query query = getSession().createQuery(queryStr);
945
        query.setBoolean("publish", publish);
946
        if (relTypeUuids != null && !relTypeUuids.isEmpty()){
947
            query.setParameterList("relTypeUuid", relTypeUuids);
948
        }
949
        @SuppressWarnings("unchecked")
950
        List<List<Integer>> partitionList = splitIdList(query.list(), DEFAULT_SET_SUBTREE_PARTITION_SIZE);
951
        for (List<Integer> taxonIdList : partitionList){
952
            @SuppressWarnings({ "unchecked", "rawtypes" })
953
            List<T> taxonList = (List)taxonDao.loadList(taxonIdList, null, null);
954
            for (T taxonBase : taxonList){
955
                if (taxonBase != null){
956
                    if (taxonBase.isPublish() != publish){  //to be on the save side
957
                        taxonBase.setPublish(publish);
958
                        result.add(CdmBase.deproxy(taxonBase));
959
                    }
960
                    monitor.worked(1);
961
                    if (monitor.isCanceled()){
962
                        return result;
963
                    }
964
                }
965
            }
966
            commitAndRestartTransaction();
967
        }
968
        return result;
969
    }
970

    
971
    private String forSubtreeSynonymQueryStr(boolean includeSharedTaxa, TreeIndex subTreeIndex, boolean excludeHybrids, SelectMode mode) {
972
        String queryStr = "SELECT " + mode.hql("syn")
973
                + " FROM TaxonNode tn "
974
                + "   JOIN tn.taxon t "
975
                + "   JOIN t.synonyms syn  "
976
                + "   LEFT JOIN syn.name n "
977
                + "   LEFT JOIN syn.secSource ss ";
978
        String whereStr = " tn.treeIndex LIKE '%1$s%%' ";
979
        if (!includeSharedTaxa){
980
            whereStr += " AND NOT EXISTS ("
981
                    + "FROM TaxonNode tn2 WHERE tn2.taxon = t AND tn2.treeIndex not like '%1$s%%')  ";
982
        }
983
        whereStr = handleExcludeHybrids(whereStr, excludeHybrids, "syn");
984
        queryStr += " WHERE " + String.format(whereStr, subTreeIndex.toString());
985

    
986
        return queryStr;
987
    }
988

    
989
    private String handleExcludeHybrids(String whereStr, boolean excludeHybrids, String t) {
990
        if(excludeHybrids){
991

    
992
            String hybridWhere =  " AND (n is NULL OR "
993
                    + " (n.monomHybrid=0 AND n.binomHybrid=0 "
994
                    + "   AND n.trinomHybrid=0 AND n.hybridFormula=0 )) ";
995

    
996
            whereStr += hybridWhere; //String.format(hybridWhere, t);
997
        }
998
        return whereStr;
999
    }
1000

    
1001
    private String forSubtreeRelatedTaxaQueryStr(boolean includeSharedTaxa, TreeIndex subTreeIndex,
1002
            boolean excludeHybrids, SelectMode mode) {
1003
        String queryStr = "SELECT " + mode.hql("relTax")
1004
                + " FROM TaxonNode tn "
1005
                + "   JOIN tn.taxon t "
1006
                + "   JOIN t.relationsToThisTaxon rel"
1007
                + "   JOIN rel.relatedFrom relTax "
1008
                + "   LEFT JOIN relTax.name n ";
1009
        String whereStr =" tn.treeIndex LIKE '%1$s%%' ";
1010
        if (!includeSharedTaxa){
1011
            //toTaxon should only be used in the given subtree
1012
            whereStr += " AND NOT EXISTS ("
1013
                    + "FROM TaxonNode tn2 WHERE tn2.taxon = t AND tn2.treeIndex not like '%1$s%%')  ";
1014
            //from taxon should not be used in another classification
1015
            whereStr += " AND NOT EXISTS ("
1016
                    + "FROM TaxonNode tn3 WHERE tn3.taxon = relTax AND tn3.treeIndex not like '%1$s%%')  ";
1017
            //fromTaxon should not be related as e.g. pro parte synonym or misapplication to
1018
            //another taxon which is not part of the subtree
1019
            //TODO and has not the publish state
1020
            whereStr += " AND NOT EXISTS ("
1021
                    + "FROM TaxonNode tn4 JOIN tn4.taxon t2 JOIN t2.relationsToThisTaxon rel2  "
1022
                    + "   WHERE rel2.relatedFrom = relTax AND tn4.treeIndex not like '%1$s%%' "
1023
                    + "         AND tn4.taxon.publish != :publish ) ";
1024
        }
1025
        whereStr = handleExcludeHybrids(whereStr, excludeHybrids, "relTax");
1026
        queryStr += " WHERE " + String.format(whereStr, subTreeIndex.toString());
1027

    
1028
        return queryStr;
1029
    }
1030

    
1031
    /**
1032
     * query for
1033
     */
1034
    private String forSubtreeRelationQueryStr(boolean includeSharedTaxa, boolean overwriteExisting,
1035
            TreeIndex subTreeIndex, SelectMode mode) {
1036

    
1037
        String queryStr = "SELECT " + mode.hql("rel")
1038
                + " FROM TaxonNode tn "
1039
                + "   JOIN tn.taxon t "
1040
                + "   JOIN t.relationsToThisTaxon rel "
1041
                + "   LEFT JOIN rel.source src ";
1042
        String whereStr =" tn.treeIndex LIKE '%1$s%%' ";
1043
        if (!includeSharedTaxa){
1044
            //toTaxon should only be used in the given subtree
1045
            whereStr += " AND NOT EXISTS ("
1046
                    + "FROM TaxonNode tn2 WHERE tn2.taxon = t AND tn2.treeIndex not like '%1$s%%')  ";
1047
        }
1048
        queryStr += " WHERE " + String.format(whereStr, subTreeIndex.toString());
1049
        if (!overwriteExisting){
1050
            queryStr += " AND (rel.source IS NULL OR src.citation IS NULL) ";
1051
        }
1052

    
1053
        return queryStr;
1054
    }
1055

    
1056
    private enum SelectMode{
1057
        COUNT(" count(*) "),
1058
        ID ("id "),
1059
        UUID("uuid "),
1060
        FULL("");
1061
        private String hql;
1062
        SelectMode(String hql){
1063
            this.hql = hql;
1064
        }
1065
        public String hql(String prefix){
1066
            switch (this){
1067
            case ID:
1068
            case UUID:
1069
                return CdmUtils.Nz(prefix)+"." + hql;
1070
            case FULL:
1071
                return CdmUtils.Nz(prefix) + hql;
1072
            case COUNT:
1073
            default: return hql;
1074
            }
1075

    
1076
        }
1077
    }
1078

    
1079
    private String forSubtreeAcceptedQueryStr(boolean includeSharedTaxa, TreeIndex subTreeIndex, boolean excludeHybrids, SelectMode mode) {
1080
        String queryStr = "SELECT " + mode.hql("t")
1081
                + " FROM TaxonNode tn "
1082
                + "   JOIN tn.taxon t "
1083
                + "   LEFT JOIN t.name n "
1084
                + "   LEFT JOIN t.secSource ss ";
1085
        String whereStr = " tn.treeIndex like '%1$s%%' ";
1086
        if (!includeSharedTaxa){
1087
            whereStr += " AND NOT EXISTS ("
1088
                    + "FROM TaxonNode tn2 WHERE tn2.taxon = t AND tn2.treeIndex not like '%1$s%%')  ";
1089
        }
1090
        whereStr = handleExcludeHybrids(whereStr, excludeHybrids, "t");
1091
        queryStr += " WHERE " + String.format(whereStr, subTreeIndex.toString());
1092

    
1093
        return queryStr;
1094
    }
1095

    
1096
    @Override
1097
    public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(Classification classification, Integer limit, String pattern, boolean searchForClassifications, boolean includeDoubtful) {
1098

    
1099
         Query query = createQueryForUuidAndTitleCache(limit, classification.getUuid(), pattern, includeDoubtful);
1100
         @SuppressWarnings("unchecked")
1101
         List<SortableTaxonNodeQueryResult> result = query.list();
1102

    
1103

    
1104
         if (searchForClassifications){
1105
             String queryString = "SELECT new " + SortableTaxonNodeQueryResult.class.getName() + "("
1106
                     + " node.uuid, node.id, node.classification.titleCache, parent.uuid"
1107
                     + ") "
1108
                     + " FROM TaxonNode AS node "
1109
                     + " LEFT OUTER JOIN node.parent as parent"
1110
                     + " WHERE node.classification.id = " + classification.getId() +
1111
                          " AND node.taxon IS NULL";
1112
             if (pattern != null){
1113
                 if (pattern.equals("?")){
1114
                     limit = null;
1115
                 } else{
1116
                     if (!pattern.endsWith("*")){
1117
                         pattern += "%";
1118
                     }
1119
                     pattern = pattern.replace("*", "%");
1120
                     pattern = pattern.replace("?", "%");
1121
                     queryString = queryString + " AND node.classification.titleCache LIKE (:pattern) " ;
1122
                 }
1123
             }
1124
             query = getSession().createQuery(queryString);
1125

    
1126
             if (limit != null){
1127
                 query.setMaxResults(limit);
1128
             }
1129

    
1130
             if (pattern != null && !pattern.equals("?")){
1131
                 query.setParameter("pattern", pattern);
1132
             }
1133
             @SuppressWarnings("unchecked")
1134
             List<SortableTaxonNodeQueryResult> resultClassifications = query.list();
1135

    
1136
             result.addAll(resultClassifications);
1137
         }
1138

    
1139
         if(result.size() == 0){
1140
             return null;
1141
         }else{
1142
             List<UuidAndTitleCache<TaxonNode>> list = new ArrayList<>(result.size());
1143
             Collections.sort(result, new SortableTaxonNodeQueryResultComparator());
1144
             for (SortableTaxonNodeQueryResult resultDTO : result){
1145
                 list.add(new UuidAndTitleCache<>(TaxonNode.class, resultDTO.getTaxonNodeUuid(), resultDTO.getTaxonNodeId(), resultDTO.getTaxonTitleCache()));
1146
             }
1147

    
1148
             return list;
1149
         }
1150
    }
1151

    
1152
    @Override
1153
    public List<TaxonNodeDto> getTaxonNodeDto(Integer limit, String pattern, UUID classificationUuid) {
1154
        String queryString = getTaxonNodeDtoQuery();
1155
        queryString += "   INNER JOIN tn.classification AS cls " + " WHERE t.titleCache LIKE :pattern ";
1156

    
1157
        if(classificationUuid != null){
1158
            queryString += "AND cls.uuid = :classificationUuid";
1159
        }
1160

    
1161
        Query query =  getSession().createQuery(queryString);
1162

    
1163
        query.setParameter("pattern", pattern.toLowerCase()+"%");
1164
        if(classificationUuid != null){
1165
            query.setParameter("classificationUuid", classificationUuid);
1166
        }
1167

    
1168
        @SuppressWarnings("unchecked")
1169
        List<SortableTaxonNodeQueryResult> result = query.list();
1170
        List<TaxonNodeDto> list = createNodeDtos(result);
1171

    
1172
        return list;
1173
    }
1174

    
1175
    @Override
1176
    public TaxonNodeDto getTaxonNodeDto(UUID nodeUuid) {
1177

    
1178
        String queryString = getTaxonNodeDtoQuery();
1179
        queryString += " WHERE tn.uuid = :uuid ";
1180
        Query query =  getSession().createQuery(queryString);
1181
        query.setParameter("uuid", nodeUuid);
1182

    
1183
        @SuppressWarnings("unchecked")
1184
        List<SortableTaxonNodeQueryResult> result = query.list();
1185
        List<TaxonNodeDto> list = createNodeDtos(result);
1186
        return list.get(0);
1187
    }
1188

    
1189
    /**
1190
     * @param nodeUuid
1191
     * @return
1192
     */
1193
    private String getTaxonNodeDtoQuery() {
1194
        String queryString = "SELECT new " + SortableTaxonNodeQueryResult.class.getName() + "("
1195
                + "tn.uuid, tn.id, tn.treeIndex, t.uuid, t.titleCache, name.titleCache, rank, p.uuid "
1196
                + ") "
1197
                + " FROM TaxonNode tn "
1198
                + "   INNER JOIN tn.taxon AS t "
1199
                + "   INNER JOIN tn.parent AS p"
1200
                + "   INNER JOIN t.name AS name "
1201
                + "   LEFT OUTER JOIN name.rank AS rank ";
1202

    
1203
        return queryString;
1204
    }
1205

    
1206

    
1207

    
1208
    @Override
1209
    public List<TaxonNodeDto> getTaxonNodeDtos(List<UUID> nodeUuids) {
1210
        String queryString = getTaxonNodeDtoQuery();
1211
        queryString = queryString + " WHERE tn.uuid IN (:uuid) ";
1212

    
1213
        Query query =  getSession().createQuery(queryString);
1214
        query.setParameterList("uuid", nodeUuids);
1215

    
1216
        @SuppressWarnings("unchecked")
1217
        List<SortableTaxonNodeQueryResult> result = query.list();
1218

    
1219
        List<TaxonNodeDto> list = createNodeDtos(result);
1220

    
1221
        return list;
1222
    }
1223

    
1224
    /**
1225
     * @param result
1226
     * @param list
1227
     */
1228
    @Override
1229
    public List<TaxonNodeDto> createNodeDtos(List<SortableTaxonNodeQueryResult> result) {
1230
        List<TaxonNodeDto> nodeDtos = new ArrayList<>();
1231
        Collections.sort(result, new SortableTaxonNodeQueryResultComparator());
1232
        for(SortableTaxonNodeQueryResult queryDTO : result){
1233
            TaxonNodeDto nodeDto = new TaxonNodeDto(queryDTO.getTaxonNodeUuid(), queryDTO.getTaxonNodeId(), queryDTO.getTreeIndex(), queryDTO.getNameTitleCache(), queryDTO.getTaxonTitleCache(), queryDTO.getNameRank().getOrderIndex(), queryDTO.getParentNodeUuid());
1234
            nodeDtos.add(nodeDto);
1235
        }
1236
        return nodeDtos;
1237
    }
1238

    
1239
    @Override
1240
    public List<TaxonNodeDto> getTaxonNodeForTaxonInClassificationDto(UUID taxonUUID, UUID classificationUuid) {
1241
        String queryString = getTaxonNodeDtoQuery();
1242
        queryString = queryString + "   INNER JOIN tn.classification AS cls "  + " WHERE t.uuid = :uuid ";
1243

    
1244
        if(classificationUuid != null){
1245
            queryString += "AND cls.uuid = :classificationUuid";
1246
        }
1247

    
1248
        Query query =  getSession().createQuery(queryString);
1249
        query.setParameter("uuid", taxonUUID);
1250

    
1251
        if(classificationUuid != null){
1252
            query.setParameter("classificationUuid", classificationUuid);
1253
        }
1254

    
1255
        @SuppressWarnings("unchecked")
1256
        List<SortableTaxonNodeQueryResult> result = query.list();
1257

    
1258
        List<TaxonNodeDto> list = createNodeDtos(result);
1259

    
1260
        return list;
1261
    }
1262

    
1263

    
1264
    @Override
1265
    public List<TaxonNodeDto> getUuidAndTitleCache(Integer limit, String pattern, UUID classificationUuid) {
1266
        return getUuidAndTitleCache(limit, pattern, classificationUuid, false);
1267
    }
1268

    
1269
    @Override
1270
    public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(
1271
            Classification classification, Integer limit, String pattern, boolean searchForClassifications) {
1272
        return getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classification, limit, pattern, searchForClassifications, false);
1273
    }
1274

    
1275
}
(4-4/6)