Project

General

Profile

Revision db3d35f7

IDdb3d35f77ca051f8085948cc69d63756fbd5ae6b
Parent 83a544c7
Child 06ad0dec

Added by Andreas Kohlbecker about 3 years ago

#6612 generic dao list method providing a flexible multi property filter:
- PropertyNameMatchMode class introduces
- AbstractPagerImpl.limitStartforRange() to replace hasResultsInRange()

View differences:

cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/common/ICdmEntityDao.java
225 225
     * @param type
226 226
     *          Restrict the query to objects of a certain class, or null for
227 227
     *          all objects of type T or subclasses
228
     * @param propertyName
229
     *      The name of a entity property.
230
     * @param value
231
     *      The value for the comparison with the entity property.
232
     * @param matchMode
233
     *      The comparison method to use. <b>NOTE:</b> For non string type properties you must use
228
     * @param restrictions
229
     *      This defines a filter for multiple properties represented by the map keys. Sine the keys are of the type
230
     *      {@link PropertyNameMatchMode} for each property a single MatchMode is defined. Multiple alternative values
231
     *      can be supplied per property, that is the values per property are combined with OR. The per property
232
     *      restrictions are combined with AND. </br>
233
     *      <b>NOTE:</b> For non string type properties you must use
234 234
     *      {@link MatchMode#EXACT}. If set <code>null</code> {@link MatchMode#EXACT} will be used
235 235
     *      as default.
236 236
     * @param limit
......
246 246
     * @return
247 247
     * @throws DataAccessException
248 248
     */
249
    public List<T> list(Class<? extends T> type, String propertyName, Object value, MatchMode matchMode, Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths);
249
    public List<T> list(Class<? extends T> type, Map<PropertyNameMatchMode,  Collection<? extends Object>> restrictions, Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths);
250 250

  
251 251
    /**
252 252
     * Counts the Cdm entities matching the restrictions defined by
......
255 255
     * @param type
256 256
     *          Restrict the query to objects of a certain class, or null for
257 257
     *          all objects of type T or subclasses
258
     * @param propertyName
259
     *      The name of a entity property.
260
     * @param value
261
     *      The value for the comparison with the entity property.
262
     * @param matchMode
263
     *      The comparison method to use. <b>NOTE:</b> For non string type properties you must use
258
     * @param restrictions
259
     *      This defines a filter for multiple properties represented by the map keys. Sine the keys are of the type
260
     *      {@link PropertyNameMatchMode} for each property a single MatchMode is defined. Multiple alternative values
261
     *      can be supplied per property, that is the values per property are combined with OR. The per property
262
     *      restrictions are combined with AND. </br>
263
     *      <b>NOTE:</b> For non string type properties you must use
264 264
     *      {@link MatchMode#EXACT}. If set <code>null</code> {@link MatchMode#EXACT} will be used
265 265
     *      as default.
266 266
     * @param criteria
......
268 268
     *
269 269
     * @return
270 270
     */
271
    public int count(Class<? extends T> type, String propertyName, Object value, MatchMode matchMode);
271
    public int count(Class<? extends T> type, Map<PropertyNameMatchMode,  Collection<? extends Object>> restrictions);
272 272

  
273 273
    /**
274 274
     * Returns a sublist of CdmBase instances of type <TYPE> stored in the database.
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/common/PropertyNameMatchMode.java
1
/**
2
* Copyright (C) 2017 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.common;
10

  
11
import eu.etaxonomy.cdm.persistence.query.MatchMode;
12

  
13
/**
14
 * @author a.kohlbecker
15
 * @since May 8, 2017
16
 *
17
 */
18
public class PropertyNameMatchMode {
19

  
20
    private String propertyName;
21

  
22
    private MatchMode matchMode;
23

  
24

  
25

  
26
    /**
27
     * @param propertyName
28
     * @param matchMode
29
     */
30
    public PropertyNameMatchMode(String propertyName, MatchMode matchMode) {
31
        this.propertyName = propertyName;
32
        this.matchMode = matchMode;
33
    }
34

  
35
    /**
36
     * @return the propertyName
37
     */
38
    public String getPropertyName() {
39
        return propertyName;
40
    }
41

  
42
    /**
43
     * @param propertyName the propertyName to set
44
     */
45
    public void setPropertyName(String propertyName) {
46
        this.propertyName = propertyName;
47
    }
48

  
49
    /**
50
     * @return the matchMode
51
     */
52
    public MatchMode getMatchMode() {
53
        return matchMode;
54
    }
55

  
56
    /**
57
     * @param matchMode the matchMode to set
58
     */
59
    public void setMatchMode(MatchMode matchMode) {
60
        this.matchMode = matchMode;
61
    }
62

  
63

  
64
}
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/common/CdmEntityDaoBase.java
55 55
import eu.etaxonomy.cdm.model.common.User;
56 56
import eu.etaxonomy.cdm.model.common.VersionableEntity;
57 57
import eu.etaxonomy.cdm.persistence.dao.common.ICdmEntityDao;
58
import eu.etaxonomy.cdm.persistence.dao.common.PropertyNameMatchMode;
58 59
import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;
59 60
import eu.etaxonomy.cdm.persistence.dto.MergeResult;
60 61
import eu.etaxonomy.cdm.persistence.hibernate.PostMergeEntityListener;
......
452 453
     * {@inheritDoc}
453 454
     */
454 455
    @Override
455
    public List<T> list(Class<? extends T> type, String propertyName, Object value, MatchMode matchMode,
456
    public List<T> list(Class<? extends T> type, Map<PropertyNameMatchMode,  Collection<? extends Object>> restrictions,
456 457
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
457 458

  
458 459
        Criteria criteria = criterionForType(type);
459 460

  
460
        if (propertyName != null) {
461
            addRestriction(propertyName, value, matchMode, criteria);
462
        }
461
        addRestrictions(restrictions, criteria);
463 462

  
464 463
        addLimitAndStart(limit, start, criteria);
465 464
        addOrder(criteria, orderHints);
......
471 470
    }
472 471

  
473 472
    /**
473
     * @param restrictions
474
     * @param criteria
475
     */
476
    private void addRestrictions(Map<PropertyNameMatchMode, Collection<? extends Object>> restrictions, Criteria criteria) {
477
        List<Criterion> perProperty = new ArrayList<>(restrictions.size());
478
        for(PropertyNameMatchMode propMatchMode : restrictions.keySet()){
479
            Collection<? extends Object> values = restrictions.get(propMatchMode);
480
            if(values != null && !values.isEmpty()){
481
                Criterion[] predicates = new Criterion[values.size()];
482
                int i = 0;
483
                for(Object v : values){
484
                    predicates[i++] = createRestriction(propMatchMode.getPropertyName(), v, propMatchMode.getMatchMode());
485
                }
486
                perProperty.add(Restrictions.or(predicates));
487
            }
488
        }
489
        if(!perProperty.isEmpty()){
490
            criteria.add(Restrictions.and(perProperty.toArray(new Criterion[perProperty.size()])));
491
        }
492
    }
493

  
494
    /**
474 495
     * @param propertyName
475 496
     * @param value
476 497
     * @param matchMode
477 498
     * @param criteria
499
     * @return
478 500
     */
479
    private void addRestriction(String propertyName, Object value, MatchMode matchMode, Criteria criteria) {
501
    private Criterion createRestriction(String propertyName, Object value, MatchMode matchMode) {
480 502
        Criterion restriction;
481 503
        if(matchMode == null) {
482 504
            restriction = Restrictions.eq(propertyName, value);
......
496 518
                restriction = Restrictions.ilike(propertyName, queryString, org.hibernate.criterion.MatchMode.ANYWHERE);
497 519
            }
498 520
        }
499
        criteria.add(restriction);
521
        return restriction;
500 522
    }
501 523

  
502 524
    /**
503 525
     * {@inheritDoc}
504 526
     */
505 527
    @Override
506
    public int count(Class<? extends T> type, String propertyName, Object value, MatchMode matchMode) {
528
    public int count(Class<? extends T> type, Map<PropertyNameMatchMode,  Collection<? extends Object>> restrictions) {
507 529

  
508 530
        Criteria criteria = criterionForType(type);
509 531

  
510
        if (propertyName != null) {
511
            addRestriction(propertyName, value, matchMode, criteria);
512
        }
532
        addRestrictions(restrictions, criteria);
513 533

  
514 534
        criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));
515 535

  
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/agent/AgentDaoImplTest.java
15 15

  
16 16
import java.io.FileNotFoundException;
17 17
import java.util.ArrayList;
18
import java.util.Collection;
19
import java.util.HashMap;
18 20
import java.util.List;
21
import java.util.Map;
19 22
import java.util.UUID;
20 23

  
21
import org.hibernate.criterion.Criterion;
22
import org.hibernate.criterion.Restrictions;
23 24
import org.junit.After;
24 25
import org.junit.Assert;
25 26
import org.junit.Before;
......
35 36
import eu.etaxonomy.cdm.model.view.AuditEvent;
36 37
import eu.etaxonomy.cdm.model.view.context.AuditEventContextHolder;
37 38
import eu.etaxonomy.cdm.persistence.dao.agent.IAgentDao;
39
import eu.etaxonomy.cdm.persistence.dao.common.PropertyNameMatchMode;
38 40
import eu.etaxonomy.cdm.persistence.dao.reference.IReferenceDao;
39 41
import eu.etaxonomy.cdm.persistence.query.MatchMode;
40 42
import eu.etaxonomy.cdm.persistence.query.OrderHint;
......
260 262
    @DataSet("AgentDaoImplTest.xml")
261 263
    public void testListPeopleFiltered() {
262 264

  
263
        List<AgentBase> result = agentDao.list(null, (String)null, null, (MatchMode)null, (Integer)null, (Integer)null, null, null);
265
        Map<PropertyNameMatchMode,  Collection<? extends Object>> restrictions = new HashMap<>();
266
        List<AgentBase> result = agentDao.list(null, restrictions, (Integer)null, (Integer)null, null, null);
264 267
        Assert.assertNotNull("list() should return a list",result);
265 268
        Assert.assertEquals("list() should return 9 AgentBase entities in the current view", 9 ,result.size());
266 269

  
267
        List<AgentBase>  personResults = agentDao.list(Person.class, (String)null, null, (MatchMode)null, (Integer)null, (Integer)null, null, null);
270
        List<AgentBase>  personResults = agentDao.list(Person.class, restrictions, (Integer)null, (Integer)null, null, null);
268 271
        Assert.assertEquals("list() should return 5 Persons entities", 5, personResults.size());
269 272

  
270
        personResults = agentDao.list(Person.class, "firstname", "Ben", MatchMode.EXACT, (Integer)null, (Integer)null, null, null);
273
        Collection<String> values = new ArrayList<>();
274
        PropertyNameMatchMode firstNameExact = new PropertyNameMatchMode("firstname", MatchMode.EXACT);
275
        restrictions.put(firstNameExact,  values);
276

  
277
        personResults = agentDao.list(Person.class, restrictions, (Integer)null, (Integer)null, null, null);
278
        Assert.assertEquals("list() empty value lists should be ignored", 5, personResults.size());
279

  
280
        values.add("Ben");
281
        restrictions.put(firstNameExact, values);
282
        personResults = agentDao.list(Person.class, restrictions, (Integer)null, (Integer)null, null, null);
271 283
        Assert.assertEquals("list() should return 1 AgentBase entity having the firstname 'Ben'", 1 ,personResults.size());
272 284
    }
273 285

  
274 286
    @Test
275 287
    @DataSet("AgentDaoImplTest.xml")
276 288
    public void testCountPeopleFiltered() {
277
        List<Criterion> restrictions = new ArrayList<Criterion>();
278 289

  
279
        Assert.assertEquals("count() should return 9 AgentBase entities", 9 , agentDao.count(null, (String)null, null, (MatchMode)null));
290
        Map<PropertyNameMatchMode,  Collection<? extends Object>> restrictions = new HashMap<>();
291
        Assert.assertEquals("count() should return 9 AgentBase entities", 9 , agentDao.count(null, restrictions));
292

  
293
        Assert.assertEquals("count() should return 5 Persons entities", 5, agentDao.count(Person.class, restrictions));
280 294

  
281
        Assert.assertEquals("count() should return 5 Persons entities", 5, agentDao.count(Person.class, (String)null, null, (MatchMode)null));
295
        Collection<String> values = new ArrayList<>();
296
        PropertyNameMatchMode firstNameExact = new PropertyNameMatchMode("firstname", MatchMode.EXACT);
297
        restrictions.put(firstNameExact,  values);
298
        Assert.assertEquals("count() empty value lists should be ignored", 5, agentDao.count(Person.class, restrictions));
282 299

  
283
        restrictions.add(Restrictions.eq("firstname", "Ben"));
284
        Assert.assertEquals("count() should return 1 Persons entity having the firstname 'Ben'", 1 , agentDao.count(Person.class, "firstname", "Ben", MatchMode.EXACT));
300
        values.add("Ben");
301
        restrictions.put(firstNameExact, values);
302
        Assert.assertEquals("count() should return 1 Persons entity having the firstname 'Ben'", 1 , agentDao.count(Person.class, restrictions));
285 303
    }
286 304

  
287 305
    @Test
cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/IRegistrationService.java
78 78
     * @param pageNumber The offset (in pageSize chunks) from the start of the result set (0 - based,
79 79
     *                   can be null, equivalent of starting at the beginning of the recordset)
80 80
     * @param submitter
81
     *            The user who submitted the Registration
81
     *            Limits the result set to Registrations having the given submitter. This filter is ignored if set to <code>null</code>.
82 82
     * @param includedStatus
83 83
     *            filters the Registration by the RegistrationStatus. Only Registration having one of
84 84
     *            the supplied status will included.
cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/RegistrationServiceImpl.java
9 9
package eu.etaxonomy.cdm.api.service;
10 10

  
11 11
import java.util.ArrayList;
12
import java.util.Arrays;
12 13
import java.util.Collection;
14
import java.util.HashMap;
13 15
import java.util.List;
16
import java.util.Map;
14 17
import java.util.Optional;
15 18

  
16 19
import org.springframework.beans.factory.annotation.Autowired;
......
18 21
import org.springframework.transaction.annotation.Transactional;
19 22

  
20 23
import eu.etaxonomy.cdm.api.service.pager.Pager;
21
import eu.etaxonomy.cdm.api.service.pager.PagerUtils;
22 24
import eu.etaxonomy.cdm.api.service.pager.impl.AbstractPagerImpl;
23 25
import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
24 26
import eu.etaxonomy.cdm.model.common.User;
25 27
import eu.etaxonomy.cdm.model.name.Registration;
26 28
import eu.etaxonomy.cdm.model.name.RegistrationStatus;
27 29
import eu.etaxonomy.cdm.model.reference.Reference;
30
import eu.etaxonomy.cdm.persistence.dao.common.PropertyNameMatchMode;
28 31
import eu.etaxonomy.cdm.persistence.dao.name.IRegistrationDao;
32
import eu.etaxonomy.cdm.persistence.query.MatchMode;
29 33
import eu.etaxonomy.cdm.persistence.query.OrderHint;
30 34

  
31 35
/**
......
56 60
        long numberOfResults = dao.count(reference, includedStatus);
57 61

  
58 62
        List<Registration> results = new ArrayList<>();
59
        if(AbstractPagerImpl.hasResultsInRange(numberOfResults, pageIndex, pageSize)) {
60
            Integer limit = PagerUtils.limitFor(pageSize);
61
            Integer start = PagerUtils.startFor(pageSize, pageIndex);
62
            results = dao.list(reference, includedStatus, limit, start, propertyPaths);
63
        int [] limitStart = AbstractPagerImpl.limitStartforRange(numberOfResults, pageIndex, pageSize);
64
        if(limitStart != null) {
65
            results = dao.list(reference, includedStatus, limitStart[0], limitStart[1], propertyPaths);
63 66
        }
64 67

  
65
         return new DefaultPagerImpl<>(pageIndex, numberOfResults, pageSize, results);
68
        return new DefaultPagerImpl<>(pageIndex, numberOfResults, pageSize, results);
66 69
    }
67 70

  
68 71
    /**
69 72
     * {@inheritDoc}
70
     * TODO: includedStatus not yet implemented
71 73
     */
72 74
    @Override
73 75
    public Pager<Registration> page(User submitter, Collection<RegistrationStatus> includedStatus,
74 76
            Integer pageSize, Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths) {
75 77

  
76
        long numberOfResults = dao.count(Registration.class, "submitter", submitter, null);
78
        Map<PropertyNameMatchMode,  Collection<? extends Object>> restrictions = new HashMap<>();
79
        if(submitter != null){
80
            restrictions.put(new PropertyNameMatchMode("submitter", MatchMode.EXACT), Arrays.asList(submitter));
81
        }
82
        if(includedStatus != null && !includedStatus.isEmpty()){
83
            restrictions.put(new PropertyNameMatchMode("status", MatchMode.EXACT), includedStatus);
84
        }
85

  
86
        long numberOfResults = dao.count(Registration.class, restrictions);
77 87

  
78 88
        List<Registration> results = new ArrayList<Registration>();
79
        if(AbstractPagerImpl.hasResultsInRange(numberOfResults, pageIndex, pageSize)) {
80
            Integer limit = PagerUtils.limitFor(pageSize);
81
            Integer start = PagerUtils.startFor(pageSize, pageIndex);
82
            results = dao.list(Registration.class, "submitter", submitter, null, limit, start, orderHints, propertyPaths);
89
        int [] limitStart = AbstractPagerImpl.limitStartforRange(numberOfResults, pageIndex, pageSize);
90
        if(limitStart != null) {
91
            results = dao.list(Registration.class, restrictions, limitStart[0], limitStart[1], orderHints, propertyPaths);
83 92
        }
84 93

  
85
         return new DefaultPagerImpl<>(pageIndex, numberOfResults, pageSize, results);
94
        return new DefaultPagerImpl<>(pageIndex, numberOfResults, pageSize, results);
86 95
    }
87 96

  
88 97

  
cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/pager/impl/AbstractPagerImpl.java
266 266

  
267 267
	/**
268 268
	 * Test if the the <code>numberOfResults</code> in the range of the page specified by <code>pageIndex</code> and <code>pageSize</code>.
269
	 *
269
	 * <p>
270 270
	 * When using this method in a service layer class you will also need to provide the according <code>limit</code> and <code>start</code>
271 271
	 * parameters for dao list methods. The PagerUtil class provides the according methods for the required calculation:
272
	 * {@link PagerUtils#limitFor(Integer)} and {@link PagerUtils#startFor(Integer, Integer)}
272
	 * {@link PagerUtils#limitFor(Integer)} and {@link PagerUtils#startFor(Integer, Integer)}.<br/>
273
     * <b>NOTE:</b> It is highly recommended to use the {@link #limitStartforRange(Long, Integer, Integer)} method instead which already
274
     * includes the calculation of <code>limit</code> and <code>start</code>.
273 275
	 *
274 276
	 * @param numberOfResults
275 277
	 * @param pageIndex
276 278
	 * @param pageSize
277 279
	 * @return
280
	 *
281
	 * @deprecated use {@link #limitStartforRange(Long, Integer, Integer)} instead if appropriate.
278 282
	 */
283
	@Deprecated
279 284
	public static boolean hasResultsInRange(Long numberOfResults, Integer pageIndex, Integer pageSize) {
280 285
		return numberOfResults > 0 // no results at all
281 286
				&& (pageSize == null // page size may be null : return all in this case
282 287
				|| pageIndex != null && numberOfResults > (pageIndex * pageSize));
283 288
	}
284 289

  
290
	/**
291
     * Test if the the <code>numberOfResults</code> in the range of the page specified by <code>pageIndex</code> and <code>pageSize</code>.
292
     * And returns the according <code>limit</code> and <code>start</code> values as an array in case the test is successful. If there is no
293
     * result in the specified range the return value will be <code>null</code>.
294
     * <p>
295
     * When using this method in a service layer class you will also need to provide the according <code>limit</code> and <code>start</code>
296
     * parameters for dao list methods. The PagerUtil class provides the according methods for the required calculation:
297
     * {@link PagerUtils#limitFor(Integer)} and {@link PagerUtils#startFor(Integer, Integer)}
298
     *
299
     * @param numberOfResults
300
     * @param pageIndex
301
     * @param pageSize
302
     * @return An <code>int</code> array containing limit and start: <code>new int[]{limit, start}</code> or null if there is no result in the range of
303
     *  <code>pageIndex</code> and <code>pageSize</code>.
304
     */
305
    public static int[] limitStartforRange(Long numberOfResults, Integer pageIndex, Integer pageSize) {
306
        if(hasResultsInRange(numberOfResults, pageIndex, pageSize)){
307
            return  new int[]{PagerUtils.limitFor(pageSize), PagerUtils.startFor(pageSize, pageIndex)};
308
        }
309
        return null;
310
    }
311

  
285 312
}

Also available in: Unified diff

Add picture from clipboard (Maximum size: 40 MB)