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