Project

General

Profile

Download (42.8 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.Reference;
43
import eu.etaxonomy.cdm.model.taxon.Classification;
44
import eu.etaxonomy.cdm.model.taxon.Synonym;
45
import eu.etaxonomy.cdm.model.taxon.Taxon;
46
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
47
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
48
import eu.etaxonomy.cdm.model.taxon.TaxonNodeAgentRelation;
49
import eu.etaxonomy.cdm.persistence.dao.common.Restriction;
50
import eu.etaxonomy.cdm.persistence.dao.hibernate.common.AnnotatableDaoImpl;
51
import eu.etaxonomy.cdm.persistence.dao.taxon.IClassificationDao;
52
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
53
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeDao;
54
import eu.etaxonomy.cdm.persistence.dto.SortableTaxonNodeQueryResult;
55
import eu.etaxonomy.cdm.persistence.dto.SortableTaxonNodeQueryResultComparator;
56
import eu.etaxonomy.cdm.persistence.dto.TaxonNodeDto;
57
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
58
import eu.etaxonomy.cdm.persistence.query.OrderHint;
59

    
60
/**
61
 * @author a.mueller
62
 * @since 16.06.2009
63
 */
64
@Repository
65
@Qualifier("taxonNodeDaoHibernateImpl")
66
public class TaxonNodeDaoHibernateImpl extends AnnotatableDaoImpl<TaxonNode>
67
		implements ITaxonNodeDao {
68

    
69
	private static final Logger logger = Logger.getLogger(TaxonNodeDaoHibernateImpl.class);
70

    
71
	@Autowired
72
	private ITaxonDao taxonDao;
73
	@Autowired
74
	private IClassificationDao classificationDao;
75

    
76
	public TaxonNodeDaoHibernateImpl() {
77
		super(TaxonNode.class);
78
	}
79

    
80
	@Override
81
	public UUID delete(TaxonNode persistentObject, boolean deleteChildren){
82
		Taxon taxon = persistentObject.getTaxon();
83
		taxon = HibernateProxyHelper.deproxy(taxon);
84

    
85
		/*Session session = this.getSession();
86
		Query query = session.createQuery("from TaxonNode t where t.taxon = :taxon");
87
		query.setParameter("taxon", taxon);
88
		List result = query.list();*/
89
		if (taxon != null){
90
		    Hibernate.initialize(taxon);
91
		    Hibernate.initialize(taxon.getTaxonNodes());
92
			Set<TaxonNode> nodes = taxon.getTaxonNodes();
93
			//Hibernate.initialize(taxon.getTaxonNodes());
94
			for (TaxonNode node:nodes) {
95
			    node = HibernateProxyHelper.deproxy(node);
96

    
97
			    if (node.equals(persistentObject)){
98
			        if (node.hasChildNodes()){
99
			            Iterator<TaxonNode> childNodes = node.getChildNodes().iterator();
100
			            TaxonNode childNode;
101
			            List<TaxonNode> listForDeletion = new ArrayList<>();
102
	                    while (childNodes.hasNext()){
103
	                        childNode = childNodes.next();
104
	                        listForDeletion.add(childNode);
105
	                        childNodes.remove();
106

    
107
	                    }
108
	                    for (TaxonNode deleteNode:listForDeletion){
109
	                        delete(deleteNode, deleteChildren);
110
	                    }
111
	                }
112

    
113
			        taxon.removeTaxonNode(node, deleteChildren);
114
			        taxonDao.saveOrUpdate(taxon);
115
    				taxon = HibernateProxyHelper.deproxy(taxonDao.findByUuid(taxon.getUuid()), Taxon.class);
116
    				taxonDao.delete(taxon);
117

    
118
			    }
119
			}
120
		}
121

    
122
		UUID result = super.delete(persistentObject);
123
		return result;
124
	}
125

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

    
143
        return result;
144
	}
145

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

    
160
    @Override
161
    public List<TaxonNodeDto> listChildNodesAsUuidAndTitleCache(TaxonNodeDto parent) {
162
        String queryString =
163
                  " SELECT tn.uuid, tn.id, t.titleCache "
164
                + " FROM TaxonNode tn "
165
                + "    INNER JOIN tn.taxon AS t "
166
                + " WHERE tn.parent.uuid = :parentId";
167

    
168
        Query query =  getSession().createQuery(queryString);
169
        query.setParameter("parentId", parent.getUuid());
170

    
171
        @SuppressWarnings("unchecked")
172
        List<Object[]> result = query.list();
173

    
174
        List<TaxonNodeDto> list = new ArrayList<>();
175
        for(Object[] object : result){
176
            list.add(new TaxonNodeDto((UUID) object[0],(Integer) object[1], (String) object[2]));
177
        }
178
        return list;
179
    }
180

    
181
    @Override
182
    public List<TaxonNodeDto> listChildNodesAsTaxonNodeDto(TaxonNodeDto parent) {
183
        String queryString =
184
                 " SELECT tn "
185
               + " FROM TaxonNode tn "
186
               + "    INNER JOIN tn.taxon AS t "
187
               + " WHERE tn.parent.uuid = :parentId";
188
        Query query =  getSession().createQuery(queryString);
189
        query.setParameter("parentId", parent.getUuid());
190

    
191
        @SuppressWarnings("unchecked")
192
        List<TaxonNode> result = query.list();
193

    
194
        List<TaxonNodeDto> list = new ArrayList<>();
195
        for(TaxonNode object : result){
196
            list.add(new TaxonNodeDto(object));
197
        }
198
        return list;
199
    }
200

    
201
    @Override
202
    public List<TaxonNodeDto> getUuidAndTitleCache(Integer limit, String pattern, UUID classificationUuid, boolean includeDoubtful) {
203

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

    
220
        }
221

    
222
        return list;
223
    }
224

    
225
    private Query createQueryForUuidAndTitleCache(Integer limit, UUID classificationUuid, String pattern, boolean includeDoubtful){
226
        String doubtfulPattern = "";
227
        String queryString = "SELECT new " + SortableTaxonNodeQueryResult.class.getName() + "("
228
                + " node.uuid, node.id, t.titleCache, rank"
229
                + ") "
230
                + " FROM TaxonNode AS node "
231
                + "   JOIN node.taxon as t " // FIXME why not inner join here?
232
                + "   INNER JOIN t.name AS name "
233
                + "   LEFT OUTER JOIN name.rank AS rank "
234
                + " WHERE ";
235

    
236
      if (classificationUuid != null){
237
          queryString = queryString + " node.classification.uuid like :classificationUuid " ;
238
      }
239
      if (pattern != null){
240
          if (pattern.equals("?")){
241
              limit = null;
242
          } else{
243
              if (!pattern.endsWith("*")){
244
                  pattern += "%";
245
              }
246
              pattern = pattern.replace("*", "%");
247
              pattern = pattern.replace("?", "%");
248
              if (classificationUuid != null){
249
                  queryString = queryString + " AND ";
250
              }
251
              queryString = queryString + " (t.titleCache LIKE (:pattern) " ;
252
              doubtfulPattern = "?" + pattern;
253
              if (includeDoubtful){
254
                  queryString = queryString + " OR t.titleCache LIKE (:doubtfulPattern))";
255
              }else{
256
                  queryString = queryString + ")";
257
              }
258
          }
259

    
260
      }
261

    
262
      Query query =  getSession().createQuery(queryString);
263
      if (pattern != null){
264
          query.setParameter("pattern", pattern);
265
      }
266
      if (includeDoubtful){
267
          query.setParameter("doubtfulPattern", doubtfulPattern);
268
      }
269

    
270
      if(classificationUuid != null){
271
          query.setParameter("classificationUuid", classificationUuid);
272
      }
273
      if (limit != null){
274
          query.setMaxResults(limit);
275
      }
276
      return query;
277
    }
278

    
279

    
280

    
281
    @Override
282
    public TaxonNodeDto getParentUuidAndTitleCache(TaxonNodeDto child) {
283
        String queryString = ""
284
                + " SELECT tn.parent.uuid, tn.parent.id, tn.parent.taxon.titleCache, "
285
                + "                  tn.parent.classification.titleCache "
286
                + " FROM TaxonNode tn"
287
                + "    LEFT OUTER JOIN tn.parent.taxon"
288
                + " WHERE tn.id = :childId";
289
        Query query =  getSession().createQuery(queryString);
290
        query.setParameter("childId", child.getId());
291
        List<TaxonNodeDto> list = new ArrayList<>();
292

    
293
        @SuppressWarnings("unchecked")
294
        List<Object[]> result = query.list();
295

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

    
319
    private List<TaxonNode> listChildrenOfRecursive(TaxonNode node, List<TaxonNode> previousResult, Integer pageSize, Integer pageIndex,
320
            boolean recursive, boolean includeUnpublished, List<String> propertyPaths, Comparator<TaxonNode> comparator){
321

    
322
        if (!previousResult.contains(node)){
323
            previousResult.add(node);
324
        }
325
        if (recursive == true && comparator == null ){
326
    		Criteria crit = childrenOfCriteria(node, includeUnpublished);
327

    
328
    		this.addPageSizeAndNumber(crit, pageSize, pageIndex);
329
    		@SuppressWarnings("unchecked")
330
            List<TaxonNode> results = crit.list();
331
    		results.remove(node);
332
    		defaultBeanInitializer.initializeAll(results, propertyPaths);
333
    		return results;
334

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

    
349
        } else{
350
    		return classificationDao.listChildrenOf(node.getTaxon(), node.getClassification(), null,
351
    		       includeUnpublished, pageSize, pageIndex, propertyPaths);
352
    	}
353
    }
354

    
355
    @Override
356
	public Long countChildrenOf(TaxonNode node, Classification classification,
357
			boolean recursive, boolean includeUnpublished) {
358

    
359
		if (recursive == true){
360
			Criteria crit = childrenOfCriteria(node, includeUnpublished);
361
    		crit.setProjection(Projections.rowCount());
362
    		return ((Integer)crit.uniqueResult().hashCode()).longValue();
363
		}else{
364
			return classificationDao.countChildrenOf(
365
			        node.getTaxon(), classification, null, includeUnpublished);
366
		}
367
	}
368

    
369
    private Criteria childrenOfCriteria(TaxonNode node, boolean includeUnpublished) {
370
        Criteria crit = getSession().createCriteria(TaxonNode.class);
371
        crit.add( Restrictions.like("treeIndex", node.treeIndex()+ "%") );
372
        if (!includeUnpublished){
373
            crit.createCriteria("taxon").add( Restrictions.eq("publish", Boolean.TRUE));
374
        }
375
        return crit;
376
    }
377

    
378
    @Override
379
    public List<TaxonNodeAgentRelation> listTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid,
380
            UUID agentUuid, UUID rankUuid, UUID relTypeUuid, Integer start, Integer limit,
381
            List<String> propertyPaths) {
382

    
383
        StringBuilder hql = prepareListTaxonNodeAgentRelations(taxonUuid, classificationUuid,
384
                agentUuid, rankUuid, relTypeUuid, false);
385

    
386
        Query query =  getSession().createQuery(hql.toString());
387

    
388
        if(limit != null) {
389
            query.setMaxResults(limit);
390
            if(start != null) {
391
                query.setFirstResult(start);
392
            }
393
        }
394

    
395
        setParamsForListTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid, query);
396

    
397
        @SuppressWarnings("unchecked")
398
        List<TaxonNodeAgentRelation> records = query.list();
399

    
400
        if(propertyPaths != null) {
401
            defaultBeanInitializer.initializeAll(records, propertyPaths);
402
        }
403
        return records;
404
    }
405

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

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

    
417
        Criteria criteria = createCriteria(type, restrictions, false);
418

    
419
        if(!includePublished){
420
            criteria.add(Restrictions.eq("taxon.publish", true));
421
        }
422

    
423
        addLimitAndStart(criteria, limit, start);
424
        addOrder(criteria, orderHints);
425

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

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

    
437

    
438
    @Override
439
    public long count(Class<? extends TaxonNode> type, List<Restriction<?>> restrictions, boolean includePublished) {
440

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

    
449
    @Override
450
    public long countTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid, UUID agentUuid, UUID rankUuid, UUID relTypeUuid) {
451

    
452
        StringBuilder hql = prepareListTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid, true);
453
        Query query =  getSession().createQuery(hql.toString());
454

    
455
        setParamsForListTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid, query);
456

    
457
        Long count = Long.parseLong(query.uniqueResult().toString());
458

    
459
        return count;
460
    }
461

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

    
474
        StringBuilder hql = new StringBuilder();
475

    
476
        String join_fetch_mode = doCount ? "JOIN" : "JOIN FETCH";
477

    
478
        if(doCount) {
479
            hql.append("SELECT COUNT(tnar)");
480
        } else {
481
            hql.append("SELECT tnar");
482
        }
483

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

    
506
        hql.append(" WHERE (1 = 1) ");
507

    
508
        if(relTypeUuid != null) {
509
            hql.append(" AND tnar.type.uuid = :relTypeUuid ");
510
        }
511

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

    
526
        hql.append(" ORDER BY a.titleCache");
527
        return hql;
528
    }
529

    
530
    /**
531
     * @param taxonUuid
532
     * @param classificationUuid
533
     * @param agentUuid
534
     * @param relTypeUuid TODO
535
     * @param query
536
     * @param rankId TODO
537
     */
538
    private void setParamsForListTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid, UUID agentUuid,
539
            UUID rankUuid, UUID relTypeUuid, Query query) {
540

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

    
559
    @Override
560
    public Map<TreeIndex, Integer> rankOrderIndexForTreeIndex(List<TreeIndex> treeIndexes,
561
            Integer minRankOrderIndex,
562
            Integer maxRankOrderIndex) {
563

    
564
        Map<TreeIndex, Integer> result = new HashMap<>();
565
        if (treeIndexes == null || treeIndexes.isEmpty()){
566
            return result;
567
        }
568

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

    
582
        Query query =  getSession().createQuery(hql);
583
        query.setParameterList("treeIndexes", TreeIndex.toString(treeIndexes));
584
        if (minRankOrderIndex != null){
585
            query.setParameter("minOrderIndex", minRankOrderIndex);
586
        }
587
        if (maxRankOrderIndex != null){
588
            query.setParameter("maxOrderIndex", maxRankOrderIndex);
589
        }
590

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

    
599
    @Override
600
    public Map<TreeIndex, UuidAndTitleCache<?>> taxonUuidsForTreeIndexes(Collection<TreeIndex> treeIndexes) {
601
        Map<TreeIndex, UuidAndTitleCache<?>> result = new HashMap<>();
602
        if (treeIndexes == null || treeIndexes.isEmpty()){
603
            return result;
604
        }
605

    
606
        String hql =
607
                  " SELECT tn.treeIndex, t.uuid, tnb.titleCache "
608
                + " FROM TaxonNode tn JOIN tn.taxon t Join t.name tnb "
609
                + " WHERE tn.treeIndex IN (:treeIndexes) ";
610
        Query query =  getSession().createQuery(hql);
611
        query.setParameterList("treeIndexes", TreeIndex.toString(treeIndexes));
612

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

    
621
    @Override
622
    public List<TaxonNodeDto> getParentTaxonNodeDtoForRank(
623
            Classification classification, Rank rank, TaxonBase<?> taxonBase) {
624

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

    
640
            Criteria nodeCrit = getSession().createCriteria(TaxonNode.class);
641
            Criteria taxonCrit = nodeCrit.createCriteria("taxon");
642
            Criteria nameCrit = taxonCrit.createCriteria("name");
643
            nodeCrit.add(Restrictions.in("id", ancestorNodeIds));
644
            nodeCrit.add(Restrictions.eq("classification", classification));
645
            nameCrit.add(Restrictions.eq("rank", rank));
646

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

    
657

    
658
    @Override
659
    public List<TaxonNodeDto> getParentTaxonNodeDtoForRank(
660
            Classification classification, Rank rank, TaxonName name) {
661

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

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

    
691
    private int countResult(String queryStr) {
692
        Query query = getSession().createQuery(queryStr);
693
        return ((Long)query.uniqueResult()).intValue();
694
    }
695

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

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

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

    
729
    }
730

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

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

    
742
    @SuppressWarnings("unchecked")
743
    private <T extends TaxonBase<?>> Set<T> setSecundum(Reference newSec, boolean emptyDetail, String queryStr, IProgressMonitor monitor) {
744
        Set<T> result = new HashSet<>();
745
        Query query = getSession().createQuery(queryStr);
746
        List<List<Integer>> partitionList = splitIdList(query.list(), DEFAULT_PARTITION_SIZE);
747
        for (List<Integer> taxonIdList : partitionList){
748
            List<TaxonBase> taxonList = taxonDao.loadList(taxonIdList, null, null);
749
            for (TaxonBase<?> taxonBase : taxonList){
750
                if (taxonBase != null){
751
                    taxonBase = taxonDao.load(taxonBase.getUuid());
752
                    taxonBase.setSec(newSec);
753
                    if (emptyDetail){
754
                        taxonBase.setSecMicroReference(null);
755
                    }
756
                    result.add((T)CdmBase.deproxy(taxonBase));
757
                    monitor.worked(1);
758
                    if (monitor.isCanceled()){
759
                        return result;
760
                    }
761
                }
762
            }
763
        }
764
        return result;
765
    }
766

    
767
    private List<List<Integer>> splitIdList(List<Integer> idList, Integer size){
768
        List<List<Integer>> result = new ArrayList<>();
769
        for (int i = 0; (i*size)<idList.size(); i++) {
770
            int upper = Math.min((i+1)*size, idList.size());
771
            result.add(idList.subList(i*size, upper));
772
        }
773
        return result;
774
    }
775

    
776
    @Override
777
    public int countPublishForSubtreeAcceptedTaxa(TreeIndex subTreeIndex, boolean publish, boolean includeSharedTaxa, boolean includeHybrids) {
778
        String queryStr = forSubtreeAcceptedQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.COUNT);
779
        queryStr += " AND t.publish != :publish ";
780
        System.out.println(queryStr);
781
        Query query = getSession().createQuery(queryStr);
782
        query.setBoolean("publish", publish);
783
        return ((Long)query.uniqueResult()).intValue();
784
    }
785

    
786
    @Override
787
    public int countPublishForSubtreeSynonyms(TreeIndex subTreeIndex, boolean publish, boolean includeSharedTaxa, boolean includeHybrids) {
788
        String queryStr = forSubtreeSynonymQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.COUNT);
789
        queryStr += " AND syn.publish != :publish ";
790
        Query query = getSession().createQuery(queryStr);
791
        query.setBoolean("publish", publish);
792
        return ((Long)query.uniqueResult()).intValue();
793
    }
794

    
795
    @Override
796
    public Set<TaxonBase> setPublishForSubtreeAcceptedTaxa(TreeIndex subTreeIndex, boolean publish,
797
            boolean includeSharedTaxa, boolean includeHybrids, IProgressMonitor monitor) {
798
        String queryStr = forSubtreeAcceptedQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.ID);
799
        queryStr += " AND t.publish != :publish ";
800
        return setPublish(publish, queryStr, null, monitor);
801
    }
802

    
803
    @Override
804
    public Set<TaxonBase> setPublishForSubtreeSynonyms(TreeIndex subTreeIndex, boolean publish,
805
            boolean includeSharedTaxa, boolean includeHybrids, IProgressMonitor monitor) {
806
        String queryStr = forSubtreeSynonymQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.ID);
807
        queryStr += " AND syn.publish != :publish ";
808
        return setPublish(publish, queryStr, null, monitor);
809
    }
810

    
811
    @Override
812
    public int countPublishForSubtreeRelatedTaxa(TreeIndex subTreeIndex, boolean publish, boolean includeSharedTaxa, boolean includeHybrids) {
813
        String queryStr = forSubtreeRelatedTaxaQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.COUNT);
814
        queryStr += " AND relTax.publish != :publish ";
815
        Query query = getSession().createQuery(queryStr);
816
        query.setBoolean("publish", publish);
817
        return ((Long)query.uniqueResult()).intValue();
818
    }
819

    
820
    @Override
821
    public Set<TaxonBase> setPublishForSubtreeRelatedTaxa(TreeIndex subTreeIndex, boolean publish,
822
            Set<UUID> relationTypes, boolean includeSharedTaxa, boolean includeHybrids,
823
            IProgressMonitor monitor) {
824
        String queryStr = forSubtreeRelatedTaxaQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.ID);
825
        queryStr += " AND relTax.publish != :publish ";
826
        queryStr += " AND rel.type.uuid IN (:relTypeUuid)";
827
        return setPublish(publish, queryStr, relationTypes, monitor);
828
    }
829

    
830
    private static final int DEFAULT_PARTITION_SIZE = 100;
831

    
832
    private <T extends TaxonBase<?>> Set<T> setPublish(boolean publish, String queryStr, Set<UUID> relTypeUuids, IProgressMonitor monitor) {
833
        Set<T> result = new HashSet<>();
834
        Query query = getSession().createQuery(queryStr);
835
        query.setBoolean("publish", publish);
836
        if (relTypeUuids != null && !relTypeUuids.isEmpty()){
837
            query.setParameterList("relTypeUuid", relTypeUuids);
838
        }
839
        @SuppressWarnings("unchecked")
840
        List<List<Integer>> partitionList = splitIdList(query.list(), DEFAULT_PARTITION_SIZE);
841
        for (List<Integer> taxonIdList : partitionList){
842
            List<TaxonBase> taxonList = taxonDao.loadList(taxonIdList, null, null);
843
            for (TaxonBase<?> taxonBase : taxonList){
844
                if (taxonBase != null){
845
                    taxonBase.setPublish(publish);
846
                    result.add((T)CdmBase.deproxy(taxonBase));
847
                    monitor.worked(1);
848
                    if (monitor.isCanceled()){
849
                        return result;
850
                    }
851
                }
852
            }
853
        }
854
        return result;
855
    }
856

    
857
    private String forSubtreeSynonymQueryStr(boolean includeSharedTaxa, TreeIndex subTreeIndex, boolean excludeHybrids, SelectMode mode) {
858
        String queryStr = "SELECT " + mode.hql("syn")
859
                + " FROM TaxonNode tn "
860
                + "   JOIN tn.taxon t "
861
                + "   JOIN t.synonyms syn  LEFT JOIN syn.name n ";
862
        String whereStr = " tn.treeIndex LIKE '%1$s%%' ";
863
        if (!includeSharedTaxa){
864
            whereStr += " AND NOT EXISTS ("
865
                    + "FROM TaxonNode tn2 WHERE tn2.taxon = t AND tn2.treeIndex not like '%1$s%%')  ";
866
        }
867
        whereStr = handleExcludeHybrids(whereStr, excludeHybrids, "syn");
868
        queryStr += " WHERE " + String.format(whereStr, subTreeIndex.toString());
869

    
870
        return queryStr;
871
    }
872

    
873
    private String handleExcludeHybrids(String whereStr, boolean excludeHybrids, String t) {
874
        if(excludeHybrids){
875

    
876
            String hybridWhere =  " AND (n is NULL OR "
877
                    + " (n.monomHybrid=0 AND n.binomHybrid=0 "
878
                    + "   AND n.trinomHybrid=0 AND n.hybridFormula=0 )) ";
879

    
880
            whereStr += hybridWhere; //String.format(hybridWhere, t);
881
        }
882
        return whereStr;
883
    }
884

    
885
    private String forSubtreeRelatedTaxaQueryStr(boolean includeSharedTaxa, TreeIndex subTreeIndex,
886
            boolean excludeHybrids, SelectMode mode) {
887
        String queryStr = "SELECT " + mode.hql("relTax")
888
                + " FROM TaxonNode tn "
889
                + "   JOIN tn.taxon t "
890
                + "   JOIN t.relationsToThisTaxon rel"
891
                + "   JOIN rel.relatedFrom relTax  LEFT JOIN relTax.name n ";
892
        String whereStr =" tn.treeIndex LIKE '%1$s%%' ";
893
        if (!includeSharedTaxa){
894
            //toTaxon should only be used in the given subtree
895
            whereStr += " AND NOT EXISTS ("
896
                    + "FROM TaxonNode tn2 WHERE tn2.taxon = t AND tn2.treeIndex not like '%1$s%%')  ";
897
            //from taxon should not be used in another classification
898
            whereStr += " AND NOT EXISTS ("
899
                    + "FROM TaxonNode tn3 WHERE tn3.taxon = relTax AND tn3.treeIndex not like '%1$s%%')  ";
900
            //fromTaxon should not be related as e.g. pro parte synonym or misapplication to
901
            //another taxon which is not part of the subtree
902
            //TODO and has not the publish state
903
            whereStr += " AND NOT EXISTS ("
904
                    + "FROM TaxonNode tn4 JOIN tn4.taxon t2 JOIN t2.relationsToThisTaxon rel2  "
905
                    + "   WHERE rel2.relatedFrom = relTax AND tn4.treeIndex not like '%1$s%%' "
906
                    + "         AND tn4.taxon.publish != :publish ) ";
907
        }
908
        whereStr = handleExcludeHybrids(whereStr, excludeHybrids, "relTax");
909
        queryStr += " WHERE " + String.format(whereStr, subTreeIndex.toString());
910

    
911
        return queryStr;
912
    }
913

    
914
    private enum SelectMode{
915
        COUNT(" count(*) "),
916
        ID ("id "),
917
        UUID("uuid "),
918
        FULL("");
919
        private String hql;
920
        SelectMode(String hql){
921
            this.hql = hql;
922
        }
923
        public String hql(String prefix){
924
            switch (this){
925
            case ID:
926
            case UUID:
927
                return CdmUtils.Nz(prefix)+"." + hql;
928
            case FULL:
929
                return CdmUtils.Nz(prefix) + hql;
930
            case COUNT:
931
            default: return hql;
932
            }
933

    
934
        }
935
    }
936

    
937
    private String forSubtreeAcceptedQueryStr(boolean includeSharedTaxa, TreeIndex subTreeIndex, boolean excludeHybrids, SelectMode mode) {
938
        String queryStr = "SELECT " + mode.hql("t")
939
                + " FROM TaxonNode tn JOIN tn.taxon t LEFT JOIN t.name n ";
940
        String whereStr = " tn.treeIndex like '%1$s%%' ";
941
        if (!includeSharedTaxa){
942
            whereStr += " AND NOT EXISTS ("
943
                    + "FROM TaxonNode tn2 WHERE tn2.taxon = t AND tn2.treeIndex not like '%1$s%%')  ";
944
        }
945
        whereStr = handleExcludeHybrids(whereStr, excludeHybrids, "t");
946
        queryStr += " WHERE " + String.format(whereStr, subTreeIndex.toString());
947

    
948
        return queryStr;
949
    }
950

    
951
    @Override
952
    public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(Classification classification, Integer limit, String pattern, boolean searchForClassifications, boolean includeDoubtful) {
953

    
954
         Query query = createQueryForUuidAndTitleCache(limit, classification.getUuid(), pattern, includeDoubtful);
955
         @SuppressWarnings("unchecked")
956
         List<SortableTaxonNodeQueryResult> result = query.list();
957

    
958

    
959
         if (searchForClassifications){
960
             String queryString = "SELECT new " + SortableTaxonNodeQueryResult.class.getName() + "("
961
                     + " node.uuid, node.id, node.classification.titleCache"
962
                     + ") "
963
                     + " FROM TaxonNode AS node "
964
                     + " WHERE node.classification.id = " + classification.getId() +
965
                          " AND node.taxon IS NULL";
966
             if (pattern != null){
967
                 if (pattern.equals("?")){
968
                     limit = null;
969
                 } else{
970
                     if (!pattern.endsWith("*")){
971
                         pattern += "%";
972
                     }
973
                     pattern = pattern.replace("*", "%");
974
                     pattern = pattern.replace("?", "%");
975
                     queryString = queryString + " AND node.classification.titleCache LIKE (:pattern) " ;
976
                 }
977
             }
978
             query = getSession().createQuery(queryString);
979

    
980
             if (limit != null){
981
                 query.setMaxResults(limit);
982
             }
983

    
984
             if (pattern != null && !pattern.equals("?")){
985
                 query.setParameter("pattern", pattern);
986
             }
987
             @SuppressWarnings("unchecked")
988
             List<SortableTaxonNodeQueryResult> resultClassifications = query.list();
989

    
990
             result.addAll(resultClassifications);
991
         }
992

    
993
         if(result.size() == 0){
994
             return null;
995
         }else{
996
             List<UuidAndTitleCache<TaxonNode>> list = new ArrayList<>(result.size());
997
             Collections.sort(result, new SortableTaxonNodeQueryResultComparator());
998
             for (SortableTaxonNodeQueryResult resultDTO : result){
999
                 list.add(new UuidAndTitleCache<>(TaxonNode.class, resultDTO.getTaxonNodeUuid(), resultDTO.getTaxonNodeId(), resultDTO.getTaxonTitleCache()));
1000
             }
1001

    
1002
             return list;
1003
         }
1004
    }
1005

    
1006
    @Override
1007
    public List<TaxonNodeDto> getTaxonNodeDto(Integer limit, String pattern, UUID classificationUuid) {
1008
        String queryString = "SELECT new " + SortableTaxonNodeQueryResult.class.getName() + "("
1009
                + "tn.uuid, tn.id, t.titleCache, rank "
1010
                + ") "
1011
                + " FROM TaxonNode tn "
1012
                + "   INNER JOIN tn.taxon AS t "
1013
                + "   INNER JOIN tn.classification AS cls "
1014
                + "   INNER JOIN t.name AS name "
1015
                + "   LEFT OUTER JOIN name.rank AS rank "
1016
                + " WHERE t.titleCache LIKE :pattern ";
1017
        if(classificationUuid != null){
1018
            queryString += "AND cls.uuid = :classificationUuid";
1019
        }
1020

    
1021
        Query query =  getSession().createQuery(queryString);
1022

    
1023
        query.setParameter("pattern", pattern.toLowerCase()+"%");
1024
        if(classificationUuid != null){
1025
            query.setParameter("classificationUuid", classificationUuid);
1026
        }
1027

    
1028
        @SuppressWarnings("unchecked")
1029
        List<SortableTaxonNodeQueryResult> result = query.list();
1030
        Collections.sort(result, new SortableTaxonNodeQueryResultComparator());
1031

    
1032
        List<TaxonNodeDto> list = new ArrayList<>();
1033
        for(SortableTaxonNodeQueryResult queryDTO : result){
1034
            list.add(new TaxonNodeDto(queryDTO.getTaxonNodeUuid(), queryDTO.getTaxonNodeId(), queryDTO.getTaxonTitleCache()));
1035
        }
1036
        return list;
1037
    }
1038

    
1039
    @Override
1040
    public List<TaxonNodeDto> getUuidAndTitleCache(Integer limit, String pattern, UUID classificationUuid) {
1041
        return getUuidAndTitleCache(limit, pattern, classificationUuid, false);
1042
    }
1043

    
1044
    @Override
1045
    public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(
1046
            Classification classification, Integer limit, String pattern, boolean searchForClassifications) {
1047
        return getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classification, limit, pattern, searchForClassifications, false);
1048
    }
1049

    
1050
}
(4-4/5)