From 6f972c854b6d342efeffd240897951545e14524d Mon Sep 17 00:00:00 2001 From: Andrew Rouse Date: Thu, 10 Sep 2015 10:56:19 +0100 Subject: [PATCH] Add ability to query just for the number of assets A HEAD request to /assets will include a "count" header which will be set to the number of assets which would have been returned for a GET request to the same URL. --- .../java/com/ibm/ws/lars/rest/ApiTest.java | 21 ++++++++ .../ibm/ws/lars/rest/RepositoryContext.java | 16 +++++++ .../ibm/ws/lars/rest/AssetServiceLayer.java | 7 +++ .../com/ibm/ws/lars/rest/PersistenceBean.java | 22 +++++++++ .../java/com/ibm/ws/lars/rest/Persistor.java | 12 +++++ .../ws/lars/rest/RepositoryRESTResource.java | 15 ++++++ .../com/ibm/ws/lars/rest/MemoryPersistor.java | 6 +++ .../rest/PersistenceBeanBasicSearchTest.java | 48 +++++++++++++++++++ .../lars/rest/PersistenceBeanLoggingTest.java | 19 ++++++++ .../ibm/ws/lars/rest/PersistenceBeanTest.java | 35 ++++++++++++++ .../RepositoryRESTResourceLoggingTest.java | 20 ++++++++ 11 files changed, 221 insertions(+) diff --git a/server/src/fat/java/com/ibm/ws/lars/rest/ApiTest.java b/server/src/fat/java/com/ibm/ws/lars/rest/ApiTest.java index e886c07..cb7d941 100644 --- a/server/src/fat/java/com/ibm/ws/lars/rest/ApiTest.java +++ b/server/src/fat/java/com/ibm/ws/lars/rest/ApiTest.java @@ -821,6 +821,27 @@ public void testGetAllAssetsSorted() throws Exception { assertThat(collatePages(page1, page2), contains(smallFoo, bigFoo, giantBar)); } + @SuppressWarnings("unused") + @Test + public void countAllAssets() throws Exception { + Asset bigFoo = addLittleAsset("name", "Big Foo", "category", "foo", "size", "20"); + Asset smallFoo = addLittleAsset("name", "Small Foo", "category", "foo", "size", "10"); + Asset giantBar = addLittleAsset("name", "Giant Bar", "category", "bar", "size", "40"); + Asset smallBar = addLittleAsset("name", "Small Bar", "category", "bar", "size", "10"); + + int result = repository.getAssetCount(""); + assertEquals(4, result); + + result = repository.getAssetCount("category=foo"); + assertEquals(2, result); + + result = repository.getAssetCount("q=foo"); + assertEquals(2, result); + + result = repository.getAssetCount("q=foo&size=10"); + assertEquals(1, result); + } + @SuppressWarnings("unchecked") @Test public void testGetAssetSummary() throws Exception { diff --git a/server/src/fat/java/com/ibm/ws/lars/rest/RepositoryContext.java b/server/src/fat/java/com/ibm/ws/lars/rest/RepositoryContext.java index 03f6af6..348f5a8 100644 --- a/server/src/fat/java/com/ibm/ws/lars/rest/RepositoryContext.java +++ b/server/src/fat/java/com/ibm/ws/lars/rest/RepositoryContext.java @@ -36,6 +36,7 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.ParseException; import org.apache.http.StatusLine; @@ -47,6 +48,7 @@ import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; @@ -273,6 +275,15 @@ public String doDelete(String url, int expectedStatusCode) return doRequest(delete, expectedStatusCode); } + public HttpResponse doHead(String url, int expectedStatusCode) + throws ClientProtocolException, IOException { + HttpHead head = new HttpHead(fullURL + url); + HttpResponse response = httpClient.execute(targetHost, head, context); + + assertStatusCode(expectedStatusCode, response); + return response; + } + public String doRequest(HttpEntityEnclosingRequestBase request, String content, int expectedStatusCode) @@ -444,6 +455,11 @@ void getBadAssetSummary(String parameters, int expectedRC) throws IOException { doGet("/assets/summary?" + parameters, expectedRC); } + int getAssetCount(String parameters) throws IOException { + HttpResponse response = doHead("/assets?" + parameters, HttpStatus.SC_NO_CONTENT); + return Integer.parseInt(response.getFirstHeader("count").getValue()); + } + protected Attachment doPostAttachmentNoContent(String assetId, String name, Attachment attachment) throws ClientProtocolException, IOException, InvalidJsonAssetException { List qparams = new ArrayList<>(); qparams.add(new BasicNameValuePair("name", name)); diff --git a/server/src/main/java/com/ibm/ws/lars/rest/AssetServiceLayer.java b/server/src/main/java/com/ibm/ws/lars/rest/AssetServiceLayer.java index 80ef4e8..7a39793 100644 --- a/server/src/main/java/com/ibm/ws/lars/rest/AssetServiceLayer.java +++ b/server/src/main/java/com/ibm/ws/lars/rest/AssetServiceLayer.java @@ -79,6 +79,13 @@ public AssetList retrieveAllAssets(Map> filters, String return persistenceBean.retrieveAllAssets(filters, searchTerm, pagination, sortOptions); } + /** + * @see Persistor#countAllAssets(Map, String) + */ + public int countAllAssets(Map> filters, String searchTerm) { + return persistenceBean.countAllAssets(filters, searchTerm); + } + /** * Summarizes a list of fields from the assets matched by the given filters and search term. *

diff --git a/server/src/main/java/com/ibm/ws/lars/rest/PersistenceBean.java b/server/src/main/java/com/ibm/ws/lars/rest/PersistenceBean.java index e47d6da..fe2ea3b 100644 --- a/server/src/main/java/com/ibm/ws/lars/rest/PersistenceBean.java +++ b/server/src/main/java/com/ibm/ws/lars/rest/PersistenceBean.java @@ -184,6 +184,13 @@ public AssetList retrieveAllAssets(Map> filters, String return AssetList.createAssetListFromMaps(assets); } + /** {@inheritDoc} */ + @Override + public int countAllAssets(Map> filters, String searchTerm) { + BasicDBObject filterObject = createFilterObject(filters, searchTerm); + return queryCount(filterObject); + } + /** {@inheritDoc} */ @SuppressWarnings("unchecked") @Override @@ -278,6 +285,21 @@ private List query(DBObject filterObject, DBObject sortObject, DBObjec return results; } + private int queryCount(DBObject filterObject) { + if (logger.isLoggable(Level.FINE)) { + logger.fine("queryCount: Querying database with query object " + filterObject); + } + + DBCursor cursor = getAssetCollection().find(filterObject); + int count = cursor.count(); + + if (logger.isLoggable(Level.FINE)) { + logger.fine("queryCount: found " + count + " assets."); + } + + return count; + } + private int getMongoSortOrder(SortOrder sortOrder) { switch (sortOrder) { case ASCENDING: diff --git a/server/src/main/java/com/ibm/ws/lars/rest/Persistor.java b/server/src/main/java/com/ibm/ws/lars/rest/Persistor.java index 35f557b..d21e943 100644 --- a/server/src/main/java/com/ibm/ws/lars/rest/Persistor.java +++ b/server/src/main/java/com/ibm/ws/lars/rest/Persistor.java @@ -60,6 +60,18 @@ public interface Persistor { */ public AssetList retrieveAllAssets(Map> filters, String searchTerm, PaginationOptions pagination, SortOptions sortOptions); + /** + * Retrieve the number of assets which match the given set of filters. + *

+ * filters and searchTerm have the same meaning as in + * {@link #retrieveAllAssets(Map, String, PaginationOptions, SortOptions)} + * + * @param filters filters to apply to the results, may be empty to not filter + * @param searchTerm search to match against the results, may be null to not search + * @return the number of assets which match the given filter and search term. + */ + public int countAllAssets(Map> filters, String searchTerm); + /** * Gets the list of distinct values of the given field in all assets which match the given * filters and searchTerm. diff --git a/server/src/main/java/com/ibm/ws/lars/rest/RepositoryRESTResource.java b/server/src/main/java/com/ibm/ws/lars/rest/RepositoryRESTResource.java index 3d0b0d2..d2c2395 100644 --- a/server/src/main/java/com/ibm/ws/lars/rest/RepositoryRESTResource.java +++ b/server/src/main/java/com/ibm/ws/lars/rest/RepositoryRESTResource.java @@ -34,6 +34,7 @@ import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; +import javax.ws.rs.HEAD; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; @@ -128,6 +129,20 @@ public Response getAssets(@Context UriInfo info) throws JsonProcessingException, return Response.ok(json).build(); } + @HEAD + @Path("/assets") + public Response countAssets(@Context UriInfo info) throws InvalidParameterException { + if (logger.isLoggable(Level.FINE)) { + logger.fine("countAssets called with query parameters: " + info.getRequestUri().getRawQuery()); + } + + AssetQueryParameters params = AssetQueryParameters.create(info); + + int count = assetService.countAllAssets(params.getFilterMap(), params.getSearchTerm()); + + return Response.noContent().header("count", count).build(); + } + @POST @Path("/assets") @Produces(MediaType.APPLICATION_JSON) diff --git a/server/src/test/java/com/ibm/ws/lars/rest/MemoryPersistor.java b/server/src/test/java/com/ibm/ws/lars/rest/MemoryPersistor.java index 6f674aa..d7b1912 100644 --- a/server/src/test/java/com/ibm/ws/lars/rest/MemoryPersistor.java +++ b/server/src/test/java/com/ibm/ws/lars/rest/MemoryPersistor.java @@ -78,6 +78,12 @@ public List getDistinctValues(String field, Map> throw new UnsupportedOperationException("Filtering is not supported in this test facade"); } + /** {@inheritDoc} */ + @Override + public int countAllAssets(Map> filters, String searchTerm) { + throw new UnsupportedOperationException("Filtering is not supported in this test facade"); + } + /* * (non-Javadoc) * diff --git a/server/src/test/java/com/ibm/ws/lars/rest/PersistenceBeanBasicSearchTest.java b/server/src/test/java/com/ibm/ws/lars/rest/PersistenceBeanBasicSearchTest.java index f29abdb..d889339 100644 --- a/server/src/test/java/com/ibm/ws/lars/rest/PersistenceBeanBasicSearchTest.java +++ b/server/src/test/java/com/ibm/ws/lars/rest/PersistenceBeanBasicSearchTest.java @@ -15,6 +15,8 @@ *******************************************************************************/ package com.ibm.ws.lars.rest; +import static org.junit.Assert.assertEquals; + import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -201,4 +203,50 @@ public void testRetrieveAllAssetsWithSearchAndSort(final @Mocked DBCollection co SortOptions sortOptions = new SortOptions("key2", SortOrder.DESCENDING); createTestBean().retrieveAllAssets(filters, "foo", null, sortOptions); } + + @Test + public void testCountAllAssets(final @Mocked DBCollection collection, final @Injectable DBCursor cursor) { + BasicDBList list = new BasicDBList(); + list.add(new BasicDBObject("key1", "value1")); + final BasicDBObject searchObject = new BasicDBObject("$and", list); + + new Expectations() { + { + collection.find(searchObject); + result = cursor; + + cursor.count(); + result = 3; + } + }; + + HashMap> filters = new HashMap<>(); + filters.put("key1", Arrays.asList(new Condition[] { new Condition(Operation.EQUALS, "value1") })); + int count = createTestBean().countAllAssets(filters, null); + assertEquals(3, count); + } + + @Test + public void testCountAllAssetsWithSearch(final @Mocked DBCollection collection, final @Injectable DBCursor cursor) { + BasicDBList list = new BasicDBList(); + list.add(new BasicDBObject("key1", "value1")); + list.add(new BasicDBObject("$text", new BasicDBObject("$search", "foo"))); + final BasicDBObject searchObject = new BasicDBObject("$and", list); + + new Expectations() { + { + collection.find(searchObject); + result = cursor; + + cursor.count(); + result = 3; + } + }; + + HashMap> filters = new HashMap<>(); + filters.put("key1", Arrays.asList(new Condition[] { new Condition(Operation.EQUALS, "value1") })); + int count = createTestBean().countAllAssets(filters, "foo"); + assertEquals(3, count); + } + } diff --git a/server/src/test/java/com/ibm/ws/lars/rest/PersistenceBeanLoggingTest.java b/server/src/test/java/com/ibm/ws/lars/rest/PersistenceBeanLoggingTest.java index 1b9d2f9..3d12804 100644 --- a/server/src/test/java/com/ibm/ws/lars/rest/PersistenceBeanLoggingTest.java +++ b/server/src/test/java/com/ibm/ws/lars/rest/PersistenceBeanLoggingTest.java @@ -29,6 +29,7 @@ import com.ibm.ws.lars.rest.model.Asset; import com.ibm.ws.lars.rest.model.Attachment; import com.mongodb.BasicDBObject; +import com.mongodb.DBCursor; public class PersistenceBeanLoggingTest { @@ -144,4 +145,22 @@ public void testFindAttachmentsForAsset() { createTestBean().findAttachmentsForAsset(NON_EXISTENT_ID); } + @Test + public void testQueryCount(@Mocked final DBCursor cursor) { + final BasicDBObject filterObject = new BasicDBObject("key1", "value1"); + new Expectations() { + { + logger.isLoggable(Level.FINE); + result = true; + + logger.fine("queryCount: Querying database with query object " + filterObject); + + cursor.count(); + result = 3; + + logger.fine("queryCount: found 3 assets."); + } + }; + Deencapsulation.invoke(createTestBean(), "queryCount", filterObject); + } } diff --git a/server/src/test/java/com/ibm/ws/lars/rest/PersistenceBeanTest.java b/server/src/test/java/com/ibm/ws/lars/rest/PersistenceBeanTest.java index 9c3e8b2..e9a6e5d 100644 --- a/server/src/test/java/com/ibm/ws/lars/rest/PersistenceBeanTest.java +++ b/server/src/test/java/com/ibm/ws/lars/rest/PersistenceBeanTest.java @@ -502,6 +502,41 @@ public void testSortOptions() throws Exception { assertThat(collatePages(page1, page2), contains(asset1, asset2, asset3, asset4)); } + @Test + public void testCountAllAssets() throws Exception { + persistenceBean.createAsset(Asset.deserializeAssetFromJson("{\"weather\":\"hot\", \"ground\":\"flat\", \"name\":\"hot and flat\"}")); + persistenceBean.createAsset(Asset.deserializeAssetFromJson("{\"weather\":\"hot\", \"ground\":\"hilly\", \"name\":\"hot and hilly\"}")); + persistenceBean.createAsset(Asset.deserializeAssetFromJson("{\"weather\":\"hot\", \"ground\":\"mountainous\"}")); + persistenceBean.createAsset(Asset.deserializeAssetFromJson("{\"weather\":\"cold\", \"ground\":\"flat\"}")); + persistenceBean.createAsset(Asset.deserializeAssetFromJson("{\"weather\":\"cold\", \"ground\":\"hilly\"}")); + persistenceBean.createAsset(Asset.deserializeAssetFromJson("{\"weather\":\"cold\", \"ground\":\"mountainous\"}")); + persistenceBean.createAsset(Asset.deserializeAssetFromJson("{\"weather\":\"warm\", \"ground\":\"flat\", \"name\":\"warm and flat\"}")); + persistenceBean.createAsset(Asset.deserializeAssetFromJson("{\"weather\":\"warm\", \"ground\":\"hilly\"}")); + persistenceBean.createAsset(Asset.deserializeAssetFromJson("{\"weather\":\"warm\", \"ground\":\"mountainous\"}")); + + Map> emptyFilter = Collections.emptyMap(); + + // Test counting all assets + int result = persistenceBean.countAllAssets(emptyFilter, null); + assertEquals(9, result); + + // Test counting assets with a filter + Map> filter = new HashMap<>(); + filter.put("weather", Arrays.asList(eq("hot"))); + result = persistenceBean.countAllAssets(filter, null); + assertEquals(3, result); + + // Test counting assets with a search + result = persistenceBean.countAllAssets(emptyFilter, "flat"); + assertEquals(2, result); + + // Test counting assets with a filter and a search + filter.clear(); + filter.put("weather", Arrays.asList(eq("hot"))); + result = persistenceBean.countAllAssets(filter, "flat"); + assertEquals(1, result); + } + /** * Collate the contents of several AssetLists into one List. *

diff --git a/server/src/test/java/com/ibm/ws/lars/rest/RepositoryRESTResourceLoggingTest.java b/server/src/test/java/com/ibm/ws/lars/rest/RepositoryRESTResourceLoggingTest.java index 8724917..eb81e42 100644 --- a/server/src/test/java/com/ibm/ws/lars/rest/RepositoryRESTResourceLoggingTest.java +++ b/server/src/test/java/com/ibm/ws/lars/rest/RepositoryRESTResourceLoggingTest.java @@ -97,6 +97,26 @@ public void testGetAssets(@Mocked final Logger logger, @Mocked final UriInfo inf getRestResource().getAssets(info); } + @Test + public void testCountAssets(@Mocked final Logger logger, @Mocked final UriInfo info) throws URISyntaxException, InvalidParameterException { + + new Expectations() { + { + info.getQueryParameters(false); + + logger.isLoggable(Level.FINE); + result = true; + + info.getRequestUri(); + result = new URI("http://localhost:9085/ma/v1/assets?foo=bar"); + + logger.fine("countAssets called with query parameters: foo=bar"); + } + }; + + getRestResource().countAssets(info); + } + @Test public void testPostAssets(@Mocked final Logger logger) {