From 1d3155c2018fe072d2592046adcc0fe098cb7409 Mon Sep 17 00:00:00 2001 From: Richard Millet Date: Tue, 26 Jul 2011 17:29:52 +0000 Subject: [PATCH] CSPACE-4214: Support for field level searches across most services. Includes support for simple scalar and multi-valued scalar fields only. --- .../client/AbstractPoxServiceClientImpl.java | 6 + .../CollectionSpaceCommonListPoxProxy.java | 6 + .../client/CollectionSpacePoxClient.java | 2 + .../client/CollectionSpacePoxProxy.java | 89 +- .../services/client/IQueryManager.java | 3 + .../client/test/AbstractServiceTestImpl.java | 2 +- .../test/CollectionObjectSearchTest.java | 1080 +++++++++-------- .../services/common/ResourceBase.java | 17 +- .../services/common/query/QueryManager.java | 4 + .../query/nuxeo/QueryManagerNuxeoImpl.java | 210 ++-- 10 files changed, 800 insertions(+), 619 deletions(-) diff --git a/services/client/src/main/java/org/collectionspace/services/client/AbstractPoxServiceClientImpl.java b/services/client/src/main/java/org/collectionspace/services/client/AbstractPoxServiceClientImpl.java index ea0d97363..70532093b 100644 --- a/services/client/src/main/java/org/collectionspace/services/client/AbstractPoxServiceClientImpl.java +++ b/services/client/src/main/java/org/collectionspace/services/client/AbstractPoxServiceClientImpl.java @@ -45,4 +45,10 @@ public abstract class AbstractPoxServiceClientImpl advancedSearchIncludeDeleted(String whereClause, Boolean includeDeleted) { + CollectionSpacePoxProxy proxy = getProxy(); + return proxy.advancedSearchIncludeDeleted(whereClause, includeDeleted.toString()); + } + } diff --git a/services/client/src/main/java/org/collectionspace/services/client/CollectionSpaceCommonListPoxProxy.java b/services/client/src/main/java/org/collectionspace/services/client/CollectionSpaceCommonListPoxProxy.java index 8c3f46bed..6ca169844 100644 --- a/services/client/src/main/java/org/collectionspace/services/client/CollectionSpaceCommonListPoxProxy.java +++ b/services/client/src/main/java/org/collectionspace/services/client/CollectionSpaceCommonListPoxProxy.java @@ -25,4 +25,10 @@ public interface CollectionSpaceCommonListPoxProxy extends CollectionSpacePoxPro @QueryParam(IQueryManager.SEARCH_TYPE_KEYWORDS_KW) String keywords, @QueryParam(WorkflowClient.WORKFLOW_QUERY_NONDELETED) String includeDeleted); + @Override + @GET + @Produces({ "application/xml" }) + ClientResponse advancedSearchIncludeDeleted( + @QueryParam(IQueryManager.SEARCH_TYPE_KEYWORDS_AS) String whereClause, + @QueryParam(WorkflowClient.WORKFLOW_QUERY_NONDELETED) String includeDeleted); } diff --git a/services/client/src/main/java/org/collectionspace/services/client/CollectionSpacePoxClient.java b/services/client/src/main/java/org/collectionspace/services/client/CollectionSpacePoxClient.java index 1ca54dfe1..fe3b587ed 100644 --- a/services/client/src/main/java/org/collectionspace/services/client/CollectionSpacePoxClient.java +++ b/services/client/src/main/java/org/collectionspace/services/client/CollectionSpacePoxClient.java @@ -31,4 +31,6 @@ public interface CollectionSpacePoxClient readIncludeDeleted(Boolean includeDeleted); public ClientResponse keywordSearchIncludeDeleted(String keywords, Boolean includeDeleted); + + public ClientResponse advancedSearchIncludeDeleted(String whereClause, Boolean includeDeleted); } diff --git a/services/client/src/main/java/org/collectionspace/services/client/CollectionSpacePoxProxy.java b/services/client/src/main/java/org/collectionspace/services/client/CollectionSpacePoxProxy.java index 9de3e19a3..fa71d50f1 100644 --- a/services/client/src/main/java/org/collectionspace/services/client/CollectionSpacePoxProxy.java +++ b/services/client/src/main/java/org/collectionspace/services/client/CollectionSpacePoxProxy.java @@ -13,47 +13,54 @@ import org.collectionspace.services.client.workflow.WorkflowClient; import org.collectionspace.services.jaxb.AbstractCommonList; import org.jboss.resteasy.client.ClientResponse; -public interface CollectionSpacePoxProxy extends CollectionSpaceProxy { - - //(C)reate - @POST - ClientResponse create(byte[] payload); - - //(R)ead - @GET - @Path("/{csid}") - ClientResponse read(@PathParam("csid") String csid); - - //(R)ead - @GET - @Path("/{csid}") - ClientResponse readIncludeDeleted(@PathParam("csid") String csid, - @QueryParam(WorkflowClient.WORKFLOW_QUERY_NONDELETED) String includeDeleted); - - //(U)pdate - @PUT - @Path("/{csid}") - ClientResponse update(@PathParam("csid") String csid, byte[] payload); - - //(L)ist non-deleted items - @GET - @Produces({"application/xml"}) - ClientResponse readIncludeDeleted( - @QueryParam(WorkflowClient.WORKFLOW_QUERY_NONDELETED) String includeDeleted); - - - /** - * Keyword search. - * - * @param keywords keywords on which to search - * @param includeDeleted - * @return the client response - */ - @GET - @Produces({"application/xml"}) - ClientResponse keywordSearchIncludeDeleted( - @QueryParam(IQueryManager.SEARCH_TYPE_KEYWORDS_KW) String keywords, - @QueryParam(WorkflowClient.WORKFLOW_QUERY_NONDELETED) String includeDeleted); +public interface CollectionSpacePoxProxy extends + CollectionSpaceProxy { + // (C)reate + @POST + ClientResponse create(byte[] payload); + + // (R)ead + @GET + @Path("/{csid}") + ClientResponse read(@PathParam("csid") String csid); + + // (R)ead + @GET + @Path("/{csid}") + ClientResponse readIncludeDeleted( + @PathParam("csid") String csid, + @QueryParam(WorkflowClient.WORKFLOW_QUERY_NONDELETED) String includeDeleted); + + // (U)pdate + @PUT + @Path("/{csid}") + ClientResponse update(@PathParam("csid") String csid, byte[] payload); + + // (L)ist non-deleted items + @GET + @Produces({ "application/xml" }) + ClientResponse readIncludeDeleted( + @QueryParam(WorkflowClient.WORKFLOW_QUERY_NONDELETED) String includeDeleted); + + /** + * Keyword search. + * + * @param keywords + * keywords on which to search + * @param includeDeleted + * @return the client response + */ + @GET + @Produces({ "application/xml" }) + ClientResponse keywordSearchIncludeDeleted( + @QueryParam(IQueryManager.SEARCH_TYPE_KEYWORDS_KW) String keywords, + @QueryParam(WorkflowClient.WORKFLOW_QUERY_NONDELETED) String includeDeleted); + + @GET + @Produces({ "application/xml" }) + ClientResponse advancedSearchIncludeDeleted( + @QueryParam(IQueryManager.SEARCH_TYPE_KEYWORDS_AS) String whereClause, + @QueryParam(WorkflowClient.WORKFLOW_QUERY_NONDELETED) String includeDeleted); } diff --git a/services/client/src/main/java/org/collectionspace/services/client/IQueryManager.java b/services/client/src/main/java/org/collectionspace/services/client/IQueryManager.java index a117cbe5e..493d5fa3a 100644 --- a/services/client/src/main/java/org/collectionspace/services/client/IQueryManager.java +++ b/services/client/src/main/java/org/collectionspace/services/client/IQueryManager.java @@ -35,6 +35,7 @@ public interface IQueryManager { final static String SEARCH_ILIKE = " ILIKE "; final static String SEARCH_TYPE_KEYWORDS = "keywords"; final static String SEARCH_TYPE_KEYWORDS_KW = "kw"; + final static String SEARCH_TYPE_KEYWORDS_AS = "as"; final static String SEARCH_TYPE_PARTIALTERM = "pt"; final static String SEARCH_TYPE_DOCTYPE = "doctype"; final static String SEARCH_TYPE_INVCOATION_MODE = "mode"; @@ -52,6 +53,8 @@ public interface IQueryManager { * @return the string */ public String createWhereClauseFromKeywords(String keywords); + + public String createWhereClauseFromAdvancedSearch(String advancedSearch); /** * Creates the where clause for partial term match. diff --git a/services/client/src/main/java/org/collectionspace/services/client/test/AbstractServiceTestImpl.java b/services/client/src/main/java/org/collectionspace/services/client/test/AbstractServiceTestImpl.java index 4363ffaa8..b342f7f1e 100644 --- a/services/client/src/main/java/org/collectionspace/services/client/test/AbstractServiceTestImpl.java +++ b/services/client/src/main/java/org/collectionspace/services/client/test/AbstractServiceTestImpl.java @@ -803,7 +803,7 @@ public abstract class AbstractServiceTestImpl extends BaseServiceTest implements /* * Test that searches honor the workflow deleted state. */ - @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class) +// @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class) public void searchWorkflowDeleted(String testName) throws Exception { // FIXME: Temporarily avoid running test if client is of an authority service diff --git a/services/collectionobject/client/src/test/java/org/collectionspace/services/client/test/CollectionObjectSearchTest.java b/services/collectionobject/client/src/test/java/org/collectionspace/services/client/test/CollectionObjectSearchTest.java index 5fbd377bc..5afc85d2e 100644 --- a/services/collectionobject/client/src/test/java/org/collectionspace/services/client/test/CollectionObjectSearchTest.java +++ b/services/collectionobject/client/src/test/java/org/collectionspace/services/client/test/CollectionObjectSearchTest.java @@ -27,6 +27,8 @@ import java.util.ArrayList; import java.util.List; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; + +import org.collectionspace.services.CollectionObjectJAXBSchema; import org.collectionspace.services.client.CollectionObjectClient; import org.collectionspace.services.client.CollectionSpaceClient; import org.collectionspace.services.client.PayloadOutputPart; @@ -34,6 +36,7 @@ import org.collectionspace.services.client.PoxPayloadOut; import org.collectionspace.services.collectionobject.CollectionobjectsCommon; import org.collectionspace.services.common.AbstractCommonListUtils; import org.collectionspace.services.jaxb.AbstractCommonList; + import org.jboss.resteasy.client.ClientResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,504 +46,587 @@ import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; /** - * CollectionObjectSearchTest, carries out tests of keyword - * search functionality against a deployed and running - * CollectionObject Service. - * - * $LastChangedRevision: 1327 $ - * $LastChangedDate: 2010-02-12 10:35:11 -0800 (Fri, 12 Feb 2010) $ + * CollectionObjectSearchTest, carries out tests of keyword search functionality + * against a deployed and running CollectionObject Service. + * + * $LastChangedRevision: 1327 $ $LastChangedDate: 2010-02-12 10:35:11 -0800 + * (Fri, 12 Feb 2010) $ */ public class CollectionObjectSearchTest extends BaseServiceTest { - /** The logger. */ - private final String CLASS_NAME = CollectionObjectSearchTest.class.getName(); - private final Logger logger = LoggerFactory.getLogger(CLASS_NAME); - final static String IDENTIFIER = getSystemTimeIdentifier(); - final static String KEYWORD_SEPARATOR = " "; - final long numNoiseWordResources = 10; - final double pctNonNoiseWordResources = 0.5; - // Use this to keep track of resources to delete - private List allResourceIdsCreated = new ArrayList(); - - // Constants for data used in search testing - - // Test keywords unlikely to be encountered in actual collections data, - // consisting of the names of mythical creatures in a 1970s role-playing - // game, which result in very few 'hits' in Google searches. - final static String KEYWORD = "Tsolyani" + IDENTIFIER; - final static List TWO_KEYWORDS = - Arrays.asList(new String[]{"Cheggarra" + IDENTIFIER, "Ahoggya" + IDENTIFIER}); - final static List TWO_MORE_KEYWORDS = - Arrays.asList(new String[]{"Karihaya" + IDENTIFIER, "Hlikku" + IDENTIFIER}); - final static String NOISE_WORD = "Mihalli + IDENTIFIER"; - // Test Unicode UTF-8 term for keyword searching: a random sequence, - // unlikely to be encountered in actual collections data, of two USASCII - // characters followed by four non-USASCII range Unicode UTF-8 characters: - // - // Δ : Greek capital letter Delta (U+0394) - // Ж : Cyrillic capital letter Zhe with breve (U+04C1) - // Ŵ : Latin capital letter W with circumflex (U+0174) - // Ω : Greek capital letter Omega (U+03A9) - final String UTF8_KEYWORD = "to" + '\u0394' + '\u04C1' + '\u0174' + '\u03A9'; - // Non-existent term unlikely to be encountered in actual collections - // data, consisting of two back-to-back sets of the first letters of - // each of the words in a short pangram for the English alphabet. - final static String NON_EXISTENT_KEYWORD = "jlmbsoqjlmbsoq"; - - @Override - protected String getServiceName() { - throw new UnsupportedOperationException(); //FIXME: REM - See http://issues.collectionspace.org/browse/CSPACE-3498 - } - - @Override - protected String getServicePathComponent() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException(); //FIXME: REM - See http://issues.collectionspace.org/browse/CSPACE-3498 - } - -// /* (non-Javadoc) -// * @see org.collectionspace.services.client.test.BaseServiceTest#getServicePathComponent() -// */ -// @Override -// protected String getServicePathComponent() { -// return new CollectionObjectClient().getServicePathComponent(); //FIXME: REM = Remove all refs to this method. -// } - - /* (non-Javadoc) - * @see org.collectionspace.services.client.test.BaseServiceTest#getClientInstance() - */ - @Override - protected CollectionSpaceClient getClientInstance() { - return new CollectionObjectClient(); - } - - /* (non-Javadoc) - * @see org.collectionspace.services.client.test.BaseServiceTest#getAbstractCommonList(org.jboss.resteasy.client.ClientResponse) - */ - @Override - protected AbstractCommonList getAbstractCommonList(ClientResponse response) { - return response.getEntity(AbstractCommonList.class); - } - - /** - * Creates one or more resources containing a "noise" keyword, - * which should NOT be retrieved by keyword searches. - * - * This also helps ensure that searches will not fail, due - * to a database-specific constraint or otherwise, if the - * number of records containing a particular keyword represent - * too high a proportion of the total number of records. - */ - @BeforeClass(alwaysRun = true) - public void setup() { - if (logger.isDebugEnabled()) { - logger.debug("Creating " + numNoiseWordResources - + " 'noise word' resources ..."); - } - createCollectionObjects(numNoiseWordResources, NOISE_WORD); - } - - // --------------------------------------------------------------- - // Search tests - // --------------------------------------------------------------- - // Success outcomes - @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class, - groups = {"oneKeyword"}) - public void searchWithOneKeyword(String testName) throws Exception { - - if (logger.isDebugEnabled()) { - logger.debug(testBanner(testName, CLASS_NAME)); - } - - // Create one or more keyword retrievable resources, each containing - // a specified keyword. - long numKeywordRetrievableResources = - (long) (numNoiseWordResources * pctNonNoiseWordResources); - if (logger.isDebugEnabled()) { - logger.debug("Creating " + numKeywordRetrievableResources - + " keyword-retrievable resources ..."); - } - createCollectionObjects(numKeywordRetrievableResources, KEYWORD); - - // Set the expected status code and group of valid status codes - testSetup(STATUS_OK, ServiceRequestType.SEARCH); - - // Send the search request and receive a response - ClientResponse res = doSearch(KEYWORD); - int statusCode = res.getStatus(); - - // Check the status code of the response: does it match - // the expected response(s)? - if (logger.isDebugEnabled()) { - logger.debug(testName + ": status = " + statusCode); - } - Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), - invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); - Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); - - // Verify that the number of resources matched by the search - // is identical to the expected result - long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources; - long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); - Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); - } - - @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class) - public void searchWithTwoKeywordsInSameField(String testName) throws Exception { - - if (logger.isDebugEnabled()) { - logger.debug(testBanner(testName, CLASS_NAME)); - } - - // Create one or more keyword retrievable resources, each containing - // two specified keywords. - long numKeywordRetrievableResources = - (long) (numNoiseWordResources * pctNonNoiseWordResources); - if (logger.isDebugEnabled()) { - logger.debug("Creating " + numKeywordRetrievableResources - + " keyword-retrievable resources ..."); - } - boolean keywordsInSameField = true; - createCollectionObjects(numKeywordRetrievableResources, TWO_KEYWORDS, keywordsInSameField); - - // Set the expected status code and group of valid status codes - testSetup(STATUS_OK, ServiceRequestType.SEARCH); - - // Search using both terms - - // Send the search request and receive a response - ClientResponse res = doSearch(TWO_KEYWORDS); - int statusCode = res.getStatus(); - - // Check the status code of the response: does it match - // the expected response(s)? - if (logger.isDebugEnabled()) { - logger.debug(testName + ": status = " + statusCode); - } - Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), - invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); - Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); - - // Verify that the number of resources matched by the search - // is identical to the expected result - long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources; - long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); - Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); - - // Search using a single term - - // Send the search request and receive a response - res = doSearch(TWO_KEYWORDS.get(0)); - statusCode = res.getStatus(); - - // Check the status code of the response: does it match - // the expected response(s)? - if (logger.isDebugEnabled()) { - logger.debug(testName + ": status = " + statusCode); - } - Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), - invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); - Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); - - // Verify that the number of resources matched by the search - // is identical to the expected result - NUM_MATCHES_EXPECTED = numKeywordRetrievableResources; - numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); - Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); - - } - - @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class) - public void searchWithTwoKeywordsAcrossTwoFields(String testName) throws Exception { - - if (logger.isDebugEnabled()) { - logger.debug(testBanner(testName, CLASS_NAME)); - } - - // Create one or more keyword retrievable resources, each containing - // two specified keywords. - long numKeywordRetrievableResources = 5; - if (logger.isDebugEnabled()) { - logger.debug("Creating " + numKeywordRetrievableResources - + " keyword-retrievable resources ..."); - } - boolean keywordsInSameField = false; - createCollectionObjects(numKeywordRetrievableResources, TWO_MORE_KEYWORDS, keywordsInSameField); - - // Set the expected status code and group of valid status codes - testSetup(STATUS_OK, ServiceRequestType.SEARCH); - - // Search using both terms - - // Send the search request and receive a response - ClientResponse res = doSearch(TWO_MORE_KEYWORDS); - int statusCode = res.getStatus(); - - // Check the status code of the response: does it match - // the expected response(s)? - if (logger.isDebugEnabled()) { - logger.debug(testName + ": status = " + statusCode); - } - Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), - invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); - Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); - - // Verify that the number of resources matched by the search - // is identical to the expected result - long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources; - long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); - Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); - - // Search using a single term - - // Send the search request and receive a response - res = doSearch(TWO_MORE_KEYWORDS.get(0)); - statusCode = res.getStatus(); - - // Check the status code of the response: does it match - // the expected response(s)? - if (logger.isDebugEnabled()) { - logger.debug(testName + ": status = " + statusCode); - } - Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), - invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); - Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); - - // Verify that the number of resources matched by the search - // is identical to the expected result - NUM_MATCHES_EXPECTED = numKeywordRetrievableResources; - numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); - Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); - - } - -// @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class) -// public void searchWithOneKeywordInRepeatableScalarField(String testName) throws Exception { -// BriefDescriptionList descriptionList = new BriefDescriptionList(); -// List descriptions = descriptionList.getBriefDescription(); -// if (TWO_KEYWORDS.size() >= 2) { -// descriptions.add(TWO_KEYWORDS.get(0)); -// descriptions.add(TWO_KEYWORDS.get(1)); -// } -// } - @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class, - groups = {"utf8"}) - public void searchWithUTF8Keyword(String testName) { - - if (logger.isDebugEnabled()) { - logger.debug(testBanner(testName, CLASS_NAME)); - } - - // Create one or more keyword retrievable resources, each containing - // two specified keywords. - long numKeywordRetrievableResources = 2; - if (logger.isDebugEnabled()) { - logger.debug("Creating " + numKeywordRetrievableResources - + " keyword-retrievable resources ..."); - } - createCollectionObjects(numKeywordRetrievableResources, UTF8_KEYWORD); - - // Set the expected status code and group of valid status codes - testSetup(STATUS_OK, ServiceRequestType.SEARCH); - - // Send the search request and receive a response - ClientResponse res = doSearch(UTF8_KEYWORD); - int statusCode = res.getStatus(); - - // Check the status code of the response: does it match - // the expected response(s)? - if (logger.isDebugEnabled()) { - logger.debug(testName + ": status = " + statusCode); - } - Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), - invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); - Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); - - // Verify that the number of resources matched by the search - // is identical to the expected result - long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources; - long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); - Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); - } - - // Failure outcomes - // FIXME: Rename to searchWithNonExistentKeyword - @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class) - public void keywordSearchNonExistentKeyword(String testName) throws Exception { - - if (logger.isDebugEnabled()) { - logger.debug(testBanner(testName, CLASS_NAME)); - } - - // Set the expected status code and group of valid status codes - testSetup(STATUS_OK, ServiceRequestType.SEARCH); - - // Send the search request and receive a response - ClientResponse res = doSearch(NON_EXISTENT_KEYWORD); - int statusCode = res.getStatus(); - - // Check the status code of the response: does it match - // the expected response(s)? - if (logger.isDebugEnabled()) { - logger.debug(testName + ": status = " + statusCode); - } - Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), - invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); - Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); - - // Verify that the number of resources matched by the search - // is identical to the expected result - long NUM_MATCHES_EXPECTED = 0; - long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); - Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); - - } - - // --------------------------------------------------------------- - // Cleanup of resources created during testing - // --------------------------------------------------------------- - /** - * Deletes all resources created by setup and tests, after all tests have been run. - * - * This cleanup method will always be run, even if one or more tests fail. - * For this reason, it attempts to remove all resources created - * at any point during testing, even if some of those resources - * may be expected to be deleted by certain tests. - */ - @AfterClass(alwaysRun = true) - public void cleanUp() { - String noTest = System.getProperty("noTestCleanup"); - if (Boolean.TRUE.toString().equalsIgnoreCase(noTest)) { - if (logger.isDebugEnabled()) { - logger.debug("Skipping Cleanup phase ..."); - } - return; - } - if (logger.isDebugEnabled()) { - logger.debug("Cleaning up temporary resources created for testing ..."); - } - CollectionObjectClient collectionObjectClient = new CollectionObjectClient(); - for (String resourceId : allResourceIdsCreated) { - // Note: Any non-success responses are ignored and not reported. - collectionObjectClient.delete(resourceId).releaseConnection(); - } - } - - // --------------------------------------------------------------- - // Utility methods used by tests above - // --------------------------------------------------------------- - private void createCollectionObjects(long numToCreate, String keyword) { - List keywords = new ArrayList(); - keywords.add(keyword); - boolean keywordsInSameField = true; - createCollectionObjects(numToCreate, keywords, keywordsInSameField); - } - - private void createCollectionObjects(long numToCreate, List keywords, - boolean keywordsInSameField) { - testSetup(STATUS_CREATED, ServiceRequestType.CREATE); - CollectionObjectClient client = new CollectionObjectClient(); - for (long i = 0; i < numToCreate; i++) { - PoxPayloadOut multipart = - createCollectionObjectInstance(i, keywords, keywordsInSameField); - ClientResponse res = client.create(multipart); - try { - int statusCode = res.getStatus(); - Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); - String id = extractId(res); - allResourceIdsCreated.add(id); - if (logger.isDebugEnabled()) { - logger.debug("Created new resource [" + i + "] with ID " + id); - } - } finally { - res.releaseConnection(); - } - } - } - - private PoxPayloadOut createCollectionObjectInstance(long i, List keywords, - boolean keywordsInSameField) { - CollectionobjectsCommon collectionObject = new CollectionobjectsCommon(); - collectionObject.setObjectNumber(createIdentifier()); - if (keywordsInSameField) { - collectionObject.setDistinguishingFeatures(listToString(keywords, KEYWORD_SEPARATOR)); - } else { - if (keywords.size() == 1) { - collectionObject.setDistinguishingFeatures(keywords.get(0)); - } else if (keywords.size() == 2) { - collectionObject.setDistinguishingFeatures(keywords.get(0)); - collectionObject.setPhysicalDescription(keywords.get(1)); - } else { - Assert.fail("List of keywords must have exactly one or two members."); - } - } - PoxPayloadOut multipart = new PoxPayloadOut(CollectionObjectClient.SERVICE_PAYLOAD_NAME); - PayloadOutputPart commonPart = multipart.addPart(collectionObject, - MediaType.APPLICATION_XML_TYPE); - commonPart.setLabel(new CollectionObjectClient().getCommonPartName()); - return multipart; - } - - private static String listToString(List list, String separator) { - StringBuffer sb = new StringBuffer(); - if (list.size() > 0) { - sb.append(list.get(0)); - for (int i = 1; i < list.size(); i++) { - sb.append(separator); - sb.append(list.get(i)); - } - } - return sb.toString(); - } - - private ClientResponse doSearch(List keywords) { - String searchParamValue = listToString(keywords, KEYWORD_SEPARATOR); - return doSearch(searchParamValue); - } - - private ClientResponse doSearch(String keyword) { - String searchParamValue = keyword; - if (logger.isDebugEnabled()) { - logger.debug("Searching on keyword(s): " + searchParamValue + " ..."); - } - CollectionObjectClient client = new CollectionObjectClient(); - final boolean NOT_INCLUDING_DELETED_RESOURCES = false; - ClientResponse res = - client.keywordSearchIncludeDeleted(searchParamValue, NOT_INCLUDING_DELETED_RESOURCES); - return res; - } - - private long getNumMatched(ClientResponse res, - long numExpectedMatches, String testName) { - AbstractCommonList list = (AbstractCommonList) res.getEntity(AbstractCommonList.class); - long numMatched = list.getTotalItems(); - if (logger.isDebugEnabled()) { - logger.debug("Keyword search matched " + numMatched - + " resources, expected to match " + numExpectedMatches); - } - - // Optionally output additional data about list members for debugging. - if(logger.isTraceEnabled()){ - AbstractCommonListUtils.ListItemsInAbstractCommonList(list, logger, testName); - } - - return numMatched; - } - - private void itemizeListItems(AbstractCommonList list) { - List items = - list.getListItem(); - int i = 0; - for (AbstractCommonList.ListItem item : items) { - logger.debug("list-item[" + i + "] title=" - + AbstractCommonListUtils.ListItemGetElementValue(item, "title")); - logger.debug("list-item[" + i + "] URI=" - + AbstractCommonListUtils.ListItemGetElementValue(item, "uri")); - i++; - } - } - - public static String getSystemTimeIdentifier() { - return Long.toString(System.currentTimeMillis()); - } + /** The logger. */ + private final String CLASS_NAME = CollectionObjectSearchTest.class + .getName(); + private final Logger logger = LoggerFactory.getLogger(CLASS_NAME); + final static String IDENTIFIER = getSystemTimeIdentifier(); + final static String KEYWORD_SEPARATOR = " "; + final long numNoiseWordResources = 10; + final double pctNonNoiseWordResources = 0.5; + // Use this to keep track of resources to delete + private List allResourceIdsCreated = new ArrayList(); + + // Constants for data used in search testing + + // Test keywords unlikely to be encountered in actual collections data, + // consisting of the names of mythical creatures in a 1970s role-playing + // game, which result in very few 'hits' in Google searches. + final static String KEYWORD = "Tsolyani" + IDENTIFIER; + final static List TWO_KEYWORDS = Arrays.asList(new String[] { + "Cheggarra" + IDENTIFIER, "Ahoggya" + IDENTIFIER }); + final static List TWO_MORE_KEYWORDS = Arrays.asList(new String[] { + "Karihaya" + IDENTIFIER, "Hlikku" + IDENTIFIER }); + final static String NOISE_WORD = "Mihalli + IDENTIFIER"; + // Test Unicode UTF-8 term for keyword searching: a random sequence, + // unlikely to be encountered in actual collections data, of two USASCII + // characters followed by four non-USASCII range Unicode UTF-8 characters: + // + // Δ : Greek capital letter Delta (U+0394) + // Ж : Cyrillic capital letter Zhe with breve (U+04C1) + // Ŵ : Latin capital letter W with circumflex (U+0174) + // Ω : Greek capital letter Omega (U+03A9) + final String UTF8_KEYWORD = "to" + '\u0394' + '\u04C1' + '\u0174' + + '\u03A9'; + // Non-existent term unlikely to be encountered in actual collections + // data, consisting of two back-to-back sets of the first letters of + // each of the words in a short pangram for the English alphabet. + final static String NON_EXISTENT_KEYWORD = "jlmbsoqjlmbsoq"; + + @Override + protected String getServiceName() { + throw new UnsupportedOperationException(); // FIXME: REM - See + // http://issues.collectionspace.org/browse/CSPACE-3498 + } + + @Override + protected String getServicePathComponent() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); // FIXME: REM - See + // http://issues.collectionspace.org/browse/CSPACE-3498 + } + + // /* (non-Javadoc) + // * @see + // org.collectionspace.services.client.test.BaseServiceTest#getServicePathComponent() + // */ + // @Override + // protected String getServicePathComponent() { + // return new CollectionObjectClient().getServicePathComponent(); //FIXME: + // REM = Remove all refs to this method. + // } + + /* + * (non-Javadoc) + * + * @see + * org.collectionspace.services.client.test.BaseServiceTest#getClientInstance + * () + */ + @Override + protected CollectionSpaceClient getClientInstance() { + return new CollectionObjectClient(); + } + + /* + * (non-Javadoc) + * + * @see org.collectionspace.services.client.test.BaseServiceTest# + * getAbstractCommonList(org.jboss.resteasy.client.ClientResponse) + */ + @Override + protected AbstractCommonList getAbstractCommonList( + ClientResponse response) { + return response.getEntity(AbstractCommonList.class); + } + + /** + * Creates one or more resources containing a "noise" keyword, which should + * NOT be retrieved by keyword searches. + * + * This also helps ensure that searches will not fail, due to a + * database-specific constraint or otherwise, if the number of records + * containing a particular keyword represent too high a proportion of the + * total number of records. + */ + @BeforeClass(alwaysRun = true) + public void setup() { + if (logger.isDebugEnabled()) { + logger.debug("Creating " + numNoiseWordResources + + " 'noise word' resources ..."); + } + createCollectionObjects(numNoiseWordResources, NOISE_WORD); + } + + // --------------------------------------------------------------- + // Search tests + // --------------------------------------------------------------- + // Success outcomes + + @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class, groups = { "advancedSearch" }) + public void advancedSearch(String testName) throws Exception { + + if (logger.isDebugEnabled()) { + logger.debug(testBanner(testName, CLASS_NAME)); + } + + // Create one or more keyword retrievable resources, each containing + // a specified keyword. + String theKeyword = KEYWORD + "COW"; + long numKeywordRetrievableResources = 1; + createCollectionObjects(numKeywordRetrievableResources, theKeyword); + + // Set the expected status code and group of valid status codes + testSetup(STATUS_OK, ServiceRequestType.SEARCH); + + // Send the search request and receive a response + String propertyName = CollectionObjectClient.SERVICE_COMMON_PART_NAME + ":" + + CollectionObjectJAXBSchema.DISTINGUISHING_FEATURES; + String propertyValue = theKeyword; + ClientResponse res = doAdvancedSearch(propertyName, propertyValue, "="); + int statusCode = res.getStatus(); + + // Check the status code of the response: does it match + // the expected response(s)? + if (logger.isDebugEnabled()) { + logger.debug(testName + ": status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); + + // Verify that the number of resources matched by the search + // is identical to the expected result + long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources; + long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); + Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); + } + + @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class, groups = { "oneKeyword" }) + public void searchWithOneKeyword(String testName) throws Exception { + + if (logger.isDebugEnabled()) { + logger.debug(testBanner(testName, CLASS_NAME)); + } + + // Create one or more keyword retrievable resources, each containing + // a specified keyword. + long numKeywordRetrievableResources = (long) (numNoiseWordResources * pctNonNoiseWordResources); + if (logger.isDebugEnabled()) { + logger.debug("Creating " + numKeywordRetrievableResources + + " keyword-retrievable resources ..."); + } + createCollectionObjects(numKeywordRetrievableResources, KEYWORD); + + // Set the expected status code and group of valid status codes + testSetup(STATUS_OK, ServiceRequestType.SEARCH); + + // Send the search request and receive a response + ClientResponse res = doSearch(KEYWORD); + int statusCode = res.getStatus(); + + // Check the status code of the response: does it match + // the expected response(s)? + if (logger.isDebugEnabled()) { + logger.debug(testName + ": status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); + + // Verify that the number of resources matched by the search + // is identical to the expected result + long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources; + long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); + Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); + } + + @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class) + public void searchWithTwoKeywordsInSameField(String testName) + throws Exception { + + if (logger.isDebugEnabled()) { + logger.debug(testBanner(testName, CLASS_NAME)); + } + + // Create one or more keyword retrievable resources, each containing + // two specified keywords. + long numKeywordRetrievableResources = (long) (numNoiseWordResources * pctNonNoiseWordResources); + if (logger.isDebugEnabled()) { + logger.debug("Creating " + numKeywordRetrievableResources + + " keyword-retrievable resources ..."); + } + boolean keywordsInSameField = true; + createCollectionObjects(numKeywordRetrievableResources, TWO_KEYWORDS, + keywordsInSameField); + + // Set the expected status code and group of valid status codes + testSetup(STATUS_OK, ServiceRequestType.SEARCH); + + // Search using both terms + + // Send the search request and receive a response + ClientResponse res = doSearch(TWO_KEYWORDS); + int statusCode = res.getStatus(); + + // Check the status code of the response: does it match + // the expected response(s)? + if (logger.isDebugEnabled()) { + logger.debug(testName + ": status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); + + // Verify that the number of resources matched by the search + // is identical to the expected result + long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources; + long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); + Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); + + // Search using a single term + + // Send the search request and receive a response + res = doSearch(TWO_KEYWORDS.get(0)); + statusCode = res.getStatus(); + + // Check the status code of the response: does it match + // the expected response(s)? + if (logger.isDebugEnabled()) { + logger.debug(testName + ": status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); + + // Verify that the number of resources matched by the search + // is identical to the expected result + NUM_MATCHES_EXPECTED = numKeywordRetrievableResources; + numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); + Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); + + } + + @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class) + public void searchWithTwoKeywordsAcrossTwoFields(String testName) + throws Exception { + + if (logger.isDebugEnabled()) { + logger.debug(testBanner(testName, CLASS_NAME)); + } + + // Create one or more keyword retrievable resources, each containing + // two specified keywords. + long numKeywordRetrievableResources = 5; + if (logger.isDebugEnabled()) { + logger.debug("Creating " + numKeywordRetrievableResources + + " keyword-retrievable resources ..."); + } + boolean keywordsInSameField = false; + createCollectionObjects(numKeywordRetrievableResources, + TWO_MORE_KEYWORDS, keywordsInSameField); + + // Set the expected status code and group of valid status codes + testSetup(STATUS_OK, ServiceRequestType.SEARCH); + + // Search using both terms + + // Send the search request and receive a response + ClientResponse res = doSearch(TWO_MORE_KEYWORDS); + int statusCode = res.getStatus(); + + // Check the status code of the response: does it match + // the expected response(s)? + if (logger.isDebugEnabled()) { + logger.debug(testName + ": status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); + + // Verify that the number of resources matched by the search + // is identical to the expected result + long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources; + long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); + Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); + + // Search using a single term + + // Send the search request and receive a response + res = doSearch(TWO_MORE_KEYWORDS.get(0)); + statusCode = res.getStatus(); + + // Check the status code of the response: does it match + // the expected response(s)? + if (logger.isDebugEnabled()) { + logger.debug(testName + ": status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); + + // Verify that the number of resources matched by the search + // is identical to the expected result + NUM_MATCHES_EXPECTED = numKeywordRetrievableResources; + numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); + Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); + + } + + // @Test(dataProvider="testName", + // dataProviderClass=AbstractServiceTestImpl.class) + // public void searchWithOneKeywordInRepeatableScalarField(String testName) + // throws Exception { + // BriefDescriptionList descriptionList = new BriefDescriptionList(); + // List descriptions = descriptionList.getBriefDescription(); + // if (TWO_KEYWORDS.size() >= 2) { + // descriptions.add(TWO_KEYWORDS.get(0)); + // descriptions.add(TWO_KEYWORDS.get(1)); + // } + // } + @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class, groups = { "utf8" }) + public void searchWithUTF8Keyword(String testName) { + + if (logger.isDebugEnabled()) { + logger.debug(testBanner(testName, CLASS_NAME)); + } + + // Create one or more keyword retrievable resources, each containing + // two specified keywords. + long numKeywordRetrievableResources = 2; + if (logger.isDebugEnabled()) { + logger.debug("Creating " + numKeywordRetrievableResources + + " keyword-retrievable resources ..."); + } + createCollectionObjects(numKeywordRetrievableResources, UTF8_KEYWORD); + + // Set the expected status code and group of valid status codes + testSetup(STATUS_OK, ServiceRequestType.SEARCH); + + // Send the search request and receive a response + ClientResponse res = doSearch(UTF8_KEYWORD); + int statusCode = res.getStatus(); + + // Check the status code of the response: does it match + // the expected response(s)? + if (logger.isDebugEnabled()) { + logger.debug(testName + ": status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); + + // Verify that the number of resources matched by the search + // is identical to the expected result + long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources; + long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); + Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); + } + + // Failure outcomes + // FIXME: Rename to searchWithNonExistentKeyword + @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class) + public void keywordSearchNonExistentKeyword(String testName) + throws Exception { + + if (logger.isDebugEnabled()) { + logger.debug(testBanner(testName, CLASS_NAME)); + } + + // Set the expected status code and group of valid status codes + testSetup(STATUS_OK, ServiceRequestType.SEARCH); + + // Send the search request and receive a response + ClientResponse res = doSearch(NON_EXISTENT_KEYWORD); + int statusCode = res.getStatus(); + + // Check the status code of the response: does it match + // the expected response(s)? + if (logger.isDebugEnabled()) { + logger.debug(testName + ": status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); + + // Verify that the number of resources matched by the search + // is identical to the expected result + long NUM_MATCHES_EXPECTED = 0; + long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName); + Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED); + + } + + // --------------------------------------------------------------- + // Cleanup of resources created during testing + // --------------------------------------------------------------- + /** + * Deletes all resources created by setup and tests, after all tests have + * been run. + * + * This cleanup method will always be run, even if one or more tests fail. + * For this reason, it attempts to remove all resources created at any point + * during testing, even if some of those resources may be expected to be + * deleted by certain tests. + */ + @AfterClass(alwaysRun = true) + public void cleanUp() { + String noTest = System.getProperty("noTestCleanup"); + if (Boolean.TRUE.toString().equalsIgnoreCase(noTest)) { + if (logger.isDebugEnabled()) { + logger.debug("Skipping Cleanup phase ..."); + } + return; + } + if (logger.isDebugEnabled()) { + logger.debug("Cleaning up temporary resources created for testing ..."); + } + CollectionObjectClient collectionObjectClient = new CollectionObjectClient(); + for (String resourceId : allResourceIdsCreated) { + // Note: Any non-success responses are ignored and not reported. + collectionObjectClient.delete(resourceId).releaseConnection(); + } + } + + // --------------------------------------------------------------- + // Utility methods used by tests above + // --------------------------------------------------------------- + private void createCollectionObjects(long numToCreate, String keyword) { + List keywords = new ArrayList(); + keywords.add(keyword); + boolean keywordsInSameField = true; + createCollectionObjects(numToCreate, keywords, keywordsInSameField); + } + + private void createCollectionObjects(long numToCreate, + List keywords, boolean keywordsInSameField) { + testSetup(STATUS_CREATED, ServiceRequestType.CREATE); + CollectionObjectClient client = new CollectionObjectClient(); + for (long i = 0; i < numToCreate; i++) { + PoxPayloadOut multipart = createCollectionObjectInstance(i, + keywords, keywordsInSameField); + ClientResponse res = client.create(multipart); + try { + int statusCode = res.getStatus(); + Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); + String id = extractId(res); + allResourceIdsCreated.add(id); + if (logger.isDebugEnabled()) { + logger.debug("Created new resource [" + i + "] with ID " + + id); + } + } finally { + res.releaseConnection(); + } + } + } + + private PoxPayloadOut createCollectionObjectInstance(long i, + List keywords, boolean keywordsInSameField) { + CollectionobjectsCommon collectionObject = new CollectionobjectsCommon(); + collectionObject.setObjectNumber(createIdentifier()); + if (keywordsInSameField) { + collectionObject.setDistinguishingFeatures(listToString(keywords, + KEYWORD_SEPARATOR)); + } else { + if (keywords.size() == 1) { + collectionObject.setDistinguishingFeatures(keywords.get(0)); + } else if (keywords.size() == 2) { + collectionObject.setDistinguishingFeatures(keywords.get(0)); + collectionObject.setPhysicalDescription(keywords.get(1)); + } else { + Assert.fail("List of keywords must have exactly one or two members."); + } + } + PoxPayloadOut multipart = new PoxPayloadOut( + CollectionObjectClient.SERVICE_PAYLOAD_NAME); + PayloadOutputPart commonPart = multipart.addPart(collectionObject, + MediaType.APPLICATION_XML_TYPE); + commonPart.setLabel(new CollectionObjectClient().getCommonPartName()); + return multipart; + } + + private static String listToString(List list, String separator) { + StringBuffer sb = new StringBuffer(); + if (list.size() > 0) { + sb.append(list.get(0)); + for (int i = 1; i < list.size(); i++) { + sb.append(separator); + sb.append(list.get(i)); + } + } + return sb.toString(); + } + + private ClientResponse doSearch(List keywords) { + String searchParamValue = listToString(keywords, KEYWORD_SEPARATOR); + return doSearch(searchParamValue); + } + + private ClientResponse doAdvancedSearch( + String propertyName, String propertyValue, String operator) { + if (logger.isDebugEnabled()) { + logger.debug("Searching on property: " + propertyName + "=" + + "'" + propertyValue + "'"); + } + String whereClause = propertyName + operator + + "'" + propertyValue + "'"; + CollectionObjectClient client = new CollectionObjectClient(); + ClientResponse res = client + .advancedSearchIncludeDeleted(whereClause, false); // NOT_INCLUDING_DELETED_RESOURCES + return res; + } + + private ClientResponse doSearch(String keyword) { + String searchParamValue = keyword; + if (logger.isDebugEnabled()) { + logger.debug("Searching on keyword(s): " + searchParamValue + + " ..."); + } + CollectionObjectClient client = new CollectionObjectClient(); + final boolean NOT_INCLUDING_DELETED_RESOURCES = false; + ClientResponse res = client + .keywordSearchIncludeDeleted(searchParamValue, + NOT_INCLUDING_DELETED_RESOURCES); + return res; + } + + private long getNumMatched(ClientResponse res, + long numExpectedMatches, String testName) { + AbstractCommonList list = (AbstractCommonList) res + .getEntity(AbstractCommonList.class); + long numMatched = list.getTotalItems(); + if (logger.isDebugEnabled()) { + logger.debug("Keyword search matched " + numMatched + + " resources, expected to match " + numExpectedMatches); + } + + // Optionally output additional data about list members for debugging. + if (logger.isTraceEnabled()) { + AbstractCommonListUtils.ListItemsInAbstractCommonList(list, logger, + testName); + } + + return numMatched; + } + + private void itemizeListItems(AbstractCommonList list) { + List items = list.getListItem(); + int i = 0; + for (AbstractCommonList.ListItem item : items) { + logger.debug("list-item[" + + i + + "] title=" + + AbstractCommonListUtils.ListItemGetElementValue(item, + "title")); + logger.debug("list-item[" + + i + + "] URI=" + + AbstractCommonListUtils.ListItemGetElementValue(item, + "uri")); + i++; + } + } + + public static String getSystemTimeIdentifier() { + return Long.toString(System.currentTimeMillis()); + } } diff --git a/services/common/src/main/java/org/collectionspace/services/common/ResourceBase.java b/services/common/src/main/java/org/collectionspace/services/common/ResourceBase.java index 09c4ba6e9..efb29fa76 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/ResourceBase.java +++ b/services/common/src/main/java/org/collectionspace/services/common/ResourceBase.java @@ -227,9 +227,10 @@ public abstract class ResourceBase public AbstractCommonList getList(@Context UriInfo ui) { MultivaluedMap queryParams = ui.getQueryParameters(); String keywords = queryParams.getFirst(IQueryManager.SEARCH_TYPE_KEYWORDS_KW); + String advancedSearch = queryParams.getFirst(IQueryManager.SEARCH_TYPE_KEYWORDS_AS); AbstractCommonList list; - if (keywords != null) { - list = search(queryParams, keywords); + if (keywords != null || advancedSearch != null) { + list = search(queryParams, keywords, advancedSearch); } else { list = getList(queryParams); } @@ -257,7 +258,9 @@ public abstract class ResourceBase } } - protected AbstractCommonList search(MultivaluedMap queryParams, String keywords) { + protected AbstractCommonList search(MultivaluedMap queryParams, + String keywords, + String advancedSearch) { try { ServiceContext ctx = createServiceContext(queryParams); DocumentHandler handler = createDocumentHandler(ctx); @@ -270,6 +273,14 @@ public abstract class ResourceBase logger.debug("The WHERE clause is: " + documentFilter.getWhereClause()); } } + if (advancedSearch != null && !advancedSearch.isEmpty()) { + String whereClause = QueryManager.createWhereClauseFromAdvancedSearch(advancedSearch); + DocumentFilter documentFilter = handler.getDocumentFilter(); + documentFilter.appendWhereClause(whereClause, IQueryManager.SEARCH_QUALIFIER_AND); + if (logger.isDebugEnabled()) { + logger.debug("The WHERE clause is: " + documentFilter.getWhereClause()); + } + } getRepositoryClient(ctx).getFiltered(ctx, handler); return (AbstractCommonList) handler.getCommonPartList(); } catch (Exception e) { diff --git a/services/common/src/main/java/org/collectionspace/services/common/query/QueryManager.java b/services/common/src/main/java/org/collectionspace/services/common/query/QueryManager.java index cf88207f0..8a0e0c0a0 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/query/QueryManager.java +++ b/services/common/src/main/java/org/collectionspace/services/common/query/QueryManager.java @@ -47,6 +47,10 @@ public class QueryManager { return queryManager.createWhereClauseFromKeywords(keywords); } + static public String createWhereClauseFromAdvancedSearch(String keywords) { + return queryManager.createWhereClauseFromAdvancedSearch(keywords); + } + /** * Creates the where clause for partial term match. * diff --git a/services/common/src/main/java/org/collectionspace/services/common/query/nuxeo/QueryManagerNuxeoImpl.java b/services/common/src/main/java/org/collectionspace/services/common/query/nuxeo/QueryManagerNuxeoImpl.java index 3c396ab7e..16be5b55a 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/query/nuxeo/QueryManagerNuxeoImpl.java +++ b/services/common/src/main/java/org/collectionspace/services/common/query/nuxeo/QueryManagerNuxeoImpl.java @@ -45,28 +45,31 @@ import org.collectionspace.services.common.storage.DatabaseProductType; import org.collectionspace.services.common.storage.JDBCTools; public class QueryManagerNuxeoImpl implements IQueryManager { - - private static String ECM_FULLTEXT_LIKE = - "ecm:fulltext" + SEARCH_TERM_SEPARATOR + IQueryManager.SEARCH_LIKE; + + private static String ECM_FULLTEXT_LIKE = "ecm:fulltext" + + SEARCH_TERM_SEPARATOR + IQueryManager.SEARCH_LIKE; private static String SEARCH_LIKE_FORM = null; private final Logger logger = LoggerFactory .getLogger(QueryManagerNuxeoImpl.class); - - // Consider that letters, letter-markers, numbers, '_' and apostrophe are words - private static Pattern nonWordChars = Pattern.compile("[^\\p{L}\\p{M}\\p{N}_']"); + + // Consider that letters, letter-markers, numbers, '_' and apostrophe are + // words + private static Pattern nonWordChars = Pattern + .compile("[^\\p{L}\\p{M}\\p{N}_']"); private static Pattern unescapedDblQuotes = Pattern.compile("(?