Project

General

Profile

Revision 9fc2a8a0

ID9fc2a8a0de9a103ea0dbb1e9391000753b744ae2
Parent 396a7563
Child 11d3d7eb

Added by Andreas Kohlbecker almost 2 years ago

fix #7467 taxon name parts lookup method with support for wildcard searches, TaxonNameParts and filter

View differences:

cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/common/CdmEntityDaoBase.java
21 21
import java.util.UUID;
22 22

  
23 23
import org.apache.log4j.Logger;
24
import org.apache.lucene.search.Sort;
25
import org.apache.lucene.search.SortField;
26 24
import org.hibernate.Criteria;
27 25
import org.hibernate.FlushMode;
28 26
import org.hibernate.HibernateException;
......
40 38
import org.hibernate.criterion.Restrictions;
41 39
import org.hibernate.envers.query.AuditQuery;
42 40
import org.hibernate.metadata.ClassMetadata;
43
import org.hibernate.search.FullTextQuery;
44 41
import org.hibernate.type.Type;
45 42
import org.joda.time.DateTime;
46 43
import org.springframework.beans.factory.annotation.Autowired;
......
809 806
        }
810 807
    }
811 808

  
812
    protected void addCriteria(Criteria criteria, List<Criterion> criterion) {
813
        if(criterion != null) {
814
            for(Criterion c : criterion) {
815
                criteria.add(c);
816
            }
817
        }
818

  
819
    }
820 809

  
821
    protected void addOrder(FullTextQuery fullTextQuery, List<OrderHint> orderHints) {
822
        //FIXME preliminary hardcoded type:
823
    	SortField.Type type = SortField.Type.STRING;
824

  
825
    	if(orderHints != null && !orderHints.isEmpty()) {
826
            org.apache.lucene.search.Sort sort = new Sort();
827
            SortField[] sortFields = new SortField[orderHints.size()];
828
            for(int i = 0; i < orderHints.size(); i++) {
829
                OrderHint orderHint = orderHints.get(i);
830
                switch(orderHint.getSortOrder()) {
831
                case ASCENDING:
832
                    sortFields[i] = new SortField(orderHint.getPropertyName(), type, true);
833
                    break;
834
                case DESCENDING:
835
                default:
836
                    sortFields[i] = new SortField(orderHint.getPropertyName(), type, false);
837 810

  
838
                }
839
            }
840
            sort.setSort(sortFields);
841
            fullTextQuery.setSort(sort);
842

  
843
        }
844
    }
845 811

  
846 812
    @Override
847 813
    public List<T> list(Integer limit, Integer start, List<OrderHint> orderHints) {
......
885 851
        return results;
886 852
    }
887 853

  
888
    /**
889
     * @param limit
890
     * @param start
891
     * @param criteria
892
     */
893
    protected void addLimitAndStart(Integer limit, Integer start, Criteria criteria) {
894
        if(limit != null) {
895
            if(start != null) {
896
                criteria.setFirstResult(start);
897
            } else {
898
                criteria.setFirstResult(0);
899
            }
900
            criteria.setMaxResults(limit);
901
        }
902
    }
903

  
904 854

  
905 855
    public <S extends T> List<S> list(Class<S> type, Integer limit, Integer start, List<OrderHint> orderHints) {
906 856
        return list(type,limit,start,orderHints,null);
......
926 876
        return type;
927 877
    }
928 878

  
929
    protected void setPagingParameter(Query query, Integer pageSize, Integer pageNumber){
879
    protected void setPagingParameter(Query query, Integer pageSize, Integer pageIndex){
930 880
        if(pageSize != null) {
931 881
            query.setMaxResults(pageSize);
932
            if(pageNumber != null) {
933
                query.setFirstResult(pageNumber * pageSize);
882
            if(pageIndex != null) {
883
                query.setFirstResult(pageIndex * pageSize);
934 884
            } else {
935 885
                query.setFirstResult(0);
936 886
            }
937 887
        }
938 888
    }
939 889

  
940
    protected void setPagingParameter(AuditQuery query, Integer pageSize, Integer pageNumber){
890
    protected void setPagingParameter(AuditQuery query, Integer pageSize, Integer pageIndex){
941 891
        if(pageSize != null) {
942 892
            query.setMaxResults(pageSize);
943
            if(pageNumber != null) {
944
                query.setFirstResult(pageNumber * pageSize);
893
            if(pageIndex != null) {
894
                query.setFirstResult(pageIndex * pageSize);
945 895
            } else {
946 896
                query.setFirstResult(0);
947 897
            }
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/common/DaoBase.java
18 18
import java.util.Iterator;
19 19
import java.util.List;
20 20
import java.util.Map;
21
import java.util.Optional;
21 22
import java.util.Set;
22 23

  
23 24
import org.apache.log4j.Logger;
25
import org.apache.lucene.search.Sort;
26
import org.apache.lucene.search.SortField;
24 27
import org.hibernate.Criteria;
25 28
import org.hibernate.HibernateException;
26 29
import org.hibernate.Session;
27 30
import org.hibernate.SessionFactory;
31
import org.hibernate.criterion.Criterion;
32
import org.hibernate.search.FullTextQuery;
28 33
import org.springframework.beans.factory.annotation.Autowired;
29 34

  
30 35
import eu.etaxonomy.cdm.persistence.query.OrderHint;
......
77 82

  
78 83
    }
79 84

  
80
    protected void addOrder(Criteria criteria, List<OrderHint> orderHints) {
85
    /**
86
     * Splits a set of e.g. query parameters into a list of sets with size <code>splitSize</code>.
87
     * Only the last set may be smaller if the collection's size is not an exact multiple of split size.
88
     * @param collection the collection to split
89
     * @param splitSize the split size
90
     * @return a list of collections
91
     */
92
    protected <T extends Object> List<Collection<T>> splitCollection(Set<T> collection, int splitSize) {
93
        if (splitSize < 1){
94
            throw new IllegalArgumentException("Split size must not be positive integer");
95
        }
96
        List<Collection<T>> result = new ArrayList<>();
97
        Iterator<T> it = collection.iterator();
98
        int i = 0;
99
        Set<T> nextCollection = new HashSet<>();
100
        while (it.hasNext()){
101
            nextCollection.add(it.next());
102
            i++;
103
            if (i == splitSize){
104
                result.add(nextCollection);
105
                nextCollection = new HashSet<>();
106
                i = 0;
107
            }
108
        }
109
        if (!nextCollection.isEmpty()){
110
            result.add(nextCollection);
111
        }
112
        return result;
113
    }
81 114

  
82
        if(orderHints != null){
83
            Collections.sort(orderHints, new OrderHintComparator());
115
    // -------------- hql, query and criteria helper methods -------------- //
116

  
117
    protected void addFieldPredicate(StringBuilder hql, String field, Optional<String> value) {
118
        if(value != null){
119
            hql.append("and " + field);
120
            if(value.isPresent()){
121
                if(value.get().contains("*")){
122
                    hql.append(" like '" + value.get().replace('*', '%') + "' ");
123
                } else {
124
                    hql.append(" = '" + value.get() + "' ");
125
                }
126
            } else {
127
                hql.append(" is null ");
128
            }
129
        }
130
    }
131
    /**
132
     * @param limit
133
     * @param start
134
     * @param criteria
135
     */
136
    protected void addLimitAndStart(Integer limit, Integer start, Criteria criteria) {
137
        if(limit != null) {
138
            if(start != null) {
139
                criteria.setFirstResult(start);
140
            } else {
141
                criteria.setFirstResult(0);
142
            }
143
            criteria.setMaxResults(limit);
144
        }
145
    }
84 146

  
85
            Map<String,Criteria> criteriaMap = new HashMap<>();
86
            for(OrderHint orderHint : orderHints){
87
                orderHint.add(criteria, criteriaMap);
147
    protected void addCriteria(Criteria criteria, List<Criterion> criterion) {
148
        if(criterion != null) {
149
            for(Criterion c : criterion) {
150
                criteria.add(c);
88 151
            }
89 152
        }
90 153
    }
......
113 176
        }
114 177
        return orderString;
115 178
    }
179
    protected void addOrder(Criteria criteria, List<OrderHint> orderHints) {
180

  
181
        if(orderHints != null){
182
            Collections.sort(orderHints, new OrderHintComparator());
116 183

  
184
            Map<String,Criteria> criteriaMap = new HashMap<>();
185
            for(OrderHint orderHint : orderHints){
186
                orderHint.add(criteria, criteriaMap);
187
            }
188
        }
189
    }
190
    protected void addOrder(FullTextQuery fullTextQuery, List<OrderHint> orderHints) {
191
        //FIXME preliminary hardcoded type:
192
        SortField.Type type = SortField.Type.STRING;
193

  
194
        if(orderHints != null && !orderHints.isEmpty()) {
195
            org.apache.lucene.search.Sort sort = new Sort();
196
            SortField[] sortFields = new SortField[orderHints.size()];
197
            for(int i = 0; i < orderHints.size(); i++) {
198
                OrderHint orderHint = orderHints.get(i);
199
                switch(orderHint.getSortOrder()) {
200
                case ASCENDING:
201
                    sortFields[i] = new SortField(orderHint.getPropertyName(), type, true);
202
                    break;
203
                case DESCENDING:
204
                default:
205
                    sortFields[i] = new SortField(orderHint.getPropertyName(), type, false);
206
                }
207
            }
208
            sort.setSort(sortFields);
209
            fullTextQuery.setSort(sort);
210
        }
211
    }
117 212

  
118 213
    /**
119
     * Splits a set of e.g. query parameters into a list of sets with size <code>splitSize</code>.
120
     * Only the last set may be smaller if the collection's size is not an exact multiple of split size.
121
     * @param collection the collection to split
122
     * @param splitSize the split size
123
     * @return a list of collections
214
     * @param hql
215
     * @param orderHints
124 216
     */
125
    protected <T extends Object> List<Collection<T>> splitCollection(Set<T> collection, int splitSize) {
126
        if (splitSize < 1){
127
            throw new IllegalArgumentException("Split size must not be positive integer");
217
    protected void addOrder(StringBuilder hql, String alias, List<OrderHint> orderHints) {
218
        boolean orderByAdded = false;
219
        String fieldPrefix = "";
220
        if(alias != null){
221
            fieldPrefix = alias + ".";
128 222
        }
129
        List<Collection<T>> result = new ArrayList<>();
130
        Iterator<T> it = collection.iterator();
131
        int i = 0;
132
        Set<T> nextCollection = new HashSet<>();
133
        while (it.hasNext()){
134
            nextCollection.add(it.next());
135
            i++;
136
            if (i == splitSize){
137
                result.add(nextCollection);
138
                nextCollection = new HashSet<>();
139
                i = 0;
223
        if(orderHints != null && !orderHints.isEmpty()){
224
            for(OrderHint oh : orderHints){
225
                if(oh != null){
226
                    if(!orderByAdded){
227
                        hql.append(" order by ");
228
                    }
229
                    hql.append(fieldPrefix + oh.toHql() + (orderByAdded ? ", ": " "));
230
                    orderByAdded = true;
231
                }
140 232
            }
233

  
141 234
        }
142
        if (!nextCollection.isEmpty()){
143
            result.add(nextCollection);
144
        }
145
        return result;
146 235
    }
147 236

  
237
   // ---------------------------- //
238

  
148 239
}
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/name/TaxonNameDaoHibernateImpl.java
12 12
import java.util.ArrayList;
13 13
import java.util.HashMap;
14 14
import java.util.List;
15
import java.util.Optional;
15 16
import java.util.Set;
16 17
import java.util.UUID;
17 18

  
......
54 55
import eu.etaxonomy.cdm.persistence.dao.name.IHomotypicalGroupDao;
55 56
import eu.etaxonomy.cdm.persistence.dao.name.ITaxonNameDao;
56 57
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
58
import eu.etaxonomy.cdm.persistence.dto.TaxonNameParts;
57 59
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
58 60
import eu.etaxonomy.cdm.persistence.query.MatchMode;
59 61
import eu.etaxonomy.cdm.persistence.query.OrderHint;
......
943 945
		return nameRecords;
944 946
    }
945 947

  
948
    /**
949
     * {@inheritDoc}
950
     */
951
    @Override
952
    public List<TaxonNameParts> findTaxonNameParts(Optional<String> genusOrUninomial,
953
            Optional<String> infraGenericEpithet, Optional<String> specificEpithet,
954
            Optional<String> infraSpecificEpithet, Rank rank, Integer pageSize, Integer pageIndex, List<OrderHint> orderHints) {
955

  
956
        StringBuilder hql = prepareFindTaxonNameParts(false, genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet, rank);
957
        addOrder(hql, "n", orderHints);
958
        Query query = getSession().createQuery(hql.toString());
959
        if(rank != null){
960
            query.setParameter("rank", rank);
961
        }
962
        setPagingParameter(query, pageSize, pageIndex);
963
        List<?> result = query.list();
964
        return (List<TaxonNameParts>) result;
965
    }
966

  
967

  
968

  
969
    /**
970
     * {@inheritDoc}
971
     */
972
    @Override
973
    public long countTaxonNameParts(Optional<String> genusOrUninomial, Optional<String> infraGenericEpithet,
974
            Optional<String> specificEpithet, Optional<String> infraSpecificEpithet, Rank rank) {
975

  
976
        StringBuilder hql = prepareFindTaxonNameParts(true, genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet, rank);
977
        Query query = getSession().createQuery(hql.toString());
978
        if(rank != null){
979
            query.setParameter("rank", rank);
980
        }
981

  
982
        Object count = query.uniqueResult();
983
        return (Long) count;
984
    }
985

  
986
    /**
987
     * @return
988
     */
989
    private StringBuilder prepareFindTaxonNameParts(boolean doCount, Optional<String> genusOrUninomial,
990
            Optional<String> infraGenericEpithet, Optional<String> specificEpithet,
991
            Optional<String> infraSpecificEpithet, Rank rank) {
992

  
993
        StringBuilder hql = new StringBuilder();
994
        if(doCount){
995
            hql.append("select count(n.id) ");
996
        } else {
997
            hql.append("select new eu.etaxonomy.cdm.persistence.dto.TaxonNameParts(n.id, n.rank, n.genusOrUninomial, n.infraGenericEpithet, n.specificEpithet, n.infraSpecificEpithet) ");
998
        }
999
        hql.append("from TaxonName n where 1 = 1 ");
1000

  
1001
        if(rank != null){
1002
            hql.append("and n.rank = :rank ");
1003
        }
1004

  
1005
        addFieldPredicate(hql, "n.genusOrUninomial", genusOrUninomial);
1006
        addFieldPredicate(hql, "n.infraGenericEpithet", infraGenericEpithet);
1007
        addFieldPredicate(hql, "n.specificEpithet", specificEpithet);
1008
        addFieldPredicate(hql, "n.infraSpecificEpithet", infraSpecificEpithet);
1009

  
1010
        return hql;
1011
    }
1012

  
946 1013

  
947 1014

  
948 1015

  
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/name/ITaxonNameDao.java
10 10

  
11 11
import java.util.HashMap;
12 12
import java.util.List;
13
import java.util.Optional;
13 14
import java.util.UUID;
14 15

  
15 16
import org.hibernate.criterion.Criterion;
......
27 28
import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
28 29
import eu.etaxonomy.cdm.model.name.TypeDesignationStatusBase;
29 30
import eu.etaxonomy.cdm.persistence.dao.common.IIdentifiableDao;
30
import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;
31
import eu.etaxonomy.cdm.persistence.dto.TaxonNameParts;
31 32
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
32 33
import eu.etaxonomy.cdm.persistence.query.MatchMode;
33 34
import eu.etaxonomy.cdm.persistence.query.OrderHint;
......
332 333
	public IZoologicalName findZoologicalNameByUUID(UUID uuid);
333 334

  
334 335
	List<HashMap<String, String>> getNameRecords();
336

  
337
	/**
338
	 * Supports using wildcards in the query parameters.
339
	 * If a name part passed to the method contains the asterisk character ('*') it will be translated into '%' the related field is search with a LIKE clause.
340
	 * <p>
341
	 * A query parameter which is passed as <code>NULL</code> value will be ignored. A parameter passed as {@link Optional} object containing a <code>NULL</code> value will be used
342
	 * to filter select taxon names where the according field is <code>null</code>.
343
	 *
344
	 * @param genusOrUninomial
345
	 * @param infraGenericEpithet
346
	 * @param specificEpithet
347
	 * @param infraSpecificEpithet
348
	 * @param rank
349
	 *     Only name having the specified rank are taken into account.
350
	 * @return
351
	 */
352
	public List<TaxonNameParts> findTaxonNameParts(Optional<String> genusOrUninomial, Optional<String> infraGenericEpithet, Optional<String> specificEpithet,
353
	        Optional<String> infraSpecificEpithet, Rank rank, Integer pageSize, Integer pageIndex, List<OrderHint> orderHints);
354
    /**
355
     * Count method complementing {@link #findTaxonNameParts(Optional, Optional, Optional, Optional, Rank)}
356
     *
357
     * @param genusOrUninomial
358
     * @param infraGenericEpithet
359
     * @param specificEpithet
360
     * @param infraSpecificEpithet
361
     * @param rank
362
     *     Only name having the specified rank are taken into account.
363
     * @return
364
     */
365
    public long countTaxonNameParts(Optional<String> genusOrUninomial, Optional<String> infraGenericEpithet, Optional<String> specificEpithet, Optional<String> infraSpecificEpithet, Rank rank);
335 366
}
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dto/TaxonNameParts.java
8 8
*/
9 9
package eu.etaxonomy.cdm.persistence.dto;
10 10

  
11
import java.util.UUID;
12

  
11 13
import eu.etaxonomy.cdm.model.name.Rank;
12
import eu.etaxonomy.cdm.model.name.TaxonName;
13 14

  
14 15
public class TaxonNameParts {
15 16

  
16
    Integer taxonNameId;
17
    protected Integer taxonNameId;
17 18

  
18
    Rank rank;
19
    protected UUID taxonNameUuid;
19 20

  
20
    private String uninomial;
21
    protected Rank rank;
21 22

  
22
    private String infraGenericEpithet;
23
    protected String genusOrUninomial;
23 24

  
24
    private String specificEpithet;
25
    protected String infraGenericEpithet;
25 26

  
26
    private String infraSpecificEpithet;
27
    protected String specificEpithet;
27 28

  
29
    protected String infraSpecificEpithet;
28 30

  
29 31
    /**
30
     * @param taxonNameId
31
     * @param rank
32
     * @param uninomial
33
     * @param infraGenericEpithet
34
     * @param specificEpithet
35
     * @param infraSpecificEpithet
32
     * @return the taxonNameId
33
     */
34
    public Integer getTaxonNameId() {
35
        return taxonNameId;
36
    }
37

  
38

  
39
    /**
40
     * @param taxonNameId the taxonNameId to set
36 41
     */
37
//    public TaxonNameParts(Integer taxonNameId, String uninomial, String infraGenericEpithet,
38
//            String specificEpithet, String infraSpecificEpithet) {
39
//        super();
40
//        this.taxonNameId = taxonNameId;
41
//        this.uninomial = uninomial;
42
//        this.infraGenericEpithet = infraGenericEpithet;
43
//        this.specificEpithet = specificEpithet;
44
//        this.infraSpecificEpithet = infraSpecificEpithet;
45
//    }
42
    public void setTaxonNameId(Integer taxonNameId) {
43
        this.taxonNameId = taxonNameId;
44
    }
45

  
46

  
47
    /**
48
     * @return the rank
49
     */
50
    public Rank getRank() {
51
        return rank;
52
    }
53

  
54

  
55
    /**
56
     * @param rank the rank to set
57
     */
58
    public void setRank(Rank rank) {
59
        this.rank = rank;
60
    }
61

  
62

  
63
    /**
64
     * @return the uninomial
65
     */
66
    public String getGenusOrUninomial() {
67
        return genusOrUninomial;
68
    }
69

  
70

  
71
    /**
72
     * @param uninomial the genusOrUninomial to set
73
     */
74
    public void setGenusOrUninomial(String genusOrUninomial) {
75
        this.genusOrUninomial = genusOrUninomial;
76
    }
77

  
78

  
79
    /**
80
     * @return the infraGenericEpithet
81
     */
82
    public String getInfraGenericEpithet() {
83
        return infraGenericEpithet;
84
    }
85

  
86

  
87
    /**
88
     * @param infraGenericEpithet the infraGenericEpithet to set
89
     */
90
    public void setInfraGenericEpithet(String infraGenericEpithet) {
91
        this.infraGenericEpithet = infraGenericEpithet;
92
    }
93

  
94

  
95
    /**
96
     * @return the specificEpithet
97
     */
98
    public String getSpecificEpithet() {
99
        return specificEpithet;
100
    }
101

  
102

  
103
    /**
104
     * @param specificEpithet the specificEpithet to set
105
     */
106
    public void setSpecificEpithet(String specificEpithet) {
107
        this.specificEpithet = specificEpithet;
108
    }
109

  
110

  
111
    /**
112
     * @return the infraSpecificEpithet
113
     */
114
    public String getInfraSpecificEpithet() {
115
        return infraSpecificEpithet;
116
    }
117

  
118

  
119
    /**
120
     * @param infraSpecificEpithet the infraSpecificEpithet to set
121
     */
122
    public void setInfraSpecificEpithet(String infraSpecificEpithet) {
123
        this.infraSpecificEpithet = infraSpecificEpithet;
124
    }
46 125

  
47 126

  
48 127
    /**
......
53 132
     * @param specificEpithet
54 133
     * @param infraSpecificEpithet
55 134
     */
56
    public TaxonNameParts(Integer taxonNameId, Rank rank, String uninomial, String infraGenericEpithet,
135
    public TaxonNameParts(Integer taxonNameId, Rank rank, String genusOrUninomial, String infraGenericEpithet,
57 136
            String specificEpithet, String infraSpecificEpithet) {
58 137
        super();
59 138
        this.taxonNameId = taxonNameId;
60 139
        this.rank = rank;
61
        this.uninomial = uninomial;
140
        this.genusOrUninomial = genusOrUninomial;
62 141
        this.infraGenericEpithet = infraGenericEpithet;
63 142
        this.specificEpithet = specificEpithet;
64 143
        this.infraSpecificEpithet = infraSpecificEpithet;
......
66 145

  
67 146

  
68 147
    /**
69
     * @param tn
70
     * @return
148
     *
71 149
     */
72
    public String nameRankSpecificNamePart(TaxonName tn) {
73
        if(rank.isSpecies()){
74
            return tn.getGenusOrUninomial();
75
        }
76
        if(rank.isInfraSpecific()){
77
            return tn.getSpecificEpithet();
78
        }
79
        return "--ERROR: INVALID RANK (" + rank.getLabel() + ")--";
150
    public TaxonNameParts() {
80 151
    }
81 152

  
82 153

  
83
    public String uninomialQueryString(String query){
84
         if(rank.isLower(Rank.GENUS())){
85
            return uninomial;
86
        } else {
87
            return query;
154
    /**
155
     * @param tn
156
     * @return
157
     */
158
    public String rankSpecificNamePart() {
159
        if(rank.isGenus() || rank.isHigher(Rank.GENUS())){
160
            return getGenusOrUninomial();
88 161
        }
89
    }
90

  
91
    public String infraGenericEpithet(String query){
92 162
        if(rank.isInfraGeneric()){
93
            return query;
94
        } else if(rank.isLower(Rank.GENUS())) {
95
            return infraGenericEpithet;
96
        } else {
97
            // mask invalid data as null
98
            return null;
163
            return getInfraGenericEpithet();
99 164
        }
100
    }
101

  
102
    public String specificEpithet(String query){
103
        if(rank.isLower(Rank.SPECIES())){
104
            return specificEpithet;
105
        } else if(rank.isSpecies()) {
106
            return query;
107
        } else {
108
            return null;
165
        if(rank.isSpecies()){
166
            return getSpecificEpithet();
109 167
        }
110
    }
111

  
112
    public String infraspecificEpithet(String query){
113 168
        if(rank.isInfraSpecific()){
114
            return query;
115
        } else {
116
            return null;
169
            return getInfraSpecificEpithet();
117 170
        }
171
        return "-- ERROR: INVALID OR UNSUPPORTED RANK (" + rank.getLabel() + ") --";
118 172
    }
119 173

  
120 174
}
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/name/TaxonNameDaoHibernateImplTest.java
13 13
import static org.junit.Assert.assertFalse;
14 14
import static org.junit.Assert.assertNotNull;
15 15
import static org.junit.Assert.assertNull;
16
import static org.junit.Assert.assertTrue;
16 17

  
17 18
import java.io.FileNotFoundException;
19
import java.util.Arrays;
18 20
import java.util.HashSet;
19 21
import java.util.Iterator;
20 22
import java.util.List;
23
import java.util.Optional;
21 24
import java.util.Set;
22 25
import java.util.UUID;
23 26

  
......
40 43
import eu.etaxonomy.cdm.persistence.dao.name.IHomotypicalGroupDao;
41 44
import eu.etaxonomy.cdm.persistence.dao.name.ITaxonNameDao;
42 45
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
46
import eu.etaxonomy.cdm.persistence.dto.TaxonNameParts;
47
import eu.etaxonomy.cdm.persistence.query.OrderHint;
48
import eu.etaxonomy.cdm.persistence.query.OrderHint.SortOrder;
43 49
import eu.etaxonomy.cdm.strategy.exceptions.UnknownCdmTypeException;
44 50
import eu.etaxonomy.cdm.test.integration.CdmIntegrationTest;
45 51

  
......
168 174
    public void testSearchNames() {
169 175
        List<TaxonName> result = taxonNameDao.searchNames("Atropos", null, null, null, Rank.GENUS(), null, null, null, null);
170 176

  
171
        assertNotNull("searcNames should return a list",result);
177
        assertNotNull("searchNames should return a list",result);
172 178
        assertFalse("the list should not be empty", result.isEmpty());
173 179
        assertEquals("searchNames should return 3 TaxonName instances",3,result.size());
174 180
    }
175 181

  
176 182
    @Test
183
    public void testFindTaxonNameParts_genusOrUninomial() {
184
        Integer pageSize = 10;
185
        Integer pageIndex = 0;
186

  
187
        List<TaxonNameParts> resuls = taxonNameDao.findTaxonNameParts(
188
                Optional.of("Atropos"), null, null, null,
189
                Rank.GENUS(),
190
                pageSize, pageIndex, Arrays.asList(new OrderHint("genusOrUninomial", SortOrder.ASCENDING)));
191

  
192
        assertNotNull("searchNames should return a list",resuls);
193
        assertFalse(resuls.isEmpty());
194
        assertEquals(3, resuls.size());
195
    }
196

  
197
    @Test
198
    public void testFindTaxonNameParts_genusOrUninomial_wildcard() {
199
        Integer pageSize = 10;
200
        Integer pageIndex = 0;
201

  
202
        List<TaxonNameParts> results = taxonNameDao.findTaxonNameParts(
203
                Optional.of("Atro*"), null, null, null,
204
                Rank.GENUS(),
205
                pageSize, pageIndex, Arrays.asList(new OrderHint("genusOrUninomial", SortOrder.ASCENDING)));
206

  
207
        assertNotNull(results);
208
        assertFalse(results.isEmpty());
209
        assertEquals(3, results.size());
210

  
211
        results = taxonNameDao.findTaxonNameParts(
212
                Optional.of("Atro*"), null, null, null,
213
                Rank.SPECIES(),
214
                pageSize, pageIndex, Arrays.asList(new OrderHint("genusOrUninomial", SortOrder.ASCENDING)));
215

  
216
        assertNotNull(results);
217
        assertTrue(results.isEmpty());
218
    }
219

  
220
    @Test
221
    public void testFindTaxonNameParts_rankSpecies() {
222
        Integer pageSize = 10;
223
        Integer pageIndex = 0;
224
        // Manduca afflicta
225
        // Manduca chinchilla
226
        // Manduca bergarmatipes
227
        List<TaxonNameParts> results = taxonNameDao.findTaxonNameParts(
228
                Optional.of("Manduca"), null, Optional.of("*"), null,
229
                Rank.SPECIES(),
230
                pageSize, pageIndex, Arrays.asList(new OrderHint("specificEpithet", SortOrder.ASCENDING)));
231

  
232
        assertEquals(3, results.size());
233
        assertEquals("afflicta", results.get(0).getSpecificEpithet());
234
        assertEquals("bergarmatipes", results.get(1).getSpecificEpithet());
235
        assertEquals("chinchilla", results.get(2).getSpecificEpithet());
236

  
237
        results = taxonNameDao.findTaxonNameParts(
238
                Optional.of("Manduca"), null, Optional.of("chin*"), null,
239
                Rank.SPECIES(),
240
                pageSize, pageIndex, null);
241

  
242
        assertEquals(1, results.size());
243
        assertEquals("chinchilla", results.get(0).getSpecificEpithet());
244
    }
245

  
246
    @Test
247
    public void testFindTaxonNameParts_rankBelowSpecies() {
248

  
249
        Integer pageSize = 10;
250
        Integer pageIndex = 0;
251
        // Cryptocoryne x purpurea nothovar borneoensis
252
        // Cryptocoryne cordata var. zonata
253
        List<TaxonNameParts> results = taxonNameDao.findTaxonNameParts(
254
                Optional.of("Cryptocoryne"), null, null, Optional.of("borneo*"),
255
                Rank.VARIETY(),
256
                pageSize, pageIndex, Arrays.asList(new OrderHint("specificEpithet", SortOrder.ASCENDING)));
257

  
258
        assertEquals(1, results.size());
259

  
260
        // now also with "infraGenericEpithet is null AND specificEpithet = purpurea"
261
        results = taxonNameDao.findTaxonNameParts(
262
                Optional.of("Cryptocoryne"), Optional.empty(), Optional.of("purpurea"), Optional.of("borneo*"),
263
                Rank.VARIETY(),
264
                pageSize, pageIndex, Arrays.asList(new OrderHint("specificEpithet", SortOrder.ASCENDING)));
265

  
266
        assertEquals(1, results.size());
267
    }
268

  
269

  
270
    @Test
177 271
    public void testCountNames() {
178 272
        int count = taxonNameDao.countNames("Atropos", null, null, null, Rank.GENUS());
179 273

  
cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/INameService.java
14 14
import java.util.HashMap;
15 15
import java.util.List;
16 16
import java.util.Map;
17
import java.util.Optional;
17 18
import java.util.UUID;
18 19

  
19 20
import org.hibernate.criterion.Criterion;
......
23 24
import eu.etaxonomy.cdm.api.service.search.DocumentSearchResult;
24 25
import eu.etaxonomy.cdm.api.service.search.LuceneParseException;
25 26
import eu.etaxonomy.cdm.api.service.search.SearchResult;
27
import eu.etaxonomy.cdm.api.utility.TaxonNamePartsFilter;
26 28
import eu.etaxonomy.cdm.model.common.CdmBase;
27 29
import eu.etaxonomy.cdm.model.common.Language;
28 30
import eu.etaxonomy.cdm.model.common.ReferencedEntityBase;
......
38 40
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignationStatus;
39 41
import eu.etaxonomy.cdm.model.name.TaxonName;
40 42
import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
43
import eu.etaxonomy.cdm.persistence.dto.TaxonNameParts;
41 44
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
42 45
import eu.etaxonomy.cdm.persistence.query.MatchMode;
43 46
import eu.etaxonomy.cdm.persistence.query.OrderHint;
......
135 138
	public List<TaxonName> findNamesByTitleCache(String titleCache, MatchMode matchMode, List<String> propertyPaths);
136 139

  
137 140
	/**
141
     * Supports using wildcards in the query parameters.
142
     * If a name part passed to the method contains the asterisk character ('*') it will be translated into '%' the related field is search with a LIKE clause.
143
     * <p>
144
     * A query parameter which is passed as <code>NULL</code> value will be ignored. A parameter passed as {@link Optional} object containing a <code>NULL</code> value will be used
145
     * to filter select taxon names where the according field is <code>null</code>.
146
     *
147
     * @param filter
148
     * @param infraGenericEpithet
149
     * @param specificEpithet
150
     * @param infraSpecificEpithet
151
     * @param rank
152
     *     Only names having the specified rank are taken into account.
153
     * @return
154
     */
155
	public Pager<TaxonNameParts> findTaxonNameParts(Optional<String> genusOrUninomial,
156
            Optional<String> infraGenericEpithet, Optional<String> specificEpithet,
157
            Optional<String> infraSpecificEpithet, Rank rank, Integer pageSize, Integer pageIndex, List<OrderHint> orderHints);
158

  
159
	/**
160
     * <b>This method behaves differently compared to {@link #findTaxonNameParts(Optional, Optional, Optional, Optional, Rank, Integer, Integer, List)}!</b>
161
     * <p>
162
     * The {@link TaxonNamePartsFilter} defines the rank and fixed name parts for the search process.
163
     * For example: In case the <code>rank</code> is "genus", the <code>genusOrUninomial</code> will be used as search parameter which needs to match exactly.
164
     * The <code>namePartQueryString</code> will be used to do a wildcard search on the specificEpithet.
165
     * <p>
166
     * For name part lookup purposes the <code>TaxonNameParts</code> in the result list can be asked to return the relavant name part by
167
     * calling {@link TaxonNameParts#nameRankSpecificNamePart(TaxonName)}
168
     *
169
     *
170
     * @param filter
171
     * @return
172
     */
173
	public Pager<TaxonNameParts> findTaxonNameParts(TaxonNamePartsFilter filter, String namePartQueryString, Integer pageSize, Integer pageIndex, List<OrderHint> orderHints);
174

  
175
	/**
138 176
	 * Returns all NonViralNames with a name cache that matches the given string
139 177
	 * using the given match mode and initialization strategy
140 178
	 *
cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/NameServiceImpl.java
16 16
import java.util.HashSet;
17 17
import java.util.List;
18 18
import java.util.Map;
19
import java.util.Optional;
19 20
import java.util.Set;
20 21
import java.util.UUID;
21 22

  
......
47 48
import eu.etaxonomy.cdm.api.service.search.QueryFactory;
48 49
import eu.etaxonomy.cdm.api.service.search.SearchResult;
49 50
import eu.etaxonomy.cdm.api.service.search.SearchResultBuilder;
51
import eu.etaxonomy.cdm.api.utility.TaxonNamePartsFilter;
50 52
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
51 53
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
52 54
import eu.etaxonomy.cdm.model.CdmBaseType;
......
79 81
import eu.etaxonomy.cdm.persistence.dao.name.INomenclaturalStatusDao;
80 82
import eu.etaxonomy.cdm.persistence.dao.name.ITaxonNameDao;
81 83
import eu.etaxonomy.cdm.persistence.dao.name.ITypeDesignationDao;
84
import eu.etaxonomy.cdm.persistence.dto.TaxonNameParts;
82 85
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
83 86
import eu.etaxonomy.cdm.persistence.query.MatchMode;
84 87
import eu.etaxonomy.cdm.persistence.query.OrderHint;
......
329 332
     */
330 333
    @Override
331 334
    public List<TaxonName> findNamesByTitleCache(String titleCache, MatchMode matchMode, List<String> propertyPaths){
332
        List result = dao.findByTitle(titleCache, matchMode, null, null, null ,propertyPaths);
335
        List result = dao.findByTitle(titleCache, matchMode, null, null, null, propertyPaths);
333 336
        return result;
334 337
    }
335 338

  
......
341 344
     */
342 345
    @Override
343 346
    public List<TaxonName> findNamesByNameCache(String nameCache, MatchMode matchMode, List<String> propertyPaths){
344
        List result = dao.findByName(false, nameCache, matchMode, null, null, null ,propertyPaths);
347
        List result = dao.findByName(false, nameCache, matchMode, null, null, null , propertyPaths);
345 348
        return result;
346 349
    }
347 350

  
351
    @Override
352
    public Pager<TaxonNameParts> findTaxonNameParts(Optional<String> genusOrUninomial,
353
            Optional<String> infraGenericEpithet, Optional<String> specificEpithet,
354
            Optional<String> infraSpecificEpithet, Rank rank, Integer pageSize, Integer pageIndex, List<OrderHint> orderHints) {
355

  
356

  
357
        long count = dao.countTaxonNameParts(genusOrUninomial, infraGenericEpithet, specificEpithet, infraGenericEpithet, rank);
358

  
359
        List<TaxonNameParts> results;
360
        if(AbstractPagerImpl.hasResultsInRange(count, pageIndex, pageSize)){
361
            results = dao.findTaxonNameParts(genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet,
362
                    rank,
363
                    pageSize, pageIndex, orderHints);
364
        } else {
365
            results = new ArrayList<>();
366
        }
367

  
368
        return new DefaultPagerImpl<TaxonNameParts>(pageIndex, count, pageSize, results);
369
    }
370

  
371
    /**
372
     * {@inheritDoc}
373
     */
374
    @Override
375
    public Pager<TaxonNameParts> findTaxonNameParts(TaxonNamePartsFilter filter, String namePartQueryString,
376
            Integer pageSize, Integer pageIndex, List<OrderHint> orderHints) {
377

  
378
        return findTaxonNameParts(
379
                filter.uninomialQueryString(namePartQueryString),
380
                filter.infraGenericEpithet(namePartQueryString),
381
                filter.specificEpithet(namePartQueryString),
382
                filter.infraspecificEpithet(namePartQueryString),
383
                filter.getRank(),
384
                pageSize, pageIndex, orderHints);
385
    }
386

  
348 387
    /**
349 388
     * TODO candidate for harmonization
350 389
     */
cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/utility/TaxonNamePartsFilter.java
1
/**
2
* Copyright (C) 2018 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.api.utility;
10

  
11
import java.util.Optional;
12

  
13
import eu.etaxonomy.cdm.model.name.Rank;
14
import eu.etaxonomy.cdm.persistence.dto.TaxonNameParts;
15

  
16
/**
17
 * The {@link TaxonNamePartsFilter} defines the rank and fixed name parts for the name part search. See as an example
18
 * {@link eu.etaxonomy.cdm.api.service.INameService#findTaxonNameParts(TaxonNamePartsFilter, String, Integer, Integer, java.util.List)}
19
 * <p>
20
 * For example: In case the <code>rank</code> is "genus", the <code>genusOrUninomial</code> will be used as search parameter which needs to match exactly.
21
 * The <code>namePartQueryString</code> will be used to do a wildcard search on the specificEpithet.
22
 * <p>
23
 * For name part lookup purposes the <code>TaxonNameParts</code> in the result list can be asked to return the relavant name part by
24
 * calling {@link TaxonNameParts#nameRankSpecificNamePart(TaxonName)}
25
 *
26
 * @author a.kohlbecker
27
 * @since Jun 12, 2018
28
 *
29
 */
30
public class TaxonNamePartsFilter extends TaxonNameParts {
31

  
32
    /**
33
     * @param taxonNameId
34
     * @param rank
35
     * @param genusOrUninomial
36
     * @param infraGenericEpithet
37
     * @param specificEpithet
38
     * @param infraSpecificEpithet
39
     */
40
    public TaxonNamePartsFilter(Rank rank, String genusOrUninomial, String infraGenericEpithet,
41
            String specificEpithet, String infraSpecificEpithet) {
42
        super(null, rank, genusOrUninomial, infraGenericEpithet, specificEpithet, infraSpecificEpithet);
43
    }
44

  
45
    public TaxonNamePartsFilter(){
46
        super();
47
    }
48

  
49
    public Optional<String> uninomialQueryString(String query){
50
         if(rank.isLower(Rank.GENUS())){
51
             return optionalForNonNull(genusOrUninomial);
52
        } else {
53
            return Optional.of(appendWildcard(query));
54
        }
55
    }
56

  
57
    public Optional<String> infraGenericEpithet(String query){
58
        if(rank.isInfraGeneric()){
59
            return Optional.of(appendWildcard(query));
60
        } else if(rank.isLower(Rank.GENUS())) {
61
            return optionalForNonNull(infraGenericEpithet);
62
        } else {
63
            // mask invalid data as null
64
            return null;
65
        }
66
    }
67

  
68
    public Optional<String> specificEpithet(String query){
69
        if(rank.isLower(Rank.SPECIES())){
70
            return optionalForNonNull(specificEpithet);
71
        } else if(rank.isSpecies()) {
72
            return Optional.of(appendWildcard(query));
73
        } else {
74
            return null;
75
        }
76
    }
77

  
78
    public Optional<String> infraspecificEpithet(String query){
79
        if(rank.isInfraSpecific()){
80
            return Optional.of(appendWildcard(query));
81
        } else {
82
            return null;
83
        }
84
    }
85

  
86
    private Optional<String> optionalForNonNull(String value){
87
        if(value != null){
88
            return Optional.of(value);
89
        } else {
90
            return null;
91
        }
92
    }
93

  
94
    private String appendWildcard(String query){
95
        if(!query.endsWith("*")){
96
            return query + "*";
97
        }
98
        return query;
99

  
100
    }
101
}

Also available in: Unified diff

Add picture from clipboard (Maximum size: 40 MB)