Project

General

Profile

Download (82.4 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
 * Copyright (C) 2007 EDIT
3
 * European Distributed Institute of Taxonomy
4
 * http://www.e-taxonomy.eu
5
 *
6
 * The contents of this file are subject to the Mozilla Public License Version 1.1
7
 * See LICENSE.TXT at the top of this package for the full license terms.
8
 */
9
package eu.etaxonomy.cdm.persistence.dao.hibernate.taxon;
10

    
11
import java.util.ArrayList;
12
import java.util.Collection;
13
import java.util.Collections;
14
import java.util.Comparator;
15
import java.util.HashMap;
16
import java.util.HashSet;
17
import java.util.List;
18
import java.util.Map;
19
import java.util.Set;
20
import java.util.SortedSet;
21
import java.util.TreeSet;
22
import java.util.UUID;
23
import java.util.stream.Collectors;
24

    
25
import org.apache.commons.lang.StringUtils;
26
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
27
import org.hibernate.Criteria;
28
import org.hibernate.FetchMode;
29
import org.hibernate.Hibernate;
30
import org.hibernate.Session;
31
import org.hibernate.criterion.Criterion;
32
import org.hibernate.criterion.Order;
33
import org.hibernate.criterion.Projections;
34
import org.hibernate.criterion.Restrictions;
35
import org.hibernate.envers.query.AuditEntity;
36
import org.hibernate.envers.query.AuditQuery;
37
import org.hibernate.envers.query.criteria.internal.NotNullAuditExpression;
38
import org.hibernate.envers.query.internal.property.EntityPropertyName;
39
import org.hibernate.query.Query;
40
import org.hibernate.search.FullTextSession;
41
import org.hibernate.search.Search;
42
import org.springframework.beans.factory.annotation.Autowired;
43
import org.springframework.beans.factory.annotation.Qualifier;
44
import org.springframework.dao.DataAccessException;
45
import org.springframework.stereotype.Repository;
46

    
47
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
48
import eu.etaxonomy.cdm.model.common.LSID;
49
import eu.etaxonomy.cdm.model.common.MarkerType;
50
import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
51
import eu.etaxonomy.cdm.model.location.NamedArea;
52
import eu.etaxonomy.cdm.model.name.Rank;
53
import eu.etaxonomy.cdm.model.name.TaxonName;
54
import eu.etaxonomy.cdm.model.reference.Reference;
55
import eu.etaxonomy.cdm.model.taxon.Classification;
56
import eu.etaxonomy.cdm.model.taxon.Synonym;
57
import eu.etaxonomy.cdm.model.taxon.SynonymType;
58
import eu.etaxonomy.cdm.model.taxon.Taxon;
59
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
60
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
61
import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
62
import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
63
import eu.etaxonomy.cdm.model.term.DefinedTerm;
64
import eu.etaxonomy.cdm.model.view.AuditEvent;
65
import eu.etaxonomy.cdm.persistence.dao.common.Restriction;
66
import eu.etaxonomy.cdm.persistence.dao.hibernate.common.IdentifiableDaoBase;
67
import eu.etaxonomy.cdm.persistence.dao.name.ITaxonNameDao;
68
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
69
import eu.etaxonomy.cdm.persistence.dto.SortableTaxonNodeQueryResult;
70
import eu.etaxonomy.cdm.persistence.dto.SortableTaxonNodeQueryResultComparator;
71
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
72
import eu.etaxonomy.cdm.persistence.query.MatchMode;
73
import eu.etaxonomy.cdm.persistence.query.NameSearchOrder;
74
import eu.etaxonomy.cdm.persistence.query.OrderHint;
75
import eu.etaxonomy.cdm.persistence.query.TaxonTitleType;
76

    
77
/**
78
 * @author a.mueller
79
 * @since 24.11.2008
80
 */
81
@Repository
82
@Qualifier("taxonDaoHibernateImpl")
83
public class TaxonDaoHibernateImpl
84
              extends IdentifiableDaoBase<TaxonBase>
85
              implements ITaxonDao {
86
//    private AlternativeSpellingSuggestionParser<TaxonBase> alternativeSpellingSuggestionParser;
87
    private static final Logger logger = LogManager.getLogger(TaxonDaoHibernateImpl.class);
88

    
89
    public TaxonDaoHibernateImpl() {
90
        super(TaxonBase.class);
91
        indexedClasses = new Class[2];
92
        indexedClasses[0] = Taxon.class;
93
        indexedClasses[1] = Synonym.class;
94
        super.defaultField = "name.titleCache_tokenized";
95
    }
96

    
97
    @Autowired
98
    private ITaxonNameDao taxonNameDao;
99

    
100
////    spelling support currently disabled in appcontext, see spelling.xml ... "
101
////    @Autowired(required = false)   //TODO switched of because it caused problems when starting CdmApplicationController
102
//    public void setAlternativeSpellingSuggestionParser(AlternativeSpellingSuggestionParser<TaxonBase> alternativeSpellingSuggestionParser) {
103
//        this.alternativeSpellingSuggestionParser = alternativeSpellingSuggestionParser;
104
//    }
105

    
106
    @Override
107
    public TaxonBase load(UUID uuid, List<String> propertyPaths){
108
        return load(uuid, INCLUDE_UNPUBLISHED, propertyPaths);
109
    }
110

    
111
    @Override
112
    public TaxonBase load(UUID uuid, boolean includeUnpublished, List<String> propertyPaths){
113
        TaxonBase<?> result = super.load(uuid, includeUnpublished, propertyPaths);
114
        return result; //(result == null || (!result.isPublish() && !includeUnpublished))? null : result;
115
    }
116

    
117
    @Override
118
    public <S extends TaxonBase> List<S> list(Class<S> type, List<Restriction<?>> restrictions, Integer limit,
119
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
120
        return list(type, restrictions, limit, start, orderHints, propertyPaths, INCLUDE_UNPUBLISHED);
121
    }
122

    
123

    
124
    @Override
125
    public <S extends TaxonBase> List<S> list(Class<S> type, List<Restriction<?>> restrictions, Integer limit, Integer start,
126
            List<OrderHint> orderHints, List<String> propertyPaths, boolean includePublished) {
127

    
128
        Criteria criteria = createCriteria(type, restrictions, false);
129

    
130
        if(!includePublished){
131
            criteria.add(Restrictions.eq("publish", true));
132
        }
133

    
134
        addLimitAndStart(criteria, limit, start);
135
        addOrder(criteria, orderHints);
136

    
137
        @SuppressWarnings("unchecked")
138
        List<S> result = criteria.list();
139
        defaultBeanInitializer.initializeAll(result, propertyPaths);
140
        return result;
141
    }
142

    
143
    @Override
144
    public long count(Class<? extends TaxonBase> type, List<Restriction<?>> restrictions) {
145
        return count(type, restrictions, INCLUDE_UNPUBLISHED);
146
    }
147

    
148
    @Override
149
    public long count(Class<? extends TaxonBase> type, List<Restriction<?>> restrictions, boolean includePublished) {
150

    
151
        Criteria criteria = createCriteria(type, restrictions, false);
152
        if(!includePublished){
153
            criteria.add(Restrictions.eq("publish", true));
154
        }
155
        criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
156
        return (Long) criteria.uniqueResult();
157
    }
158

    
159
    @Override
160
    public List<TaxonBase> getTaxaByName(String queryString, boolean includeUnpublished, Reference sec) {
161

    
162
        return getTaxaByName(queryString, true, includeUnpublished, sec);
163
    }
164

    
165
    @Override
166
    public List<TaxonBase> getTaxaByName(String queryString, Boolean accepted, boolean includeUnpublished, Reference sec) {
167
        checkNotInPriorView("TaxonDaoHibernateImpl.getTaxaByName(String name, Reference sec)");
168

    
169
        Criteria criteria = null;
170
        if (accepted == true) {
171
            criteria = getSession().createCriteria(Taxon.class);
172
        } else {
173
            criteria = getSession().createCriteria(Synonym.class);
174
        }
175

    
176
        criteria.setFetchMode( "name", FetchMode.JOIN );
177
        criteria.createAlias("name", "name");
178

    
179
        if (!includeUnpublished){
180
            criteria.add(Restrictions.eq("publish", Boolean.TRUE ));
181
        }
182

    
183
        if (sec != null && sec.getId() != 0) {
184
            criteria.createCriteria("secSource").add(Restrictions.eq("citation", sec ) );
185
        }
186

    
187
        if (queryString != null) {
188
            criteria.add(Restrictions.ilike("name.nameCache", queryString));
189
        }
190

    
191
        @SuppressWarnings({ "unchecked", "rawtypes" })
192
        List<TaxonBase> result = criteria.list();
193
        return result;
194
    }
195

    
196
    //TODO needed? Currently only used by tests.
197
    public List<TaxonBase> getTaxaByName(boolean doTaxa, boolean doSynonyms, boolean includeUnpublished,
198
            String queryString, MatchMode matchMode, Integer pageSize, Integer pageNumber) {
199
        return getTaxaByName(doTaxa, doSynonyms, false, false, false,
200
                queryString, null, null, matchMode, null, includeUnpublished, null, pageSize, pageNumber, null);
201
    }
202

    
203
    @Override
204
    public List<TaxonBase> getTaxaByName(String queryString, MatchMode matchMode,
205
            Boolean accepted, boolean includeUnpublished, Integer pageSize, Integer pageNumber) {
206

    
207
        boolean doTaxa = true;
208
        boolean doSynonyms = true;
209

    
210
        if (accepted == true) {
211
            doSynonyms = false;
212
        } else {
213
           doTaxa = false;
214
        }
215
        return getTaxaByName(doTaxa, doSynonyms, includeUnpublished, queryString, matchMode, pageSize, pageNumber);
216
    }
217

    
218
    @Override
219
    public List<TaxonBase> getTaxaByName(boolean doTaxa, boolean doSynonyms, boolean doMisappliedNames, boolean doCommonNames,
220
            boolean includeAuthors,
221
            String queryString, Classification classification, TaxonNode subtree,
222
            MatchMode matchMode, Set<NamedArea> namedAreas, boolean includeUnpublished, NameSearchOrder order,
223
            Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
224

    
225
        boolean doCount = false;
226

    
227
        String searchField = includeAuthors ? "titleCache" : "nameCache";
228
        Query<TaxonBase> query = prepareTaxaByName(doTaxa, doSynonyms, doMisappliedNames, doCommonNames, includeUnpublished,
229
                searchField, queryString, classification, subtree, matchMode, namedAreas, order, pageSize, pageNumber, doCount,
230
                TaxonBase.class);
231

    
232
        if (query != null){
233
            @SuppressWarnings("rawtypes")
234
            List<TaxonBase> results = query.list();
235

    
236
            defaultBeanInitializer.initializeAll(results, propertyPaths);
237

    
238
            //Collections.sort(results, comp);
239
            return results;
240
        }else{
241
            return new ArrayList<>();
242
        }
243
    }
244

    
245
    //new search for the editor, for performance issues the return values are only uuid and titleCache, to avoid the initialisation of all objects
246
    @Override
247
    @SuppressWarnings("unchecked")
248
    public List<UuidAndTitleCache<? extends IdentifiableEntity>> getTaxaByNameForEditor(boolean doTaxa, boolean doSynonyms, boolean doNamesWithoutTaxa,
249
            boolean doMisappliedNames, boolean doCommonNames, boolean includeUnpublished, boolean includeAuthors, String queryString, Classification classification, TaxonNode subtree,
250
            MatchMode matchMode, Set<NamedArea> namedAreas, NameSearchOrder order) {
251

    
252
        if (order == null){
253
            order = NameSearchOrder.ALPHA;  //TODO add to signature
254
        }
255

    
256
        boolean doCount = false;
257

    
258
        @SuppressWarnings("rawtypes")
259
        List<UuidAndTitleCache<? extends IdentifiableEntity>> resultObjects = new ArrayList<>();
260
        if (doNamesWithoutTaxa){
261
        	List<TaxonName> nameResult = taxonNameDao.findByName(
262
        	        includeAuthors, queryString, matchMode, null, null, null, null);
263

    
264
        	for (TaxonName name: nameResult){
265
        		if (name.getTaxonBases().size() == 0){
266
        			resultObjects.add(new UuidAndTitleCache<>(TaxonName.class, name.getUuid(),
267
        			        name.getId(), name.getTitleCache()));
268
        		}
269
        	}
270
        	if (!doSynonyms && !doTaxa && !doCommonNames){
271
        		return resultObjects;
272
        	}
273
        }
274
        String searchField = includeAuthors ? "titleCache" : "nameCache";
275
        Query<Object[]> query = prepareTaxaByNameForEditor(doTaxa, doSynonyms, doMisappliedNames, doCommonNames, includeUnpublished,
276
                searchField, queryString, classification, subtree, matchMode, namedAreas, doCount, order, Object[].class);
277

    
278
        if (query != null){
279
            List<Object[]> results = query.list();
280

    
281
            Object[] result;
282
            for(int i = 0; i<results.size();i++){
283
                result = results.get(i);
284

    
285
                //differentiate taxa and synonyms
286
                // new Boolean(result[3].toString()) is due to the fact that result[3] could be a Boolean ora String
287
                // see FIXME in 'prepareQuery' for more details
288
                if (doTaxa || doSynonyms || doCommonNames){
289
                    if (result[3].equals("synonym")) {
290
                        resultObjects.add( new UuidAndTitleCache<>(Synonym.class, (UUID) result[0], (Integer) result[1], (String)result[2], new Boolean(result[4].toString()), null));
291
                    }
292
                    else {
293
                        resultObjects.add( new UuidAndTitleCache<>(Taxon.class, (UUID) result[0], (Integer) result[1], (String)result[2], new Boolean(result[4].toString()), null));
294
                    }
295

    
296
                }else if (doSynonyms){
297
                    resultObjects.add( new UuidAndTitleCache<>(Synonym.class, (UUID) result[0], (Integer) result[1], (String)result[2], new Boolean(result[4].toString()), null));
298
                }
299
            }
300
        }
301
        return resultObjects;
302

    
303
    }
304

    
305
    @Override
306
    public List<Taxon> getTaxaByCommonName(String queryString, Classification classification,
307
               MatchMode matchMode, Set<NamedArea> namedAreas, Integer pageSize,
308
               Integer pageNumber, List<String> propertyPaths) {
309
        boolean doCount = false;
310
        Query<Taxon> query = prepareTaxaByCommonName(queryString, classification, matchMode, namedAreas, pageSize, pageNumber, doCount, false, Taxon.class);
311
        if (query != null){
312
            List<Taxon> results = query.list();
313
            defaultBeanInitializer.initializeAll(results, propertyPaths);
314
            return results;
315
        }else{
316
            return new ArrayList<>();
317
        }
318

    
319
    }
320

    
321
    /**
322
     * @param clazz
323
     * @param searchField the field in TaxonName to be searched through usually either <code>nameCache</code> or <code>titleCache</code>
324
     * @param queryString
325
     * @param classification TODO
326
     * @param matchMode
327
     * @param namedAreas
328
     * @param pageSize
329
     * @param pageNumber
330
     * @param doCount
331
     * @return
332
     */
333
    private <R extends Object> Query<R> prepareTaxaByNameForEditor(boolean doTaxa, boolean doSynonyms, boolean doMisappliedNames, boolean doCommonNames,
334
            boolean includeUnpublished, String searchField, String queryString, Classification classification, TaxonNode subtree,
335
            MatchMode matchMode, Set<NamedArea> namedAreas, boolean doCount, NameSearchOrder order, Class<R> returnedClass) {
336
        return prepareByNameQuery(doTaxa, doSynonyms, doMisappliedNames, doCommonNames, includeUnpublished,
337
                searchField, queryString,
338
                classification, subtree, matchMode, namedAreas, order, doCount, true, returnedClass);
339
    }
340

    
341
    /**
342
     * @param doTaxa
343
     * @param doSynonyms
344
     * @param doIncludeMisappliedNames
345
     * @param doCommonNames
346
     * @param includeUnpublished
347
     * @param searchField
348
     * @param queryString
349
     * @param classification
350
     * @param matchMode
351
     * @param namedAreas
352
     * @param order
353
     * @param doCount
354
     * @param returnIdAndTitle
355
     *            If set true the seach method will not return synonym and taxon
356
     *            entities but an array containing the uuid, titleCache, and the
357
     *            DTYPE in lowercase letters.
358
     * @return
359
     */
360
    private <R extends Object> Query<R> prepareByNameQuery(boolean doTaxa, boolean doSynonyms, boolean doMisappliedNames,
361
                boolean doCommonNames, boolean includeUnpublished, String searchField, String queryString,
362
                Classification classification, TaxonNode subtree, MatchMode matchMode, Set<NamedArea> namedAreas,
363
                NameSearchOrder order, boolean doCount, boolean returnIdAndTitle, Class<R> returnedClass){
364

    
365
            boolean doProParteSynonyms = doSynonyms;  //we may distinguish in future
366
            boolean doConceptRelations = doMisappliedNames || doProParteSynonyms;
367

    
368
            if (order == null){
369
                order = NameSearchOrder.DEFAULT();
370
            }
371
            String hqlQueryString = matchMode.queryStringFrom(queryString);
372
            String selectWhat;
373
            if (returnIdAndTitle){
374
                selectWhat = "t.uuid, t.id, t.titleCache ";
375
            }else {
376
                selectWhat = (doCount ? "count(t)": "t");
377
            }
378

    
379
            //area filter
380
            //TODO share code with taxon node filter
381
            String hql = "";
382
            Set<NamedArea> areasExpanded = new HashSet<>();
383
            if(namedAreas != null && namedAreas.size() > 0){
384
                // expand areas and restrict by distribution area
385
                Query<NamedArea> areaQuery = getSession().createQuery("SELECT childArea "
386
                        + " FROM NamedArea AS childArea LEFT JOIN childArea.partOf as parentArea "
387
                        + " WHERE parentArea = :area", NamedArea.class);
388
                expandNamedAreas(namedAreas, areasExpanded, areaQuery);
389
            }
390
            boolean doAreaRestriction = areasExpanded.size() > 0;
391

    
392
            Set<UUID> namedAreasUuids = new HashSet<>();
393
            for (NamedArea area:areasExpanded){
394
                namedAreasUuids.add(area.getUuid());
395
            }
396

    
397
            Subselects subSelects = createByNameHQLString(doConceptRelations,
398
                    includeUnpublished, classification, subtree, areasExpanded, matchMode, searchField);
399
            String taxonSubselect = subSelects.taxonSubselect;
400
            String synonymSubselect = subSelects.synonymSubselect;
401
            String conceptSelect = subSelects.conceptSelect;
402
            String commonNameSubSelect = subSelects.commonNameSubselect;
403

    
404
            if (logger.isDebugEnabled()) {
405
                logger.debug("taxonSubselect: " + (taxonSubselect != null ? taxonSubselect: "NULL"));
406
                logger.debug("synonymSubselect: " + (synonymSubselect != null ? synonymSubselect: "NULL"));
407
            }
408

    
409
            List<Integer> taxonIDs = new ArrayList<>();
410
            List<Integer> synonymIDs = new ArrayList<>();
411

    
412
            if(doTaxa){
413
                // find Taxa
414
                Query<Integer> subTaxon = getSearchQueryString(hqlQueryString, taxonSubselect, true);
415

    
416
                addRestrictions(doAreaRestriction, classification, subtree, includeUnpublished,
417
                        namedAreasUuids, subTaxon);
418
                taxonIDs = subTaxon.list();
419
            }
420

    
421
            if(doSynonyms){
422
                // find synonyms
423
                Query<Integer> subSynonym = getSearchQueryString(hqlQueryString, synonymSubselect, true);
424
                addRestrictions(doAreaRestriction, classification, subtree, includeUnpublished, namedAreasUuids,subSynonym);
425
                synonymIDs = subSynonym.list();
426
            }
427
            if (doConceptRelations ){
428
                Query<Integer> subMisappliedNames = getSearchQueryString(hqlQueryString, conceptSelect, true);
429
                Set<TaxonRelationshipType> relTypeSet = new HashSet<>();
430
                if (doMisappliedNames){
431
                    relTypeSet.addAll(TaxonRelationshipType.allMisappliedNameTypes());
432
                }
433
                if (doProParteSynonyms){
434
                    relTypeSet.addAll(TaxonRelationshipType.allSynonymTypes());
435
                }
436
                subMisappliedNames.setParameterList("rTypeSet", relTypeSet);
437
                addRestrictions(doAreaRestriction, classification, subtree, includeUnpublished, namedAreasUuids, subMisappliedNames);
438
                taxonIDs.addAll(subMisappliedNames.list());
439
            }
440

    
441
            if(doCommonNames){
442
                // find Taxa
443
                Query<Integer> subCommonNames = getSearchQueryString(hqlQueryString, commonNameSubSelect, false);
444
                addRestrictions(doAreaRestriction, classification, subtree, includeUnpublished, namedAreasUuids, subCommonNames);
445
                taxonIDs.addAll(subCommonNames.list());
446
            }
447

    
448

    
449
            if(synonymIDs.size()>0 && taxonIDs.size()>0){
450
                hql = "SELECT " + selectWhat;
451
                // in doNotReturnFullEntities mode it is nesscary to also return the type of the matching entities:
452
                // also return the computed isOrphaned flag
453
                if (returnIdAndTitle &&  !doCount ){
454
                    hql += ", CASE WHEN t.id in (:taxa) THEN 'taxon' ELSE 'synonym' END, " +
455
                            " CASE WHEN t.id in (:taxa) "
456
                                    + " AND t.taxonNodes IS EMPTY "
457
                                    + " AND t.relationsFromThisTaxon IS EMPTY "
458
                                    + " AND t.relationsToThisTaxon IS EMPTY "
459
                                 + " THEN true ELSE false END ";
460
                }
461
                hql +=  " FROM %s t " +
462
                        " WHERE (t.id in (:taxa) OR t.id IN (:synonyms)) ";
463
            }else if (synonymIDs.size()>0 ){
464
                hql = "SELECT " + selectWhat;
465
                // in doNotReturnFullEntities mode it is nesscary to also return the type of the matching entities:
466
                // also return the computed isOrphaned flag
467
                if (returnIdAndTitle &&  !doCount ){
468
                    hql += ", 'synonym', 'false' ";
469

    
470
                }
471
                hql +=  " FROM %s t " +
472
                        " WHERE t.id in (:synonyms) ";
473

    
474
            } else if (taxonIDs.size()>0 ){
475
                hql = "SELECT " + selectWhat;
476
                // in doNotReturnFullEntities mode it is nesscary to also return the type of the matching entities:
477
                // also return the computed isOrphaned flag
478
                if (returnIdAndTitle &&  !doCount ){
479
                    hql += ", 'taxon', " +
480
                            " CASE WHEN t.taxonNodes is empty "
481
                            + "  AND t.relationsFromThisTaxon is empty "
482
                            + "  AND t.relationsToThisTaxon is empty "
483
                            + "THEN true ELSE false END ";
484
                }
485
                hql +=  " FROM %s t " +
486
                        " WHERE t.id in (:taxa) ";
487
            } else if (StringUtils.isBlank(queryString)){
488
                hql = "SELECT " + selectWhat + " FROM %s t";
489
            } else{
490
                return null;
491
            }
492

    
493
            String classString;
494
            if ((doTaxa || doCommonNames || doConceptRelations) && doSynonyms){
495
                classString = "TaxonBase";
496
            } else if (doTaxa || doCommonNames){
497
                classString = "Taxon";
498
            } else if (doSynonyms && !(doCommonNames|| doTaxa || doConceptRelations)){
499
                classString = "Synonym";  // as long as doProParteSynonyms = doSynonyms this case should not happen
500
            } else{//only misappliedNames
501
                classString = "Taxon";
502
            }
503

    
504
            hql = String.format(hql, classString);
505

    
506
            if (hql.isEmpty()) {
507
                return null;
508
            }
509
            if(!doCount){
510
                String orderBy = " ORDER BY ";
511
                String alphabeticBase = " t.name.genusOrUninomial, case when t.name.specificEpithet like '\"%\"' then 1 else 0 end, t.name.specificEpithet, t.name.rank desc, t.name.nameCache";
512

    
513
                if (order == NameSearchOrder.LENGTH_ALPHA_NAME){
514
                    orderBy += " length(t.name.nameCache), " + alphabeticBase;
515
                }else if (order == NameSearchOrder.LENGTH_ALPHA_TITLE){
516
                    orderBy += " length(t.name.titleCache), " + alphabeticBase;
517
                }else {
518
                    orderBy += alphabeticBase;
519
                }
520

    
521
                hql += orderBy;
522
            }
523

    
524
            if(logger.isDebugEnabled()){ logger.debug("hql: " + hql);}
525
            Query<R> query = getSession().createQuery(hql, returnedClass);
526

    
527
            // find taxa and synonyms
528
            if (taxonIDs.size()>0){
529
                query.setParameterList("taxa", taxonIDs);
530
            }
531
            if (synonymIDs.size()>0){
532
                query.setParameterList("synonyms",synonymIDs);
533
            }
534
            if (taxonIDs.size()== 0 && synonymIDs.size() == 0){
535
                return null;
536
            }
537

    
538
            return query;
539
    }
540

    
541
    protected Query<Integer> getSearchQueryString(String hqlQueryString, String subselect, boolean includeProtectedTitle) {
542
        Query<Integer> result = getSession().createQuery(subselect, Integer.class);
543
        result.setParameter("queryString", hqlQueryString);
544
        if (includeProtectedTitle){
545
            result.setParameter("protectedTitleQueryString", hqlQueryString + "%");
546
        }
547
        return result;
548
    }
549

    
550
    protected void addRestrictions(boolean doAreaRestriction, Classification classification, TaxonNode subtree, boolean includeUnpublished,
551
            Set<UUID> namedAreasUuids, Query<Integer> query) {
552
        if(doAreaRestriction){
553
            query.setParameterList("namedAreasUuids", namedAreasUuids);
554
        }
555
        if(classification != null){
556
            query.setParameter("classification", classification);
557
        }
558
        if(subtree != null){
559
            query.setParameter("treeIndexLike", subtree.treeIndex() + "%");
560
        }
561
        if(!includeUnpublished){
562
            query.setParameter("publish", true);
563
        }
564
    }
565

    
566

    
567
    /**
568
     * @param searchField the field in TaxonName to be searched through usually either <code>nameCache</code> or <code>titleCache</code>
569
     * @param queryString
570
     * @param classification TODO
571
     * @param matchMode
572
     * @param namedAreas
573
     * @param pageSize
574
     * @param pageNumber
575
     * @param doCount
576
     * @param clazz
577
     * @return
578
     *
579
     * FIXME implement classification restriction & implement test: see {@link TaxonDaoHibernateImplTest#testCountTaxaByName()}
580
     */
581
    private <R extends Object> Query<R> prepareTaxaByName(boolean doTaxa, boolean doSynonyms, boolean doMisappliedNames,
582
            boolean doCommonNames, boolean includeUnpublished, String searchField, String queryString,
583
            Classification classification, TaxonNode subtree, MatchMode matchMode, Set<NamedArea> namedAreas, NameSearchOrder order,
584
            Integer pageSize, Integer pageNumber, boolean doCount, Class<R> returnClass) {
585

    
586
        Query<R> query = prepareByNameQuery(doTaxa, doSynonyms, doMisappliedNames, doCommonNames, includeUnpublished,
587
                searchField, queryString, classification, subtree, matchMode, namedAreas, order, doCount, false, returnClass);
588

    
589
        if(!doCount && query != null) {
590
            this.addPageSizeAndNumber(query, pageSize, pageNumber);
591
        }
592

    
593
        return query;
594
    }
595

    
596
    private <R extends Object> Query<R> prepareTaxaByCommonName(String queryString, Classification classification,
597
            MatchMode matchMode, Set<NamedArea> namedAreas, Integer pageSize, Integer pageNumber,
598
            boolean doCount, boolean returnIdAndTitle, Class<R> returnClass){
599

    
600
        String what = "SELECT DISTINCT";
601
        if (returnIdAndTitle){
602
        	what += " t.uuid, t.id, t.titleCache, \'taxon\', CASE WHEN t.taxonNodes IS EMPTY AND t.relationsFromThisTaxon IS EMPTY AND t.relationsToThisTaxon IS EMPTY THEN true ELSE false END ";
603
        }else {
604
        	what += (doCount ? " count(t)": " t");
605
        }
606
        String hql= what + " from Taxon t " +
607
            "join t.descriptions d "+
608
            "join d.descriptionElements e " +
609
//            "join e.feature f " +
610
            "where e.class = 'CommonTaxonName' and e.name "+matchMode.getMatchOperator()+" :queryString";//and ls.text like 'common%'";
611

    
612
        Query<R> query = getSession().createQuery(hql, returnClass);
613

    
614
        query.setParameter("queryString", matchMode.queryStringFrom(queryString));
615
        if(!doCount) {
616
            this.addPageSizeAndNumber(query, pageSize, pageNumber);
617
        }
618
        return query;
619
    }
620

    
621
    @Override
622
    public long countTaxaByName(boolean doTaxa, boolean doSynonyms, boolean doMisappliedNames, boolean doCommonNames,
623
            boolean doIncludeAuthors, String queryString, Classification classification, TaxonNode subtree,
624
        MatchMode matchMode, Set<NamedArea> namedAreas, boolean includeUnpublished) {
625

    
626
        boolean doCount = true;
627
        String searchField = doIncludeAuthors ? "titleCache": "nameCache";
628

    
629
        Query<Long> query = prepareTaxaByName(doTaxa, doSynonyms, doMisappliedNames, doCommonNames, includeUnpublished,
630
                searchField, queryString, classification, subtree, matchMode, namedAreas, null, null, null, doCount, Long.class);
631
        if (query != null) {
632
            return query.uniqueResult();
633
        }else{
634
            return 0;
635
        }
636
    }
637

    
638
    private void expandNamedAreas(Collection<NamedArea> namedAreas, Set<NamedArea> areasExpanded, Query<NamedArea> areaQuery) {
639
        for(NamedArea a : namedAreas){
640
            areasExpanded.add(a);
641
            areaQuery.setParameter("area", a);
642
            List<NamedArea> childAreas = areaQuery.list();
643
            if(childAreas.size() > 0){
644
                areasExpanded.addAll(childAreas);
645
                expandNamedAreas(childAreas, areasExpanded, areaQuery);
646
            }
647
        }
648
    }
649

    
650
    @Override
651
    public UUID delete(TaxonBase taxonBase) throws DataAccessException{
652
        if (taxonBase == null){
653
            logger.warn("TaxonBase was 'null'");
654
            return null;
655
        }
656

    
657
        // Merge the object in if it is detached
658
        //
659
        // I think this is preferable to catching lazy initialization errors
660
        // as that solution only swallows and hides the exception, but doesn't
661
        // actually solve it.
662
        taxonBase = (TaxonBase)getSession().merge(taxonBase);
663

    
664
        taxonBase.removeSources();
665

    
666
        if (taxonBase instanceof Taxon){ // is Taxon
667
            Taxon taxon = ((Taxon)taxonBase);
668
            Set<Synonym> syns = new HashSet<>(taxon.getSynonyms());
669
            for (Synonym syn: syns){
670
                taxon.removeSynonym(syn);
671
            }
672
        }
673

    
674
       return super.delete(taxonBase);
675

    
676
    }
677

    
678
    @Override
679
    public List<TaxonBase> findByNameTitleCache(boolean doTaxa, boolean doSynonyms, boolean includeUnpublished, String queryString, Classification classification, TaxonNode subtree, MatchMode matchMode, Set<NamedArea> namedAreas, NameSearchOrder order, Integer pageNumber, Integer pageSize, List<String> propertyPaths) {
680

    
681
        boolean doCount = false;
682
        Query<TaxonBase> query = prepareTaxaByName(doTaxa, doSynonyms, false, false, includeUnpublished, "titleCache", queryString, classification, subtree, matchMode, namedAreas, order, pageSize, pageNumber, doCount, TaxonBase.class);
683
        if (query != null){
684
            @SuppressWarnings({ "unchecked", "rawtypes" })
685
            List<TaxonBase> results = query.list();
686
            defaultBeanInitializer.initializeAll(results, propertyPaths);
687
            return results;
688
        }
689
        return new ArrayList<>();
690

    
691
    }
692

    
693
    @Override
694
    public TaxonBase findByUuid(UUID uuid, List<Criterion> criteria, List<String> propertyPaths) {
695

    
696
        Criteria crit = getSession().createCriteria(type);
697

    
698
        if (uuid != null) {
699
            crit.add(Restrictions.eq("uuid", uuid));
700
        } else {
701
            logger.warn("UUID is NULL");
702
            return null;
703
        }
704
        if(criteria != null){
705
            for (Criterion criterion : criteria) {
706
                crit.add(criterion);
707
            }
708
        }
709
        crit.addOrder(Order.asc("uuid"));
710

    
711
        @SuppressWarnings({ "unchecked", "rawtypes" })
712
        List<? extends TaxonBase> results = crit.list();
713
        if (results.size() == 1) {
714
            defaultBeanInitializer.initializeAll(results, propertyPaths);
715
            TaxonBase<?> taxon = results.iterator().next();
716
            return taxon;
717
        } else if (results.size() > 1) {
718
            logger.error("Multiple results for UUID: " + uuid);
719
        } else if (results.size() == 0) {
720
            logger.info("No results for UUID: " + uuid);
721
        }
722

    
723
        return null;
724
    }
725

    
726
    @Override
727
    public List<? extends TaxonBase> findByUuids(List<UUID> uuids, List<Criterion> criteria, List<String> propertyPaths) {
728

    
729
        Criteria crit = getSession().createCriteria(type);
730

    
731
        if (uuids != null) {
732
            crit.add(Restrictions.in("uuid", uuids));
733
        } else {
734
            logger.warn("List<UUID> uuids is NULL");
735
            return null;
736
        }
737
        if(criteria != null){
738
            for (Criterion criterion : criteria) {
739
                crit.add(criterion);
740
            }
741
        }
742
        crit.addOrder(Order.asc("uuid"));
743

    
744
        @SuppressWarnings({ "unchecked", "rawtypes" })
745
        List<? extends TaxonBase> results = crit.list();
746

    
747
        defaultBeanInitializer.initializeAll(results, propertyPaths);
748
        return results;
749
    }
750

    
751
    @Override
752
    public long countMatchesByName(String queryString, MatchMode matchMode, boolean onlyAcccepted) {
753
        checkNotInPriorView("TaxonDaoHibernateImpl.countMatchesByName(String queryString, ITitledDao.MATCH_MODE matchMode, boolean onlyAcccepted)");
754

    
755
        Criteria crit = getCriteria(type);
756
        crit.add(Restrictions.ilike("titleCache", matchMode.queryStringFrom(queryString)));
757
        crit.setProjection(Projections.rowCount());
758
        return (Long)crit.uniqueResult();
759
    }
760

    
761

    
762
    @Override
763
    public long countMatchesByName(String queryString, MatchMode matchMode, boolean onlyAcccepted, List<Criterion> criteria) {
764
        checkNotInPriorView("TaxonDaoHibernateImpl.countMatchesByName(String queryString, ITitledDao.MATCH_MODE matchMode, boolean onlyAcccepted, List<Criterion> criteria)");
765

    
766
        Criteria crit = getCriteria(type);
767
        crit.add(Restrictions.ilike("titleCache", matchMode.queryStringFrom(queryString)));
768
        if(criteria != null){
769
            for (Criterion criterion : criteria) {
770
                crit.add(criterion);
771
            }
772
        }
773
        crit.setProjection(Projections.rowCount());
774
        return (Long)crit.uniqueResult();
775
    }
776

    
777

    
778
    @Override
779
    public long countSynonyms(boolean onlyAttachedToTaxon) {
780
        AuditEvent auditEvent = getAuditEventFromContext();
781
        if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
782
            String queryStr =
783
                    " SELECT count(syn) "
784
                  + " FROM Synonym syn";
785
            if (onlyAttachedToTaxon){
786
                queryStr += " WHERE syn.acceptedTaxon IS NOT NULL";
787
            }
788
            Query<Long> query = getSession().createQuery(queryStr, Long.class);
789

    
790
            return query.uniqueResult();
791
        } else {
792
            AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision(Synonym.class,auditEvent.getRevisionNumber());
793
            if (onlyAttachedToTaxon){
794
                query.add(new NotNullAuditExpression(null, new EntityPropertyName("acceptedTaxon")));
795
            }
796
            query.addProjection(AuditEntity.id().count());
797

    
798
            return (Long)query.getSingleResult();
799
        }
800
    }
801

    
802
    @Override
803
    public long countSynonyms(Taxon taxon, SynonymType type) {
804
        AuditEvent auditEvent = getAuditEventFromContext();
805
        if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
806
            Criteria criteria = getCriteria(Synonym.class);
807

    
808
            criteria.add(Restrictions.eq("acceptedTaxon", taxon));
809
            if(type != null) {
810
                criteria.add(Restrictions.eq("type", type));
811
            }
812
            criteria.setProjection(Projections.rowCount());
813
            return (Long)criteria.uniqueResult();
814
        } else {
815
            AuditQuery query = makeAuditQuery(Synonym.class,auditEvent);
816
            query.add(AuditEntity.relatedId("acceptedTaxon").eq(taxon.getId()));
817
            query.addProjection(AuditEntity.id().count());
818

    
819
            if(type != null) {
820
                query.add(AuditEntity.relatedId("type").eq(type.getId()));
821
            }
822

    
823
            return (Long)query.getSingleResult();
824
        }
825
    }
826

    
827
    @Override
828
    public long countSynonyms(Synonym synonym, SynonymType type) {
829
        AuditEvent auditEvent = getAuditEventFromContext();
830
        if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
831
            Criteria criteria = getCriteria(Synonym.class);
832

    
833
            criteria.add(Restrictions.isNotNull("acceptedTaxon"));
834
            if(type != null) {
835
                criteria.add(Restrictions.eq("type", type));
836
            }
837

    
838
            criteria.setProjection(Projections.rowCount());
839
            return (Long)criteria.uniqueResult();
840
        } else {
841
            AuditQuery query = makeAuditQuery(Synonym.class,auditEvent);
842
            query.add(new NotNullAuditExpression(null, new EntityPropertyName("acceptedTaxon")));
843
            query.addProjection(AuditEntity.id().count());
844

    
845
            if(type != null) {
846
                query.add(AuditEntity.relatedId("type").eq(type.getId()));
847
            }
848

    
849
            return (Long)query.getSingleResult();
850
        }
851
    }
852

    
853
    @Override
854
    public long countTaxaByName(Class<? extends TaxonBase> clazz, String genusOrUninomial, String infraGenericEpithet, String specificEpithet,
855
            String infraSpecificEpithet, String authorshipCache, Rank rank) {
856
        checkNotInPriorView("TaxonDaoHibernateImpl.countTaxaByName(Boolean accepted, String genusOrUninomial,	String infraGenericEpithet, String specificEpithet,	String infraSpecificEpithet, String authorshipCache, Rank rank)");
857
        Criteria criteria = null;
858

    
859
        criteria = getCriteria(clazz);
860

    
861
        criteria.setFetchMode( "name", FetchMode.JOIN );
862
        criteria.createAlias("name", "name");
863

    
864
        if(genusOrUninomial == null) {
865
            criteria.add(Restrictions.isNull("name.genusOrUninomial"));
866
        } else if(!genusOrUninomial.equals("*")) {
867
            criteria.add(Restrictions.eq("name.genusOrUninomial", genusOrUninomial));
868
        }
869

    
870
        if(infraGenericEpithet == null) {
871
            criteria.add(Restrictions.isNull("name.infraGenericEpithet"));
872
        } else if(!infraGenericEpithet.equals("*")) {
873
            criteria.add(Restrictions.eq("name.infraGenericEpithet", infraGenericEpithet));
874
        }
875

    
876
        if(specificEpithet == null) {
877
            criteria.add(Restrictions.isNull("name.specificEpithet"));
878
        } else if(!specificEpithet.equals("*")) {
879
            criteria.add(Restrictions.eq("name.specificEpithet", specificEpithet));
880

    
881
        }
882

    
883
        if(infraSpecificEpithet == null) {
884
            criteria.add(Restrictions.isNull("name.infraSpecificEpithet"));
885
        } else if(!infraSpecificEpithet.equals("*")) {
886
            criteria.add(Restrictions.eq("name.infraSpecificEpithet", infraSpecificEpithet));
887
        }
888

    
889
        if(authorshipCache == null) {
890
            criteria.add(Restrictions.eq("name.authorshipCache", ""));
891
        } else if(!authorshipCache.equals("*")) {
892
            criteria.add(Restrictions.eq("name.authorshipCache", authorshipCache));
893
        }
894

    
895
        if(rank != null) {
896
            criteria.add(Restrictions.eq("name.rank", rank));
897
        }
898

    
899
        criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
900

    
901
        return (Long)criteria.uniqueResult();
902
    }
903

    
904
    @Override
905
    public <T extends TaxonBase> List<T> findTaxaByName(Class<T> clazz, String genusOrUninomial, String infraGenericEpithet, String specificEpithet,
906
            String infraSpecificEpithet, String authorship, Rank rank, Integer pageSize,Integer pageNumber, List<String> propertyPaths) {
907
        checkNotInPriorView("TaxonDaoHibernateImpl.findTaxaByName(Boolean accepted, String genusOrUninomial, String infraGenericEpithet, String specificEpithet, String infraSpecificEpithet, String authorship, Rank rank, Integer pageSize,Integer pageNumber, List<String> propertyPaths)");
908
        Criteria criteria = getCriteria(clazz);
909

    
910
        criteria.setFetchMode( "name", FetchMode.JOIN );
911
        criteria.createAlias("name", "name");
912

    
913
        if(genusOrUninomial == null) {
914
            criteria.add(Restrictions.isNull("name.genusOrUninomial"));
915
        } else if(!genusOrUninomial.equals("*")) {
916
            criteria.add(Restrictions.eq("name.genusOrUninomial", genusOrUninomial));
917
        }
918

    
919
        if(infraGenericEpithet == null) {
920
            criteria.add(Restrictions.isNull("name.infraGenericEpithet"));
921
        } else if(!infraGenericEpithet.equals("*")) {
922
            criteria.add(Restrictions.eq("name.infraGenericEpithet", infraGenericEpithet));
923
        }
924

    
925
        if(specificEpithet == null) {
926
            criteria.add(Restrictions.isNull("name.specificEpithet"));
927
        } else if(!specificEpithet.equals("*")) {
928
            criteria.add(Restrictions.eq("name.specificEpithet", specificEpithet));
929
        }
930

    
931
        if(infraSpecificEpithet == null) {
932
            criteria.add(Restrictions.isNull("name.infraSpecificEpithet"));
933
        } else if(!infraSpecificEpithet.equals("*")) {
934
            criteria.add(Restrictions.eq("name.infraSpecificEpithet", infraSpecificEpithet));
935
        }
936

    
937
        if(authorship == null) {
938
            criteria.add(Restrictions.eq("name.authorshipCache", ""));
939
        } else if(!authorship.equals("*")) {
940
            criteria.add(Restrictions.eq("name.authorshipCache", authorship));
941
        }
942

    
943
        if(rank != null) {
944
            criteria.add(Restrictions.eq("name.rank", rank));
945
        }
946

    
947
        if(pageSize != null) {
948
            criteria.setMaxResults(pageSize);
949
            if(pageNumber != null) {
950
                criteria.setFirstResult(pageNumber * pageSize);
951
            } else {
952
                criteria.setFirstResult(0);
953
            }
954
        }
955

    
956
        @SuppressWarnings({ "unchecked"})
957
        List<T> result = criteria.list();
958

    
959
        defaultBeanInitializer.initializeAll(result, propertyPaths);
960
        return result;
961
    }
962

    
963
    @Override
964
    public long countTaxonRelationships(Taxon taxon, TaxonRelationshipType type,
965
            boolean includeUnpublished, Direction direction) {
966
        Set<TaxonRelationshipType> types = null;
967
        if (type != null){
968
            types = new HashSet<>();
969
            types.add(type);
970
        }
971
        return countTaxonRelationships(taxon, types, includeUnpublished, direction);
972
    }
973

    
974
    @Override
975
    public long countTaxonRelationships(Taxon taxon, Set<TaxonRelationshipType> types,
976
            boolean includeUnpublished, Direction direction) {
977
        AuditEvent auditEvent = getAuditEventFromContext();
978
        if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
979

    
980
            String queryString = prepareTaxonRelationshipQuery(types, includeUnpublished, direction, true);
981
            Query<Long> query = getSession().createQuery(queryString, Long.class);
982
            query.setParameter("relatedTaxon", taxon);
983
            if(types != null) {
984
                query.setParameterList("types",types);
985
            }
986
            if(! includeUnpublished) {
987
                query.setParameter("publish",Boolean.TRUE);
988
            }
989
            return query.uniqueResult();
990
        } else {
991
          //TODO unpublished
992

    
993
            AuditQuery query = makeAuditQuery(TaxonRelationship.class, auditEvent);
994
            query.add(AuditEntity.relatedId(direction.toString()).eq(taxon.getId()));
995
            query.addProjection(AuditEntity.id().count());
996

    
997
            if(types != null) {
998
                //TODO adapt to new Set semantic, was single type before
999
//                query.add(AuditEntity.relatedId("type").eq(type.getId()));
1000
            }
1001

    
1002
            return (Long)query.getSingleResult();
1003
        }
1004
    }
1005

    
1006

    
1007
    /**
1008
     * @param type
1009
     * @param includeUnpublished
1010
     * @param direction
1011
     * @param b
1012
     * @return
1013
     */
1014
    private String prepareTaxonRelationshipQuery(Set<TaxonRelationshipType> types, boolean includeUnpublished,
1015
            Direction direction, boolean isCount) {
1016
        String selectStr = isCount? " count(rel) as n ":" rel ";
1017
        String result = "SELECT " + selectStr + " FROM TaxonRelationship rel ";
1018
        if(direction != null){
1019
            result += " WHERE rel."+direction+" = :relatedTaxon";
1020
        } else {
1021
            result += " WHERE (rel.relatedFrom = :relatedTaxon OR rel.relatedTo = :relatedTaxon )";
1022
        }
1023
        if (types != null){
1024
            result += " AND rel.type IN (:types) ";
1025
        }
1026
        if(! includeUnpublished) {
1027
            result += " AND rel."+direction.invers()+".publish = :publish";
1028
        }
1029
        return result;
1030
    }
1031

    
1032
    @Override
1033
    public List<TaxonRelationship> getTaxonRelationships(Taxon taxon, TaxonRelationshipType type,
1034
            boolean includeUnpublished, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
1035
            List<String> propertyPaths, Direction direction) {
1036
        Set<TaxonRelationshipType> types = null;
1037
        if (type != null){
1038
            types = new HashSet<>();
1039
            types.add(type);
1040
        }
1041
        return getTaxonRelationships(taxon, types, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, direction);
1042
    }
1043

    
1044
    @Override
1045
    public List<TaxonRelationship> getTaxonRelationships(Taxon taxon, Set<TaxonRelationshipType> types,
1046
            boolean includeUnpublished, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
1047
            List<String> propertyPaths, Direction direction) {
1048

    
1049
        AuditEvent auditEvent = getAuditEventFromContext();
1050
        if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
1051

    
1052
            String queryString = prepareTaxonRelationshipQuery(types, includeUnpublished, direction, false);
1053

    
1054
            queryString += orderByClause("rel", orderHints);
1055

    
1056
            Query<TaxonRelationship> query = getSession().createQuery(queryString, TaxonRelationship.class);
1057
            query.setParameter("relatedTaxon", taxon);
1058
            if(types != null) {
1059
                query.setParameterList("types",types);
1060
            }
1061
            if(! includeUnpublished) {
1062
                query.setParameter("publish",Boolean.TRUE);
1063
            }
1064
            addPageSizeAndNumber(query, pageSize, pageNumber);
1065

    
1066
            List<TaxonRelationship> result = query.list();
1067
            defaultBeanInitializer.initializeAll(result, propertyPaths);
1068

    
1069
            return result;
1070
        } else {
1071
            //TODO unpublished
1072
            AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision(TaxonRelationship.class,auditEvent.getRevisionNumber());
1073
            query.add(AuditEntity.relatedId("relatedTo").eq(taxon.getId()));
1074

    
1075
            if(types != null) {
1076
                //FIXME adapt to Set (was single type before)
1077
//                query.add(AuditEntity.relatedId("type").eq(types.getId()));
1078
            }
1079

    
1080
            if(pageSize != null) {
1081
                query.setMaxResults(pageSize);
1082
                if(pageNumber != null) {
1083
                    query.setFirstResult(pageNumber * pageSize);
1084
                } else {
1085
                    query.setFirstResult(0);
1086
                }
1087
            }
1088

    
1089
            @SuppressWarnings("unchecked")
1090
            List<TaxonRelationship> result = query.getResultList();
1091
            defaultBeanInitializer.initializeAll(result, propertyPaths);
1092

    
1093
            // Ugly, but for now, there is no way to sort on a related entity property in Envers,
1094
            // and we can't live without this functionality in CATE as it screws up the whole
1095
            // taxon tree thing
1096
            if(orderHints != null && !orderHints.isEmpty()) {
1097
                SortedSet<TaxonRelationship> sortedList = new TreeSet<>(new TaxonRelationshipFromTaxonComparator());
1098
                sortedList.addAll(result);
1099
                return new ArrayList<>(sortedList);
1100
            }
1101

    
1102
            return result;
1103
        }
1104
    }
1105

    
1106
    class TaxonRelationshipFromTaxonComparator implements Comparator<TaxonRelationship> {
1107

    
1108
        @Override
1109
        public int compare(TaxonRelationship o1, TaxonRelationship o2) {
1110
            if (o1.equals(o2)){
1111
                return 0;
1112
            }
1113
            int result = o1.getFromTaxon().getTitleCache().compareTo(o2.getFromTaxon().getTitleCache());
1114
            if (result == 0 ){
1115
                result = o1.getUuid().compareTo(o2.getUuid());
1116
            }
1117
            return result;
1118
        }
1119

    
1120
    }
1121

    
1122
    @Override
1123
    public List<Synonym> getSynonyms(Taxon taxon, SynonymType type, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
1124
        AuditEvent auditEvent = getAuditEventFromContext();
1125
        if(auditEvent.equals(AuditEvent.CURRENT_VIEW)) {
1126
            Criteria criteria = getSession().createCriteria(Synonym.class);
1127

    
1128
            criteria.add(Restrictions.eq("acceptedTaxon", taxon));
1129
            if(type != null) {
1130
                criteria.add(Restrictions.eq("type", type));
1131
            }
1132

    
1133
            addOrder(criteria,orderHints);
1134

    
1135
            if(pageSize != null) {
1136
                criteria.setMaxResults(pageSize);
1137
                if(pageNumber != null) {
1138
                    criteria.setFirstResult(pageNumber * pageSize);
1139
                } else {
1140
                    criteria.setFirstResult(0);
1141
                }
1142
            }
1143

    
1144
            @SuppressWarnings("unchecked")
1145
            List<Synonym> result = criteria.list();
1146
            defaultBeanInitializer.initializeAll(result, propertyPaths);
1147

    
1148
            return result;
1149
        } else {
1150
            AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision(Synonym.class,auditEvent.getRevisionNumber());
1151
            query.add(AuditEntity.relatedId("acceptedTaxon").eq(taxon.getId()));
1152

    
1153
            if(type != null) {
1154
                query.add(AuditEntity.relatedId("type").eq(type.getId()));
1155
            }
1156

    
1157
            if(pageSize != null) {
1158
                query.setMaxResults(pageSize);
1159
                if(pageNumber != null) {
1160
                    query.setFirstResult(pageNumber * pageSize);
1161
                } else {
1162
                    query.setFirstResult(0);
1163
                }
1164
            }
1165

    
1166
            @SuppressWarnings("unchecked")
1167
            List<Synonym> result = query.getResultList();
1168
            defaultBeanInitializer.initializeAll(result, propertyPaths);
1169

    
1170
            return result;
1171
        }
1172
    }
1173

    
1174
    @Override
1175
    public void rebuildIndex() {
1176
        FullTextSession fullTextSession = Search.getFullTextSession(getSession());
1177

    
1178
        for(TaxonBase<?> taxonBase : list(null,null)) { // re-index all taxon base
1179
            Hibernate.initialize(taxonBase.getName());
1180
            fullTextSession.index(taxonBase);
1181
        }
1182
        fullTextSession.flushToIndexes();
1183
    }
1184

    
1185
    @Override
1186
    public String suggestQuery(String queryString) {
1187
        throw new RuntimeException("Query suggestion currently not implemented in TaxonDaoHibernateImpl");
1188
//        checkNotInPriorView("TaxonDaoHibernateImpl.suggestQuery(String queryString)");
1189
//        String alternativeQueryString = null;
1190
//        if (alternativeSpellingSuggestionParser != null) {
1191
//            try {
1192
//
1193
//                alternativeSpellingSuggestionParser.parse(queryString);
1194
//                org.apache.lucene.search.Query alternativeQuery = alternativeSpellingSuggestionParser.suggest(queryString);
1195
//                if (alternativeQuery != null) {
1196
//                    alternativeQueryString = alternativeQuery
1197
//                            .toString("name.titleCache");
1198
//                }
1199
//
1200
//            } catch (ParseException e) {
1201
//                throw new QueryParseException(e, queryString);
1202
//            }
1203
//        }
1204
//        return alternativeQueryString;
1205
    }
1206

    
1207
    @Override
1208
    public Taxon acceptedTaxonFor(Synonym synonym, Classification classificationFilter, List<String> propertyPaths){
1209

    
1210
        String hql = prepareListAcceptedTaxaFor(classificationFilter, false);
1211

    
1212
        Query<Taxon> query = getSession().createQuery(hql, Taxon.class);
1213
        query.setParameter("synonym", synonym);
1214
        if(classificationFilter != null){
1215
            query.setParameter("classificationFilter", classificationFilter);
1216
        }
1217

    
1218
        List<Taxon> result = query.list();
1219
        defaultBeanInitializer.initializeAll(result, propertyPaths);
1220
        return result.isEmpty()? null: result.get(0);
1221
    }
1222

    
1223
    @Override
1224
    public long countAcceptedTaxonFor(Synonym synonym, Classification classificationFilter){
1225

    
1226
        String hql = prepareListAcceptedTaxaFor(classificationFilter, true);
1227

    
1228
        Query<Long> query = getSession().createQuery(hql, Long.class);
1229
        query.setParameter("synonym", synonym);
1230
        if(classificationFilter != null){
1231
            query.setParameter("classificationFilter", classificationFilter);
1232
        }
1233

    
1234
        Long count = query.uniqueResult();
1235
        return count;
1236
    }
1237

    
1238
    private String prepareListAcceptedTaxaFor(Classification classificationFilter, boolean doCount) {
1239

    
1240
        String hql;
1241
        String hqlSelect = "SELECT " + (doCount? "COUNT(taxon)" : "taxon") +
1242
                 " FROM Synonym as syn "
1243
                 + "   JOIN syn.acceptedTaxon as taxon ";
1244
        String hqlWhere = " WHERE syn = :synonym";
1245

    
1246
        if(classificationFilter != null){
1247
            hqlSelect += " JOIN taxon.taxonNodes AS taxonNode";
1248
            hqlWhere  += " AND taxonNode.classification = :classificationFilter";
1249
        }
1250
        hql = hqlSelect + hqlWhere;
1251
        return hql;
1252
    }
1253

    
1254
    @Override
1255
    public TaxonBase find(LSID lsid) {
1256
        TaxonBase<?> taxonBase = super.find(lsid);
1257
        if(taxonBase != null) {
1258
            List<String> propertyPaths = new ArrayList<>();
1259
            propertyPaths.add("createdBy");
1260
            propertyPaths.add("updatedBy");
1261
            propertyPaths.add("name");
1262
            propertyPaths.add("secSource.citation");
1263
            propertyPaths.add("relationsToThisTaxon");
1264
            propertyPaths.add("relationsToThisTaxon.fromTaxon");
1265
            propertyPaths.add("relationsToThisTaxon.toTaxon");
1266
            propertyPaths.add("relationsFromThisTaxon");
1267
            propertyPaths.add("relationsFromThisTaxon.toTaxon");
1268
            propertyPaths.add("relationsToThisTaxon.type");
1269
            propertyPaths.add("synonyms");
1270
            propertyPaths.add("synonyms.type");
1271
            propertyPaths.add("descriptions");
1272

    
1273
            defaultBeanInitializer.initialize(taxonBase, propertyPaths);
1274
        }
1275
        return taxonBase;
1276
    }
1277

    
1278
    @Override
1279
    public List<String> taxaByNameNotInDB(List<String> taxonNames){
1280
        //get all taxa, already in db
1281
        Query<TaxonName> query = getSession().createQuery(
1282
                 " FROM TaxonName t "
1283
                +" WHERE t.nameCache IN (:taxonList)",
1284
                TaxonName.class);
1285
        query.setParameterList("taxonList", taxonNames);
1286
        List<TaxonName> taxaInDB = query.list();
1287
        //compare the original list with the result of the query
1288
        for (TaxonName taxonName: taxaInDB){
1289
            String nameCache = taxonName.getNameCache();
1290
            if (taxonNames.contains(nameCache)){
1291
                taxonNames.remove(nameCache);
1292
            }
1293
        }
1294

    
1295
        return taxonNames;
1296
    }
1297

    
1298
    @Override
1299
    public Map<String, Map<UUID,Set<TaxonName>>> findIdenticalNames(List<UUID> sourceRefUuids, List<String> propertyPaths){
1300
        Set<String> nameCacheCandidates = new HashSet<>();
1301
        try {
1302
            for (int i = 0; i<sourceRefUuids.size()-1;i++){
1303
                UUID sourceUuid1 = sourceRefUuids.get(i);
1304
                for (int j = i+1; j<sourceRefUuids.size();j++){
1305
                    UUID sourceUuid2 = sourceRefUuids.get(j);
1306
                    if (sourceUuid1.equals(sourceUuid2)){
1307
                        continue;  //just in case we have duplicates in the list
1308
                    }
1309

    
1310
                    Query<String> query = getSession().createQuery(
1311
                            " SELECT DISTINCT n1.nameCache "
1312
                          + " FROM TaxonBase t1 JOIN t1.name n1 JOIN t1.sources s1 JOIN s1.citation ref1 "
1313
                          +         " , TaxonBase t2 JOIN t2.name n2 JOIN t2.sources s2 JOIN s2.citation ref2 "
1314
                          + " WHERE  ref1.uuid = (:sourceUuid1) "
1315
                          + "       AND n1.id <> n2.id "
1316
                          + "       AND ref2.uuid IN (:sourceUuid2)"
1317
                          + "       AND ref1.uuid <> ref2.uuid "
1318
                          + "       AND n1.nameCache = n2.nameCache) "
1319
                          + "       AND t1.publish = 1 AND t2.publish = 1) "
1320
                          + " ORDER BY n1.nameCache ",
1321
                          String.class);
1322
                    query.setParameter("sourceUuid1", sourceUuid1);
1323
                    query.setParameter("sourceUuid2", sourceUuid2);
1324

    
1325
                    @SuppressWarnings("unchecked")
1326
                    List<String> queryNameCacheCandidates = query.list();
1327
                    nameCacheCandidates.addAll(queryNameCacheCandidates);
1328
                }
1329
            }
1330

    
1331
            Map<UUID, List<TaxonName>> duplicates = new HashMap<>();
1332
            for (UUID sourceUuid : sourceRefUuids){
1333
                Query<TaxonName> query=getSession().createQuery("SELECT n "
1334
                        + " FROM TaxonBase t JOIN t.name n JOIN t.sources s JOIN s.citation ref "
1335
                        + " WHERE ref.uuid = :sourceUuid AND n.nameCache IN (:nameCacheCandidates) AND t.publish = 1 "
1336
                        + " ORDER BY n.nameCache",
1337
                        TaxonName.class);
1338
                query.setParameter("sourceUuid", sourceUuid);
1339
                query.setParameterList("nameCacheCandidates", nameCacheCandidates);
1340
                List<TaxonName> sourceDuplicates = query.list();
1341
                defaultBeanInitializer.initializeAll(sourceDuplicates, propertyPaths);
1342

    
1343
                duplicates.put(sourceUuid, sourceDuplicates);
1344
            }
1345

    
1346
            List<String> nameCacheCandidateList = new ArrayList<>(nameCacheCandidates);
1347
            Map<String, Map<UUID,Set<TaxonName>>> result = new HashMap<>();
1348
            for (String nameCache: nameCacheCandidateList) {
1349
                Map<UUID,Set<TaxonName>> uuidNameMap = new HashMap<>();
1350
                result.put(nameCache, uuidNameMap);
1351
                for(UUID sourceUuid: duplicates.keySet()){
1352
                    Set<TaxonName> names = duplicates.get(sourceUuid).stream().filter(name->
1353
                            name.getNameCache().equals(nameCache)).collect(Collectors.toSet());
1354
                    uuidNameMap.put(sourceUuid, names);
1355
                }
1356
            }
1357

    
1358
            return result;
1359
        } catch (Exception e) {
1360
            e.printStackTrace();
1361
            throw e;
1362
        }
1363
    }
1364

    
1365
    @Override
1366
    public long countTaxaByCommonName(String searchString,
1367
            Classification classification, MatchMode matchMode,
1368
            Set<NamedArea> namedAreas) {
1369
        boolean doCount = true;
1370
        Query<Long> query = prepareTaxaByCommonName(searchString, classification, matchMode, namedAreas, null, null, doCount, false, Long.class);
1371
        if (query != null && !query.list().isEmpty()) {
1372
            Long o = query.uniqueResult();
1373
            if(o != null) {
1374
                return o;
1375
            }
1376
        }
1377
        return 0;
1378
    }
1379

    
1380
    private Subselects createByNameHQLString(boolean doConceptRelations,
1381
                boolean includeUnpublished, Classification classification, TaxonNode subtree,
1382
                Set<NamedArea> areasExpanded, MatchMode matchMode, String searchField){
1383

    
1384

    
1385
        boolean doAreaRestriction = areasExpanded.size() > 0;
1386
        boolean hasTaxonNodeFilter = classification != null || subtree != null;
1387

    
1388
        String doAreaRestrictionSubSelect =
1389
                     " SELECT %s.id "
1390
                   + " FROM Distribution e "
1391
                   + "    JOIN e.inDescription d "
1392
                   + "    JOIN d.taxon t " +
1393
                (hasTaxonNodeFilter ? " JOIN t.taxonNodes AS tn " : " ");
1394

    
1395
        String doAreaRestrictionConceptRelationSubSelect =
1396
                   "SELECT %s.id "
1397
                   + " FROM Distribution e "
1398
                   + "   JOIN e.inDescription d"
1399
                   + "   JOIN d.taxon t";
1400

    
1401
        String doTaxonSubSelect =
1402
                     " SELECT %s.id "
1403
                   + " FROM Taxon t " + (hasTaxonNodeFilter ? " "
1404
                           + " JOIN t.taxonNodes AS tn " : " ");
1405

    
1406
        String doTaxonMisappliedNameSubSelect =
1407
                     " SELECT %s.id "
1408
                   + " FROM Taxon t ";
1409

    
1410
        String doTaxonNameJoin = " JOIN t.name n ";
1411

    
1412
        String doSynonymSubSelect =
1413
                  " FROM Synonym s "
1414
                + " JOIN s.name sn "
1415
                + " LEFT JOIN s.acceptedTaxon ";
1416

    
1417
        String doSynonymNameJoin =
1418
                   " JOIN t.synonyms s "
1419
                 + " JOIN s.name sn";
1420

    
1421
        String doConceptRelationJoin =
1422
                   " LEFT JOIN t.relationsFromThisTaxon AS rft " +
1423
                   " LEFT JOIN rft.relatedTo AS rt " +
1424
                      (hasTaxonNodeFilter ? " LEFT JOIN rt.taxonNodes AS tn2 " : " ") +
1425
                   " LEFT JOIN rt.name AS n2" +
1426
                   " LEFT JOIN rft.type as rtype";
1427

    
1428
        String doCommonNamesJoin =
1429
                   " JOIN t.descriptions AS description "+
1430
                   " LEFT JOIN description.descriptionElements AS com " +
1431
                   " LEFT JOIN com.feature f ";
1432

    
1433

    
1434
        String doTreeWhere = classification == null ? "" : " AND tn.classification = :classification";
1435
        String doTreeForConceptRelationsWhere = classification == null ? "": " AND tn2.classification = :classification";
1436

    
1437
        String doSubtreeWhere = subtree == null? "":" AND tn.treeIndex like :treeIndexLike";
1438
        String doSubtreeForConceptRelationsWhere = subtree == null? "":" AND tn2.treeIndex like :treeIndexLike";
1439

    
1440
        String doAreaRestrictionWhere =  " e.area.uuid in (:namedAreasUuids)";
1441
        String doCommonNamesRestrictionWhere = " (com.class = 'CommonTaxonName' and com.name "+matchMode.getMatchOperator()+" :queryString )";
1442

    
1443
        String doSearchFieldWhere = " (%s." + searchField + " " + matchMode.getMatchOperator() + " :queryString OR "
1444
                + " %s.protectedTitleCache = TRUE AND %s.titleCache LIKE :protectedTitleQueryString) ";
1445

    
1446
        String doRelationshipTypeComparison = " rtype in (:rTypeSet) ";
1447

    
1448
        String taxonSubselect = null;
1449
        String synonymSubselect = null;
1450
        String conceptSelect = null;
1451
        String commonNameSubselect = null;
1452

    
1453
        if(hasTaxonNodeFilter){
1454
            if (!doConceptRelations){
1455
                if(doAreaRestriction){
1456
                    taxonSubselect = String.format(doAreaRestrictionSubSelect, "t") + doTaxonNameJoin +
1457
                            " WHERE (1=1) AND " + doAreaRestrictionWhere +
1458
                                doTreeWhere + doSubtreeWhere +
1459
                            "  AND " + String.format(doSearchFieldWhere, "n", "n", "n");
1460
                    synonymSubselect = String.format(doAreaRestrictionSubSelect, "s") + doSynonymNameJoin +
1461
                            " WHERE (1=1) AND " + doAreaRestrictionWhere +
1462
                                doTreeWhere + doSubtreeWhere +
1463
                            "  AND " + String.format(doSearchFieldWhere, "sn", "sn", "sn");
1464
                    commonNameSubselect =  String.format(doAreaRestrictionSubSelect, "t") + doCommonNamesJoin +
1465
                            " WHERE (1=1) AND " +  doAreaRestrictionWhere +
1466
                                 doTreeWhere + doSubtreeWhere +
1467
                            "  AND " + String.format(doSearchFieldWhere, "n", "n", "n") +
1468
                            "  AND " + doCommonNamesRestrictionWhere;
1469
                } else {//no area restriction
1470
                    taxonSubselect = String.format(doTaxonSubSelect, "t" )+ doTaxonNameJoin +
1471
                            " WHERE (1=1) " + doTreeWhere + doSubtreeWhere +
1472
                            "  AND " + String.format(doSearchFieldWhere, "n", "n", "n");
1473
                    synonymSubselect = String.format(doTaxonSubSelect, "s" ).replace("FROM Taxon ", doSynonymSubSelect) +  //we could also use default synonym handling here as a taxon node filter requires an accepted taxon (#9047)
1474
                            " WHERE (1=1) " + doTreeWhere + doSubtreeWhere +
1475
                            "  AND " + String.format(doSearchFieldWhere, "sn", "sn", "sn");
1476
                    commonNameSubselect =String.format(doTaxonSubSelect, "t" )+ doCommonNamesJoin +
1477
                            " WHERE (1=1) " + doTreeWhere + doSubtreeWhere +
1478
                            "  AND " + doCommonNamesRestrictionWhere;
1479
                }
1480
            }else{ //concept relations included
1481
                if(doAreaRestriction){
1482
                    conceptSelect = String.format(doAreaRestrictionConceptRelationSubSelect, "t") + doTaxonNameJoin + doConceptRelationJoin  +
1483
                            " WHERE " + doAreaRestrictionWhere +
1484
                            "  AND " + String.format(doSearchFieldWhere, "n", "n", "n") +
1485
                                 doTreeForConceptRelationsWhere + doSubtreeForConceptRelationsWhere +
1486
                            "  AND " + doRelationshipTypeComparison;
1487
                    taxonSubselect = String.format(doAreaRestrictionSubSelect, "t") + doTaxonNameJoin +
1488
                            " WHERE " + doAreaRestrictionWhere +
1489
                            "  AND " + String.format(doSearchFieldWhere, "n", "n", "n") +
1490
                                doTreeWhere + doSubtreeWhere;
1491
                    synonymSubselect = String.format(doAreaRestrictionSubSelect, "s") + doSynonymNameJoin +
1492
                            " WHERE " + doAreaRestrictionWhere +
1493
                                doTreeWhere + doSubtreeWhere +
1494
                            "  AND " + String.format(doSearchFieldWhere, "sn", "sn", "sn");
1495
                    commonNameSubselect= String.format(doAreaRestrictionSubSelect, "t")+ doCommonNamesJoin +
1496
                            " WHERE " + doAreaRestrictionWhere +
1497
                                doTreeWhere + doSubtreeWhere +
1498
                            "  AND " + doCommonNamesRestrictionWhere;
1499
                } else {//no area restriction
1500
                    conceptSelect = String.format(doTaxonMisappliedNameSubSelect, "t" ) + doTaxonNameJoin + doConceptRelationJoin +
1501
                            " WHERE " + String.format(doSearchFieldWhere, "n", "n", "n") +
1502
                                  doTreeForConceptRelationsWhere + doSubtreeForConceptRelationsWhere +
1503
                            "  AND " + doRelationshipTypeComparison;
1504
                    taxonSubselect = String.format(doTaxonSubSelect, "t" ) + doTaxonNameJoin +
1505
                            " WHERE " +  String.format(doSearchFieldWhere, "n", "n", "n") +
1506
                                 doTreeWhere + doSubtreeWhere;
1507
                    synonymSubselect = String.format(doTaxonSubSelect, "s" ).replace("FROM Taxon ", doSynonymSubSelect) + //we could also use default synonym handling here as a taxon node filter requires an accepted taxon (#9047)
1508
                            " WHERE (1=1) " + doTreeWhere + doSubtreeWhere +
1509
                            "  AND " +  String.format(doSearchFieldWhere, "sn", "sn", "sn");
1510
                    commonNameSubselect= String.format(doTaxonSubSelect, "t")+ doCommonNamesJoin +
1511
                            " WHERE (1=1) " + doTreeWhere + doSubtreeWhere +
1512
                            "  AND " + doCommonNamesRestrictionWhere;
1513
                }
1514
            }
1515
        } else { //classification = null && subtree = null
1516
            if(doAreaRestriction){
1517
                conceptSelect = String.format(doAreaRestrictionConceptRelationSubSelect, "t") + doTaxonNameJoin + doConceptRelationJoin +
1518
                        " WHERE " + doAreaRestrictionWhere +
1519
                        "  AND " + String.format(doSearchFieldWhere, "n", "n", "n")+
1520
                        "  AND " + doRelationshipTypeComparison;
1521
                taxonSubselect = String.format(doAreaRestrictionSubSelect, "t") + doTaxonNameJoin +
1522
                        " WHERE " + doAreaRestrictionWhere +
1523
                        "  AND " + String.format(doSearchFieldWhere, "n", "n", "n");
1524
                synonymSubselect = String.format(doAreaRestrictionSubSelect, "s") + doSynonymNameJoin +
1525
                        " WHERE " + doAreaRestrictionWhere +
1526
                        "  AND " + String.format(doSearchFieldWhere, "sn", "sn", "sn");
1527
                commonNameSubselect = String.format(doAreaRestrictionSubSelect, "t")+ doCommonNamesJoin +
1528
                        " WHERE " + doAreaRestrictionWhere +
1529
                        "  AND " + doCommonNamesRestrictionWhere;
1530
            } else { //no area restriction
1531
                conceptSelect = String.format(doTaxonMisappliedNameSubSelect, "t" ) + doTaxonNameJoin + doConceptRelationJoin +
1532
                        " WHERE " +  String.format(doSearchFieldWhere, "n", "n", "n") +
1533
                        " AND " + doRelationshipTypeComparison;
1534
                taxonSubselect = String.format(doTaxonSubSelect, "t" ) + doTaxonNameJoin +
1535
                        " WHERE " +  String.format(doSearchFieldWhere, "n", "n", "n");
1536
                synonymSubselect = String.format(doTaxonSubSelect, "s" ).replace("FROM Taxon ", doSynonymSubSelect) +
1537
                        " WHERE " +  String.format(doSearchFieldWhere, "sn", "sn", "sn");
1538
                commonNameSubselect = String.format(doTaxonSubSelect, "t" ) +doCommonNamesJoin +
1539
                        " WHERE "+  doCommonNamesRestrictionWhere;
1540
            }
1541
        }
1542

    
1543
        if (!includeUnpublished){
1544
            taxonSubselect   += " AND t.publish = :publish ";
1545
            synonymSubselect += " AND s.publish = :publish AND t.publish = :publish ";
1546
            commonNameSubselect += " AND t.publish = :publish ";
1547
            conceptSelect += " AND t.publish = :publish AND rt.publish = :publish ";
1548
        }
1549

    
1550
        Subselects result = new Subselects(taxonSubselect, synonymSubselect, conceptSelect, commonNameSubselect);
1551
        return result;
1552
    }
1553

    
1554
    private class Subselects{
1555
        String taxonSubselect;
1556
        String synonymSubselect;
1557
        String conceptSelect;
1558
        String commonNameSubselect;
1559
        private Subselects(String taxonSubselect, String synonymSubselect, String conceptSelect,
1560
                String commonNameSubselect) {
1561
            this.taxonSubselect = taxonSubselect;
1562
            this.synonymSubselect = synonymSubselect;
1563
            this.conceptSelect = conceptSelect;
1564
            this.commonNameSubselect = commonNameSubselect;
1565
        }
1566
    }
1567

    
1568
	@Override
1569
	public List<UuidAndTitleCache<Taxon>> getTaxaByCommonNameForEditor(
1570
			String titleSearchStringSqlized, Classification classification,
1571
			MatchMode matchMode, Set<NamedArea> namedAreas) {
1572

    
1573
		Query<Object[]> query = prepareTaxaByCommonName(titleSearchStringSqlized, classification, matchMode, namedAreas, null, null, false, true, Object[].class);
1574
        if (query != null){
1575
            List<Object[]> resultArray = query.list();
1576
            List<UuidAndTitleCache<Taxon>> returnResult = new ArrayList<>() ;
1577
            Object[] result;
1578
            for(int i = 0; i<resultArray.size();i++){
1579
            	result = resultArray.get(i);
1580
            	returnResult.add(new UuidAndTitleCache<>(Taxon.class, (UUID) result[0],(Integer)result[1], (String)result[2], new Boolean(result[4].toString()), null));
1581
            }
1582
            return returnResult;
1583
        }else{
1584
            return new ArrayList<>();
1585
        }
1586
	}
1587

    
1588
	@Override
1589
	public <S extends TaxonBase> long countByIdentifier(Class<S> clazz,
1590
			String identifier, DefinedTerm identifierType, TaxonNode subtreeFilter, MatchMode matchmode) {
1591
		if (subtreeFilter == null){
1592
			return countByIdentifier(clazz, identifier, identifierType, matchmode);
1593
		}
1594

    
1595
		Class<?> clazzParam = (clazz == null) ? type : clazz;
1596
		checkNotInPriorView("TaxonDaoHibernateImpl.countByIdentifier(T clazz, String identifier, DefinedTerm identifierType, TaxonNode subMatchMode matchmode)");
1597

    
1598
		boolean isTaxon = clazzParam == Taxon.class || clazzParam == TaxonBase.class;
1599
		boolean isSynonym = clazzParam == Synonym.class || clazzParam == TaxonBase.class;
1600

    
1601
		getSession().update(subtreeFilter);  //to avoid LIE when retrieving treeindex
1602
		String filterStr = "'" + subtreeFilter.treeIndex() + "%%'";
1603
		String accTreeJoin = isTaxon? " LEFT JOIN c.taxonNodes tn  " : "";
1604
		String synTreeJoin = isSynonym ? " LEFT JOIN c.acceptedTaxon as acc LEFT JOIN acc.taxonNodes synTn  " : "";
1605
		String accWhere = isTaxon ?  "tn.treeIndex like " + filterStr : "(1=0)";
1606
		String synWhere = isSynonym  ?  "synTn.treeIndex like " + filterStr : "(1=0)";
1607

    
1608
		String queryString = "SELECT count(*)  FROM %s as c " +
1609
                " INNER JOIN c.identifiers as ids " +
1610
                accTreeJoin +
1611
                synTreeJoin +
1612
                " WHERE (1=1) " +
1613
                	"  AND ( " + accWhere + " OR " + synWhere + ")";
1614
		queryString = String.format(queryString, clazzParam.getSimpleName());
1615

    
1616
		if (identifier != null){
1617
			if (matchmode == null || matchmode == MatchMode.EXACT){
1618
				queryString += " AND ids.identifier = '"  + identifier + "'";
1619
			}else {
1620
				queryString += " AND ids.identifier LIKE '" + matchmode.queryStringFrom(identifier)  + "'";
1621
			}
1622
		}
1623
		if (identifierType != null){
1624
        	queryString += " AND ids.type = :type";
1625
        }
1626

    
1627
		Query<Long> query = getSession().createQuery(queryString, Long.class);
1628
        if (identifierType != null){
1629
        	query.setParameter("type", identifierType);
1630
        }
1631

    
1632
		return query.uniqueResult();
1633
	}
1634

    
1635
	@Override
1636
	public <S extends TaxonBase> List<Object[]> findByIdentifier(
1637
			Class<S> clazz, String identifier, DefinedTerm identifierType, TaxonNode subtreeFilter,
1638
			MatchMode matchmode, boolean includeEntity,
1639
			Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
1640

    
1641
		checkNotInPriorView("TaxonDaoHibernateImpl.findByIdentifier(T clazz, String identifier, DefinedTerm identifierType, MatchMode matchmode, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)");
1642
		Class<?> clazzParam = clazz == null ? type : clazz;
1643

    
1644
		boolean isTaxon = clazzParam == Taxon.class || clazzParam == TaxonBase.class;
1645
		boolean isSynonym = clazzParam == Synonym.class || clazzParam == TaxonBase.class;
1646
		getSession().update(subtreeFilter);  //to avoid LIE when retrieving treeindex
1647
		String filterStr = "'" + subtreeFilter.treeIndex() + "%%'";
1648
		String accTreeJoin = isTaxon? " LEFT JOIN c.taxonNodes tn  " : "";
1649
		String synTreeJoin = isSynonym ? " LEFT JOIN c.acceptedTaxon as acc LEFT JOIN acc.taxonNodes synTn  " : "";
1650
		String accWhere = isTaxon ?  "tn.treeIndex like " + filterStr : "(1=0)";
1651
		String synWhere = isSynonym  ?  "synTn.treeIndex like " + filterStr : "(1=0)";
1652

    
1653
		String queryString = " SELECT ids.type, ids.identifier, %s " +
1654
				" FROM %s as c " +
1655
                " INNER JOIN c.identifiers as ids " +
1656
                accTreeJoin +
1657
				synTreeJoin +
1658
                " WHERE (1=1) " +
1659
                	" AND ( " + accWhere + " OR " + synWhere + ")";
1660
		queryString = String.format(queryString, (includeEntity ? "c":"c.uuid, c.titleCache") , clazzParam.getSimpleName());
1661

    
1662
		//Matchmode and identifier
1663
		if (identifier != null){
1664
			if (matchmode == null || matchmode == MatchMode.EXACT){
1665
				queryString += " AND ids.identifier = '"  + identifier + "'";
1666
			}else {
1667
				queryString += " AND ids.identifier LIKE '" + matchmode.queryStringFrom(identifier)  + "'";
1668
			}
1669
		}
1670
        if (identifierType != null){
1671
        	queryString += " AND ids.type = :type";
1672
        }
1673
        //order
1674
        queryString +=" ORDER BY ids.type.uuid, ids.identifier, c.uuid ";
1675

    
1676
		Query<Object[]> query = getSession().createQuery(queryString, Object[].class);
1677

    
1678
		//parameters
1679
		if (identifierType != null){
1680
        	query.setParameter("type", identifierType);
1681
        }
1682

    
1683
        //paging
1684
		addPageSizeAndNumber(query, pageSize, pageNumber);
1685

    
1686
        List<Object[]> results = query.list();
1687
        //initialize
1688
        if (includeEntity){
1689
        	List<S> entities = new ArrayList<>();
1690
        	for (Object[] result : results){
1691
        		entities.add((S)result[2]);
1692
        	}
1693
        	defaultBeanInitializer.initializeAll(entities, propertyPaths);
1694
        }
1695
        return results;
1696
	}
1697

    
1698
    /**
1699
     * {@inheritDoc}
1700
     *
1701
     * @see #countByIdentifier(Class, String, DefinedTerm, TaxonNode, MatchMode)
1702
     */
1703
    @Override
1704
    public <S extends TaxonBase> long countByMarker(Class<S> clazz, MarkerType markerType,
1705
            Boolean markerValue, TaxonNode subtreeFilter) {
1706
        if (markerType == null){
1707
            return 0;
1708
        }
1709

    
1710
        if (subtreeFilter == null){
1711
            return countByMarker(clazz, markerType, markerValue);
1712
        }
1713

    
1714
        Class<?> clazzParam = clazz == null ? type : clazz;
1715
        checkNotInPriorView("TaxonDaoHibernateImpl.countByMarker(Class<S> clazz, DefinedTerm markerType, boolean markerValue, TaxonNode subtreeFilter)");
1716

    
1717
        boolean isTaxon = clazzParam == Taxon.class || clazzParam == TaxonBase.class;
1718
        boolean isSynonym = clazzParam == Synonym.class || clazzParam == TaxonBase.class;
1719

    
1720
        getSession().update(subtreeFilter);  //to avoid LIE when retrieving treeindex
1721
        String filterStr = "'" + subtreeFilter.treeIndex() + "%%'";
1722
        String accTreeJoin = isTaxon? " LEFT JOIN c.taxonNodes tn  " : "";
1723
        String synTreeJoin = isSynonym ? " LEFT JOIN c.acceptedTaxon acc LEFT JOIN acc.taxonNodes synTn  " : "";
1724
        String accWhere = isTaxon ?  "tn.treeIndex like " + filterStr : "(1=0)";
1725
        String synWhere = isSynonym  ?  "synTn.treeIndex like " + filterStr : "(1=0)";
1726

    
1727
        String queryString = "SELECT count(*)  FROM %s as c " +
1728
                " INNER JOIN c.markers as mks " +
1729
                accTreeJoin +
1730
                synTreeJoin +
1731
                " WHERE (1=1) " +
1732
                    "  AND ( " + accWhere + " OR " + synWhere + ")";
1733
        queryString = String.format(queryString, clazzParam.getSimpleName());
1734

    
1735
        if (markerValue != null){
1736
            queryString += " AND mks.flag = :flag";
1737
        }
1738
        queryString += " AND mks.markerType = :type";
1739

    
1740
        Query<Long> query = getSession().createQuery(queryString, Long.class);
1741
        query.setParameter("type", markerType);
1742
        if (markerValue != null){
1743
            query.setParameter("flag", markerValue);
1744
        }
1745

    
1746
        Long c = query.uniqueResult();
1747
        return c;
1748
    }
1749

    
1750
    @Override
1751
    public <S extends TaxonBase> List<Object[]> findByMarker(Class<S> clazz, MarkerType markerType,
1752
            Boolean markerValue, TaxonNode subtreeFilter, boolean includeEntity,
1753
            TaxonTitleType titleType, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
1754
        checkNotInPriorView("TaxonDaoHibernateImpl.findByMarker(T clazz, String identifier, DefinedTerm identifierType, MatchMode matchmode, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)");
1755
        if (markerType == null){
1756
            return new ArrayList<Object[]>();
1757
        }
1758
        if (titleType == null){
1759
            titleType = TaxonTitleType.DEFAULT();
1760
        }
1761

    
1762
        Class<?> clazzParam = clazz == null ? type : clazz;
1763

    
1764
        boolean isTaxon = clazzParam == Taxon.class || clazzParam == TaxonBase.class;
1765
        boolean isSynonym = clazzParam == Synonym.class || clazzParam == TaxonBase.class;
1766
        getSession().update(subtreeFilter);  //to avoid LIE when retrieving treeindex
1767
        String filterStr = "'" + subtreeFilter.treeIndex() + "%%'";
1768
        String accTreeJoin = isTaxon? " LEFT JOIN c.taxonNodes tn  " : "";
1769
        String synTreeJoin = isSynonym ? " LEFT JOIN c.acceptedTaxon as acc LEFT JOIN acc.taxonNodes synTn  " : "";
1770
        String accWhere = isTaxon ?  "tn.treeIndex like " + filterStr : "(1=0)";
1771
        String synWhere = isSynonym  ?  "synTn.treeIndex like " + filterStr : "(1=0)";
1772
        String selectParams = includeEntity ? "c" : titleType.hqlReplaceSelect("c.uuid, c.titleCache", "c.titleCache");
1773
        String titleTypeJoin = includeEntity ? "" : titleType.hqlJoin();
1774

    
1775
        String queryString = "SELECT mks.markerType, mks.flag, %s " +
1776
                " FROM %s as c " +
1777
                " INNER JOIN c.markers as mks " +
1778
                titleTypeJoin +
1779
                accTreeJoin +
1780
                synTreeJoin +
1781
                " WHERE (1=1) " +
1782
                    " AND ( " + accWhere + " OR " + synWhere + ")";
1783
        queryString = String.format(queryString, selectParams, clazzParam.getSimpleName());
1784

    
1785
        //type and value
1786
        if (markerValue != null){
1787
            queryString += " AND mks.flag = :flag";
1788
        }
1789
        queryString += " AND mks.markerType = :type";
1790
        //order
1791
        queryString +=" ORDER BY mks.markerType.uuid, mks.flag, c.uuid ";
1792

    
1793
        Query<Object[]> query = getSession().createQuery(queryString, Object[].class);
1794

    
1795
        //parameters
1796
        query.setParameter("type", markerType);
1797
        if (markerValue != null){
1798
            query.setParameter("flag", markerValue);
1799
        }
1800

    
1801
        //paging
1802
        addPageSizeAndNumber(query, pageSize, pageNumber);
1803

    
1804
        List<Object[]> results = query.list();
1805
        //initialize
1806
        if (includeEntity){
1807
            List<S> entities = new ArrayList<S>();
1808
            for (Object[] result : results){
1809
                entities.add((S)result[2]);
1810
            }
1811
            defaultBeanInitializer.initializeAll(entities, propertyPaths);
1812
        }
1813
        return results;
1814
    }
1815

    
1816
    @Override
1817
    public long countTaxonRelationships(Set<TaxonRelationshipType> types) {
1818
        Criteria criteria = getSession().createCriteria(TaxonRelationship.class);
1819

    
1820
        if (types != null) {
1821
            if (types.isEmpty()){
1822
                return 0l;
1823
            }else{
1824
                criteria.add(Restrictions.in("type", types) );
1825
            }
1826
        }
1827
        //count
1828
        criteria.setProjection(Projections.rowCount());
1829
        long result = (Long)criteria.uniqueResult();
1830

    
1831
        return result;
1832
    }
1833

    
1834
    @Override
1835
    public List<TaxonRelationship> getTaxonRelationships(Set<TaxonRelationshipType> types,
1836
            Integer pageSize, Integer pageNumber,
1837
            List<OrderHint> orderHints, List<String> propertyPaths) {
1838

    
1839
        Criteria criteria = getCriteria(TaxonRelationship.class);
1840
        if (types != null) {
1841
            if (types.isEmpty()){
1842
                return new ArrayList<>();
1843
            }else{
1844
                criteria.add(Restrictions.in("type", types) );
1845
            }
1846
        }
1847
        addOrder(criteria,orderHints);
1848
        addPageSizeAndNumber(criteria, pageSize, pageNumber);
1849

    
1850
        @SuppressWarnings("unchecked")
1851
        List<TaxonRelationship> results = criteria.list();
1852
        defaultBeanInitializer.initializeAll(results, propertyPaths);
1853

    
1854
        return results;
1855
    }
1856

    
1857
    @Override
1858
    public  List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCache(Integer limit, String pattern){
1859
        Session session = getSession();
1860
        Query<SortableTaxonNodeQueryResult> query = null;
1861
        if (pattern != null){
1862
            query = session.createQuery(
1863
                    "SELECT new " + SortableTaxonNodeQueryResult.class.getName() + "("
1864
                  + " tb.uuid, tb.id, tb.titleCache, tb.name.rank "
1865
                  + ") "
1866
                  + " FROM TaxonBase as tb "
1867
                  + " WHERE tb.titleCache LIKE :pattern",
1868
                  SortableTaxonNodeQueryResult.class);
1869
            pattern = pattern.replace("*", "%");
1870
            pattern = pattern.replace("?", "_");
1871
            pattern = pattern + "%";
1872
            query.setParameter("pattern", pattern);
1873
        } else {
1874
            query = session.createQuery(
1875
                    " SELECT new " + SortableTaxonNodeQueryResult.class.getName()
1876
                    + "       (tb.uuid, taxonBase.id, tb.titleCache, tb.name.rank) "
1877
                    + " FROM TaxonBase AS tb",
1878
                    SortableTaxonNodeQueryResult.class);
1879
        }
1880
        if (limit != null){
1881
           query.setMaxResults(limit);
1882
        }
1883

    
1884
        return getUuidAndTitleCache(query);
1885
    }
1886

    
1887
    @Override
1888
    protected List<UuidAndTitleCache<TaxonBase>> getUuidAndTitleCache(Query query){
1889
        List<UuidAndTitleCache<TaxonBase>> list = new ArrayList<>();
1890

    
1891
        List<SortableTaxonNodeQueryResult> result = query.list();
1892
        if (!result.isEmpty()){
1893
            Collections.sort(result, new SortableTaxonNodeQueryResultComparator());
1894
        }
1895

    
1896
        for(SortableTaxonNodeQueryResult stnqr : result){
1897
            list.add(new UuidAndTitleCache<TaxonBase>(stnqr.getTaxonNodeUuid(),stnqr.getTaxonNodeId(), stnqr.getTaxonTitleCache()));
1898
        }
1899
        return list;
1900
    }
1901

    
1902

    
1903
}
(3-3/6)