]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
5fbd377bcfc7657ce5a2ef52de21f2a86581afde
[tmp/jakarta-migration.git] /
1 /**
2  * This document is a part of the source code and related artifacts
3  * for CollectionSpace, an open source collections management system
4  * for museums and related institutions:
5  *
6  * http://www.collectionspace.org
7  * http://wiki.collectionspace.org
8  *
9  * Copyright © 2009 Regents of the University of California
10  *
11  * Licensed under the Educational Community License (ECL), Version 2.0.
12  * You may not use this file except in compliance with this License.
13  *
14  * You may obtain a copy of the ECL 2.0 License at
15  * https://source.collectionspace.org/collection-space/LICENSE.txt
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 package org.collectionspace.services.client.test;
24
25 import java.util.Arrays;
26 import java.util.ArrayList;
27 import java.util.List;
28 import javax.ws.rs.core.MediaType;
29 import javax.ws.rs.core.Response;
30 import org.collectionspace.services.client.CollectionObjectClient;
31 import org.collectionspace.services.client.CollectionSpaceClient;
32 import org.collectionspace.services.client.PayloadOutputPart;
33 import org.collectionspace.services.client.PoxPayloadOut;
34 import org.collectionspace.services.collectionobject.CollectionobjectsCommon;
35 import org.collectionspace.services.common.AbstractCommonListUtils;
36 import org.collectionspace.services.jaxb.AbstractCommonList;
37 import org.jboss.resteasy.client.ClientResponse;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40 import org.testng.Assert;
41 import org.testng.annotations.AfterClass;
42 import org.testng.annotations.BeforeClass;
43 import org.testng.annotations.Test;
44
45 /**
46  * CollectionObjectSearchTest, carries out tests of keyword
47  * search functionality against a deployed and running
48  * CollectionObject Service.
49  *
50  * $LastChangedRevision: 1327 $
51  * $LastChangedDate: 2010-02-12 10:35:11 -0800 (Fri, 12 Feb 2010) $
52  */
53 public class CollectionObjectSearchTest extends BaseServiceTest {
54
55     /** The logger. */
56     private final String CLASS_NAME = CollectionObjectSearchTest.class.getName();
57     private final Logger logger = LoggerFactory.getLogger(CLASS_NAME);
58     final static String IDENTIFIER = getSystemTimeIdentifier();
59     final static String KEYWORD_SEPARATOR = " ";
60     final long numNoiseWordResources = 10;
61     final double pctNonNoiseWordResources = 0.5;
62     // Use this to keep track of resources to delete
63     private List<String> allResourceIdsCreated = new ArrayList<String>();
64     
65     // Constants for data used in search testing
66     
67     // Test keywords unlikely to be encountered in actual collections data,
68     // consisting of the names of mythical creatures in a 1970s role-playing
69     // game, which result in very few 'hits' in Google searches.
70     final static String KEYWORD = "Tsolyani" + IDENTIFIER;
71     final static List<String> TWO_KEYWORDS =
72             Arrays.asList(new String[]{"Cheggarra" + IDENTIFIER, "Ahoggya" + IDENTIFIER});
73     final static List<String> TWO_MORE_KEYWORDS =
74             Arrays.asList(new String[]{"Karihaya" + IDENTIFIER, "Hlikku" + IDENTIFIER});
75     final static String NOISE_WORD = "Mihalli + IDENTIFIER";
76     // Test Unicode UTF-8 term for keyword searching: a random sequence,
77     // unlikely to be encountered in actual collections data, of two USASCII
78     // characters followed by four non-USASCII range Unicode UTF-8 characters:
79     //
80     // Δ : Greek capital letter Delta (U+0394)
81     // Ж : Cyrillic capital letter Zhe with breve (U+04C1)
82     // Ŵ : Latin capital letter W with circumflex (U+0174)
83     // Ω : Greek capital letter Omega (U+03A9)
84     final String UTF8_KEYWORD = "to" + '\u0394' + '\u04C1' + '\u0174' + '\u03A9';
85     // Non-existent term unlikely to be encountered in actual collections
86     // data, consisting of two back-to-back sets of the first letters of
87     // each of the words in a short pangram for the English alphabet.
88      final static String NON_EXISTENT_KEYWORD = "jlmbsoqjlmbsoq";
89    
90     @Override
91     protected String getServiceName() {
92         throw new UnsupportedOperationException(); //FIXME: REM - See http://issues.collectionspace.org/browse/CSPACE-3498
93     }
94
95     @Override
96     protected String getServicePathComponent() {
97         // TODO Auto-generated method stub
98         throw new UnsupportedOperationException(); //FIXME: REM - See http://issues.collectionspace.org/browse/CSPACE-3498
99     }
100
101 //    /* (non-Javadoc)
102 //     * @see org.collectionspace.services.client.test.BaseServiceTest#getServicePathComponent()
103 //     */
104 //    @Override
105 //    protected String getServicePathComponent() {
106 //        return new CollectionObjectClient().getServicePathComponent(); //FIXME: REM = Remove all refs to this method.
107 //    }
108
109     /* (non-Javadoc)
110      * @see org.collectionspace.services.client.test.BaseServiceTest#getClientInstance()
111      */
112     @Override
113     protected CollectionSpaceClient getClientInstance() {
114         return new CollectionObjectClient();
115     }
116
117     /* (non-Javadoc)
118      * @see org.collectionspace.services.client.test.BaseServiceTest#getAbstractCommonList(org.jboss.resteasy.client.ClientResponse)
119      */
120     @Override
121     protected AbstractCommonList getAbstractCommonList(ClientResponse<AbstractCommonList> response) {
122         return response.getEntity(AbstractCommonList.class);
123     }
124
125     /**
126      * Creates one or more resources containing a "noise" keyword,
127      * which should NOT be retrieved by keyword searches.
128      *
129      * This also helps ensure that searches will not fail, due
130      * to a database-specific constraint or otherwise, if the
131      * number of records containing a particular keyword represent
132      * too high a proportion of the total number of records.
133      */
134     @BeforeClass(alwaysRun = true)
135     public void setup() {
136         if (logger.isDebugEnabled()) {
137             logger.debug("Creating " + numNoiseWordResources
138                     + " 'noise word' resources ...");
139         }
140         createCollectionObjects(numNoiseWordResources, NOISE_WORD);
141     }
142
143     // ---------------------------------------------------------------
144     // Search tests
145     // ---------------------------------------------------------------
146     // Success outcomes
147     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
148     groups = {"oneKeyword"})
149     public void searchWithOneKeyword(String testName) throws Exception {
150
151         if (logger.isDebugEnabled()) {
152             logger.debug(testBanner(testName, CLASS_NAME));
153         }
154
155         // Create one or more keyword retrievable resources, each containing
156         // a specified keyword.
157         long numKeywordRetrievableResources =
158                 (long) (numNoiseWordResources * pctNonNoiseWordResources);
159         if (logger.isDebugEnabled()) {
160             logger.debug("Creating " + numKeywordRetrievableResources
161                     + " keyword-retrievable resources ...");
162         }
163         createCollectionObjects(numKeywordRetrievableResources, KEYWORD);
164
165         // Set the expected status code and group of valid status codes
166         testSetup(STATUS_OK, ServiceRequestType.SEARCH);
167
168         // Send the search request and receive a response
169         ClientResponse<AbstractCommonList> res = doSearch(KEYWORD);
170         int statusCode = res.getStatus();
171
172         // Check the status code of the response: does it match
173         // the expected response(s)?
174         if (logger.isDebugEnabled()) {
175             logger.debug(testName + ": status = " + statusCode);
176         }
177         Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
178                 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
179         Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
180
181         // Verify that the number of resources matched by the search
182         // is identical to the expected result
183         long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources;
184         long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName);
185         Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED);
186     }
187
188     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class)
189     public void searchWithTwoKeywordsInSameField(String testName) throws Exception {
190
191         if (logger.isDebugEnabled()) {
192             logger.debug(testBanner(testName, CLASS_NAME));
193         }
194
195         // Create one or more keyword retrievable resources, each containing
196         // two specified keywords.
197         long numKeywordRetrievableResources =
198                 (long) (numNoiseWordResources * pctNonNoiseWordResources);
199         if (logger.isDebugEnabled()) {
200             logger.debug("Creating " + numKeywordRetrievableResources
201                     + " keyword-retrievable resources ...");
202         }
203         boolean keywordsInSameField = true;
204         createCollectionObjects(numKeywordRetrievableResources, TWO_KEYWORDS, keywordsInSameField);
205
206         // Set the expected status code and group of valid status codes
207         testSetup(STATUS_OK, ServiceRequestType.SEARCH);
208
209         // Search using both terms
210
211         // Send the search request and receive a response
212         ClientResponse<AbstractCommonList> res = doSearch(TWO_KEYWORDS);
213         int statusCode = res.getStatus();
214
215         // Check the status code of the response: does it match
216         // the expected response(s)?
217         if (logger.isDebugEnabled()) {
218             logger.debug(testName + ": status = " + statusCode);
219         }
220         Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
221                 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
222         Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
223
224         // Verify that the number of resources matched by the search
225         // is identical to the expected result
226         long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources;
227         long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName);
228         Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED);
229
230         // Search using a single term
231
232         // Send the search request and receive a response
233         res = doSearch(TWO_KEYWORDS.get(0));
234         statusCode = res.getStatus();
235
236         // Check the status code of the response: does it match
237         // the expected response(s)?
238         if (logger.isDebugEnabled()) {
239             logger.debug(testName + ": status = " + statusCode);
240         }
241         Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
242                 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
243         Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
244
245         // Verify that the number of resources matched by the search
246         // is identical to the expected result
247         NUM_MATCHES_EXPECTED = numKeywordRetrievableResources;
248         numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName);
249         Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED);
250
251     }
252
253     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class)
254     public void searchWithTwoKeywordsAcrossTwoFields(String testName) throws Exception {
255
256         if (logger.isDebugEnabled()) {
257             logger.debug(testBanner(testName, CLASS_NAME));
258         }
259
260         // Create one or more keyword retrievable resources, each containing
261         // two specified keywords.
262         long numKeywordRetrievableResources = 5;
263         if (logger.isDebugEnabled()) {
264             logger.debug("Creating " + numKeywordRetrievableResources
265                     + " keyword-retrievable resources ...");
266         }
267         boolean keywordsInSameField = false;
268         createCollectionObjects(numKeywordRetrievableResources, TWO_MORE_KEYWORDS, keywordsInSameField);
269
270         // Set the expected status code and group of valid status codes
271         testSetup(STATUS_OK, ServiceRequestType.SEARCH);
272
273         // Search using both terms
274
275         // Send the search request and receive a response
276         ClientResponse<AbstractCommonList> res = doSearch(TWO_MORE_KEYWORDS);
277         int statusCode = res.getStatus();
278
279         // Check the status code of the response: does it match
280         // the expected response(s)?
281         if (logger.isDebugEnabled()) {
282             logger.debug(testName + ": status = " + statusCode);
283         }
284         Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
285                 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
286         Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
287
288         // Verify that the number of resources matched by the search
289         // is identical to the expected result
290         long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources;
291         long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName);
292         Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED);
293
294         // Search using a single term
295
296         // Send the search request and receive a response
297         res = doSearch(TWO_MORE_KEYWORDS.get(0));
298         statusCode = res.getStatus();
299
300         // Check the status code of the response: does it match
301         // the expected response(s)?
302         if (logger.isDebugEnabled()) {
303             logger.debug(testName + ": status = " + statusCode);
304         }
305         Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
306                 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
307         Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
308
309         // Verify that the number of resources matched by the search
310         // is identical to the expected result
311         NUM_MATCHES_EXPECTED = numKeywordRetrievableResources;
312         numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName);
313         Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED);
314
315     }
316
317 //    @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class)
318 //    public void searchWithOneKeywordInRepeatableScalarField(String testName) throws Exception {
319 //        BriefDescriptionList descriptionList = new BriefDescriptionList();
320 //        List<String> descriptions = descriptionList.getBriefDescription();
321 //        if (TWO_KEYWORDS.size() >= 2) {
322 //            descriptions.add(TWO_KEYWORDS.get(0));
323 //            descriptions.add(TWO_KEYWORDS.get(1));
324 //        }
325 //    }
326     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
327     groups = {"utf8"})
328     public void searchWithUTF8Keyword(String testName) {
329
330         if (logger.isDebugEnabled()) {
331             logger.debug(testBanner(testName, CLASS_NAME));
332         }
333
334         // Create one or more keyword retrievable resources, each containing
335         // two specified keywords.
336         long numKeywordRetrievableResources = 2;
337         if (logger.isDebugEnabled()) {
338             logger.debug("Creating " + numKeywordRetrievableResources
339                     + " keyword-retrievable resources ...");
340         }
341         createCollectionObjects(numKeywordRetrievableResources, UTF8_KEYWORD);
342
343         // Set the expected status code and group of valid status codes
344         testSetup(STATUS_OK, ServiceRequestType.SEARCH);
345
346         // Send the search request and receive a response
347         ClientResponse<AbstractCommonList> res = doSearch(UTF8_KEYWORD);
348         int statusCode = res.getStatus();
349
350         // Check the status code of the response: does it match
351         // the expected response(s)?
352         if (logger.isDebugEnabled()) {
353             logger.debug(testName + ": status = " + statusCode);
354         }
355         Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
356                 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
357         Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
358
359         // Verify that the number of resources matched by the search
360         // is identical to the expected result
361         long NUM_MATCHES_EXPECTED = numKeywordRetrievableResources;
362         long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName);
363         Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED);
364     }
365
366     // Failure outcomes
367     // FIXME: Rename to searchWithNonExistentKeyword
368     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class)
369     public void keywordSearchNonExistentKeyword(String testName) throws Exception {
370
371         if (logger.isDebugEnabled()) {
372             logger.debug(testBanner(testName, CLASS_NAME));
373         }
374
375         // Set the expected status code and group of valid status codes
376         testSetup(STATUS_OK, ServiceRequestType.SEARCH);
377
378         // Send the search request and receive a response
379         ClientResponse<AbstractCommonList> res = doSearch(NON_EXISTENT_KEYWORD);
380         int statusCode = res.getStatus();
381
382         // Check the status code of the response: does it match
383         // the expected response(s)?
384         if (logger.isDebugEnabled()) {
385             logger.debug(testName + ": status = " + statusCode);
386         }
387         Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode),
388                 invalidStatusCodeMessage(REQUEST_TYPE, statusCode));
389         Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
390
391         // Verify that the number of resources matched by the search
392         // is identical to the expected result
393         long NUM_MATCHES_EXPECTED = 0;
394         long numMatched = getNumMatched(res, NUM_MATCHES_EXPECTED, testName);
395         Assert.assertEquals(numMatched, NUM_MATCHES_EXPECTED);
396
397     }
398
399     // ---------------------------------------------------------------
400     // Cleanup of resources created during testing
401     // ---------------------------------------------------------------
402     /**
403      * Deletes all resources created by setup and tests, after all tests have been run.
404      *
405      * This cleanup method will always be run, even if one or more tests fail.
406      * For this reason, it attempts to remove all resources created
407      * at any point during testing, even if some of those resources
408      * may be expected to be deleted by certain tests.
409      */
410     @AfterClass(alwaysRun = true)
411     public void cleanUp() {
412         String noTest = System.getProperty("noTestCleanup");
413         if (Boolean.TRUE.toString().equalsIgnoreCase(noTest)) {
414             if (logger.isDebugEnabled()) {
415                 logger.debug("Skipping Cleanup phase ...");
416             }
417             return;
418         }
419         if (logger.isDebugEnabled()) {
420             logger.debug("Cleaning up temporary resources created for testing ...");
421         }
422         CollectionObjectClient collectionObjectClient = new CollectionObjectClient();
423         for (String resourceId : allResourceIdsCreated) {
424             // Note: Any non-success responses are ignored and not reported.
425             collectionObjectClient.delete(resourceId).releaseConnection();
426         }
427     }
428
429     // ---------------------------------------------------------------
430     // Utility methods used by tests above
431     // ---------------------------------------------------------------
432     private void createCollectionObjects(long numToCreate, String keyword) {
433         List keywords = new ArrayList<String>();
434         keywords.add(keyword);
435         boolean keywordsInSameField = true;
436         createCollectionObjects(numToCreate, keywords, keywordsInSameField);
437     }
438
439     private void createCollectionObjects(long numToCreate, List<String> keywords,
440             boolean keywordsInSameField) {
441         testSetup(STATUS_CREATED, ServiceRequestType.CREATE);
442         CollectionObjectClient client = new CollectionObjectClient();
443         for (long i = 0; i < numToCreate; i++) {
444             PoxPayloadOut multipart =
445                     createCollectionObjectInstance(i, keywords, keywordsInSameField);
446             ClientResponse<Response> res = client.create(multipart);
447             try {
448                 int statusCode = res.getStatus();
449                 Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
450                 String id = extractId(res);
451                 allResourceIdsCreated.add(id);
452                 if (logger.isDebugEnabled()) {
453                     logger.debug("Created new resource [" + i + "] with ID " + id);
454                 }
455             } finally {
456                 res.releaseConnection();
457             }
458         }
459     }
460
461     private PoxPayloadOut createCollectionObjectInstance(long i, List<String> keywords,
462             boolean keywordsInSameField) {
463         CollectionobjectsCommon collectionObject = new CollectionobjectsCommon();
464         collectionObject.setObjectNumber(createIdentifier());
465         if (keywordsInSameField) {
466             collectionObject.setDistinguishingFeatures(listToString(keywords, KEYWORD_SEPARATOR));
467         } else {
468             if (keywords.size() == 1) {
469                 collectionObject.setDistinguishingFeatures(keywords.get(0));
470             } else if (keywords.size() == 2) {
471                 collectionObject.setDistinguishingFeatures(keywords.get(0));
472                 collectionObject.setPhysicalDescription(keywords.get(1));
473             } else {
474                 Assert.fail("List of keywords must have exactly one or two members.");
475             }
476         }
477         PoxPayloadOut multipart = new PoxPayloadOut(CollectionObjectClient.SERVICE_PAYLOAD_NAME);
478         PayloadOutputPart commonPart = multipart.addPart(collectionObject,
479                 MediaType.APPLICATION_XML_TYPE);
480         commonPart.setLabel(new CollectionObjectClient().getCommonPartName());
481         return multipart;
482     }
483
484     private static String listToString(List<String> list, String separator) {
485         StringBuffer sb = new StringBuffer();
486         if (list.size() > 0) {
487             sb.append(list.get(0));
488             for (int i = 1; i < list.size(); i++) {
489                 sb.append(separator);
490                 sb.append(list.get(i));
491             }
492         }
493         return sb.toString();
494     }
495
496     private ClientResponse<AbstractCommonList> doSearch(List<String> keywords) {
497         String searchParamValue = listToString(keywords, KEYWORD_SEPARATOR);
498         return doSearch(searchParamValue);
499     }
500
501     private ClientResponse<AbstractCommonList> doSearch(String keyword) {
502         String searchParamValue = keyword;
503         if (logger.isDebugEnabled()) {
504             logger.debug("Searching on keyword(s): " + searchParamValue + " ...");
505         }
506         CollectionObjectClient client = new CollectionObjectClient();
507         final boolean NOT_INCLUDING_DELETED_RESOURCES = false;
508         ClientResponse<AbstractCommonList> res =
509                 client.keywordSearchIncludeDeleted(searchParamValue, NOT_INCLUDING_DELETED_RESOURCES);
510         return res;
511     }
512
513     private long getNumMatched(ClientResponse<AbstractCommonList> res,
514             long numExpectedMatches, String testName) {
515         AbstractCommonList list = (AbstractCommonList) res.getEntity(AbstractCommonList.class);
516         long numMatched = list.getTotalItems();
517         if (logger.isDebugEnabled()) {
518             logger.debug("Keyword search matched " + numMatched
519                     + " resources, expected to match " + numExpectedMatches);
520         }
521
522         // Optionally output additional data about list members for debugging.
523         if(logger.isTraceEnabled()){
524                 AbstractCommonListUtils.ListItemsInAbstractCommonList(list, logger, testName);
525         }
526         
527         return numMatched;
528     }
529
530     private void itemizeListItems(AbstractCommonList list) {
531         List<AbstractCommonList.ListItem> items =
532                 list.getListItem();
533         int i = 0;
534         for (AbstractCommonList.ListItem item : items) {
535             logger.debug("list-item[" + i + "] title="
536                     + AbstractCommonListUtils.ListItemGetElementValue(item, "title"));
537             logger.debug("list-item[" + i + "] URI="
538                     + AbstractCommonListUtils.ListItemGetElementValue(item, "uri"));
539             i++;
540         }
541     }
542
543     public static String getSystemTimeIdentifier() {
544         return Long.toString(System.currentTimeMillis());
545     }
546 }