]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
0454a3e02081674cdc0a3807c9ba20143959acc3
[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  
24 package org.collectionspace.services.client.test;
25
26 import java.util.ArrayList;
27 import java.util.List;
28 import javax.ws.rs.core.MultivaluedMap;
29 import javax.ws.rs.core.Response;
30 import javax.xml.bind.JAXBContext;
31 import javax.xml.bind.Marshaller;
32 import org.jboss.resteasy.client.ClientResponse;
33 import org.testng.Assert;
34 import org.testng.annotations.Test;
35
36 import org.collectionspace.services.client.CollectionObjectClient;
37 import org.collectionspace.services.collectionobject.CollectionObject;
38 import org.collectionspace.services.collectionobject.CollectionObjectList;
39
40 import java.io.IOException;
41 import java.io.UnsupportedEncodingException;
42 import java.util.Arrays;
43 import java.util.Set;
44 import javax.ws.rs.core.MediaType;
45 import javax.ws.rs.core.Response.Status;
46 // import org.jboss.resteasy.client.ClientRequest;
47 import org.collectionspace.services.client.TestServiceClient;
48 import org.apache.commons.httpclient.Header;
49 import org.apache.commons.httpclient.HttpClient;
50 import org.apache.commons.httpclient.HttpException;
51 import org.apache.commons.httpclient.HttpMethod;
52 import org.apache.commons.httpclient.HttpStatus;
53 import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
54 import org.apache.commons.httpclient.methods.GetMethod;
55 import org.apache.commons.httpclient.methods.PostMethod;
56 import org.apache.commons.httpclient.methods.PutMethod;
57 import org.apache.commons.httpclient.methods.RequestEntity;
58 import org.apache.commons.httpclient.methods.StringRequestEntity;
59
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 /**
64  * CollectionObjectServiceTest, carries out tests against a
65  * deployed and running CollectionObject Service.
66  * 
67  * $LastChangedRevision$
68  * $LastChangedDate$
69  */
70 public class CollectionObjectServiceTest {
71
72   // Instance variables specific to this test.
73   final Logger logger = LoggerFactory.getLogger(CollectionObjectServiceTest.class);
74   private CollectionObjectClient client = new CollectionObjectClient();
75
76   // Instance variables common to all entity service test classes.
77   private String knownObjectId = null;
78   private final String NON_EXISTENT_ID = createNonExistentIdentifier();
79   private HttpClient httpClient = new HttpClient();
80   private TestServiceClient serviceClient = new TestServiceClient();
81
82   
83   // ---------------------------------------------------------------
84   // Service Discovery tests
85   // ---------------------------------------------------------------
86
87   // TBA
88   
89   
90   // ---------------------------------------------------------------
91   // CRUD tests : CREATE tests
92   // ---------------------------------------------------------------
93
94   // Success outcomes
95   // ----------------
96   
97   /**
98    * Tests creation of a new resource of the specified type.
99    *
100    * The 'Location' header will contain the URL for the newly created object.
101    * This is required by the extractId() utility method, below.
102    *
103    * The newly-created resource is also used by other test(s)
104    * (e.g. update, delete) which follow, below.
105    */
106   @Test
107   public void create() {
108
109      // Expected status code: 201 Created
110     final int EXPECTED_STATUS_CODE = Response.Status.CREATED.getStatusCode();
111
112     // Submit the request to the service and store the response.
113     String identifier = this.createIdentifier();
114     CollectionObject collectionObject = createCollectionObject(identifier);
115     ClientResponse<Response> res = client.createCollectionObject(collectionObject);
116     int statusCode = res.getStatus();
117
118     // Experiment to determine if we can programmatically obtain the HTTP method
119     // used in the request, from the response headers, so we can provide that to
120     // statusCodeWithinExpectedSet(), below.
121     //
122     // It doesn't appear that we can; tried this also with 'curl -i {url}' without success.
123     //
124     // Need to look into whether this is currently returned from our Java Client Library
125     // 'service access' classes, or whether we can add this functionality there.
126 /*
127     MultivaluedMap metadata = res.getMetadata();    
128     Set<String> set = metadata.keySet();
129     for (String key : set) {
130       // Note: values in the metadata (HTTP response headers) are of type List
131       verbose(key + " : " + metadata.get(key).toString());
132     }
133 */    
134
135     // Check the status code of the response: does it match the expected response(s)?
136     verbose("create: status = " + statusCode);
137     final String REQUEST_TYPE="CREATE";  // @TODO Consider replacing with enum.
138     Assert.assertTrue(statusCodeWithinExpectedSet(REQUEST_TYPE, statusCode),
139       "Status code '" + statusCode + "' in response is NOT within the expected set.");
140     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
141
142     // Store the ID returned from this create operation for additional tests below.
143     knownObjectId = extractId(res);
144   }
145
146   /**
147    * Creates two or more new objects of the specified type.
148    *
149    * Repeatedly calls the create test, above, and relies on its
150    * test assertions.
151    *
152    * The newly-created objects are also used by other test(s)
153    * (e.g. read multiple/list) which follow, below.
154    */
155   @Test(dependsOnMethods = {"create"})
156   public void createMultiple() {
157     for(int i = 0; i < 3; i++){
158       this.create();
159     }
160   }
161
162   // Failure outcomes
163   // ----------------
164
165   /**
166    * Tests creation of a resource of the specified type by sending a null to the client proxy.
167    *
168    */
169   @Test(dependsOnMethods = {"create"}, expectedExceptions = IllegalArgumentException.class)
170   public void createNull() {
171
172    // Expected result: IllegalArgumentException
173     ClientResponse<Response> res = client.createCollectionObject(null);
174   }
175   
176   /**
177    * Tests creation of a resource of the specified type by sending malformed XML data
178    * in the entity body of the request.
179    */
180 /*
181   @Test(dependsOnMethods = {"create", "testSubmitRequest"})
182   public void createWithMalformedXml() {
183
184     // Expected status code: 400 Bad Request
185     final int EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
186
187     // @TODO This test is currently commented out, because it returns a
188     // 500 Internal Server Error status code, rather than the expected status code.
189
190     // Submit the request to the service and store the response.
191     String url = getServiceRootURL();
192     PostMethod method = new PostMethod(url);
193     final String MALFORMED_XML_DATA =
194       "<malformed_xml>wrong schema contents</malformed_xml"; // Note: intentionally missing bracket.
195     StringRequestEntity entity = getXmlEntity(MALFORMED_XML_DATA);
196     int statusCode = submitRequest(method, entity);
197     
198     // Check the status code of the response: does it match the expected response(s)?
199     verbose("createWithMalformedXml url=" + url + " status=" + statusCode);
200     final String REQUEST_TYPE="CREATE";  // @TODO Consider replacing with enum.
201     Assert.assertTrue(statusCodeWithinExpectedSet(REQUEST_TYPE, statusCode),
202       "Status code '" + statusCode + "' in response is NOT within the expected set.");
203     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
204   }
205 */
206
207   /**
208    * Tests creation of a resource of the specified type by sending data
209    * in the wrong schema (e.g. in a format that doesn't match the object's schema)
210    * in the entity body of the request.
211     */
212 /*
213   @Test(dependsOnMethods = {"create", "testSubmitRequest", "createWithMalformedXml"})
214   public void createWithWrongSchema() {
215   
216     // Expected status code: 400 Bad Request
217     final int EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
218
219     // @TODO This test is currently commented out, because it returns a
220     // 500 Internal Server Error status code, rather than the expected status code.
221    
222     // Submit the request to the service and store the response.
223     String url = getServiceRootURL();
224     PostMethod method = new PostMethod(url);
225     final String WRONG_SCHEMA_DATA = "<wrong_schema>wrong schema contents</wrong_schema>";
226     StringRequestEntity entity = getXmlEntity(WRONG_SCHEMA_DATA);
227     int statusCode = submitRequest(method, entity);
228     
229     // Check the status code of the response: does it match the expected response(s)?
230     verbose("createWithWrongSchema url=" + url + " status=" + statusCode);
231     final String REQUEST_TYPE="CREATE";  // @TODO Consider replacing with enum.
232     Assert.assertTrue(statusCodeWithinExpectedSet(REQUEST_TYPE, statusCode),
233       "Status code '" + statusCode + "' in response is NOT within the expected set.");
234     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
235   }
236 */  
237
238   /**
239    * Tests creation of a resource of the specified type,
240    * by a user who is not authorized to perform this action.
241    */
242 /*
243   @Test(dependsOnMethods = {"create"})
244   public void createWithoutAuthorization() {
245
246     // Expected status code: 403 Forbidden
247     final int EXPECTED_STATUS_CODE = Response.Status.FORBIDDEN.getStatusCode();
248     
249     // @TODO Currently only a stub.  This test can be implemented
250     // when the service is revised to require authorization. 
251   }
252 */
253
254   /**
255    * Tests creation of a duplicate object of the specified type,
256    * whose unique resource identifier duplicates that of an existing object.
257    */
258 /*
259   @Test(dependsOnMethods = {"create"})
260   public void createDuplicate() {
261
262     // Expected status code: 409 Conflict
263     final int EXPECTED_STATUS_CODE = Response.Status.CONFLICT.getStatusCode();
264     
265     // @TODO This test is currently commented out because our current
266     // services do not appear to permit creation of duplicate records.
267     // Please see below for more details.
268     
269     // Note: there doesn't appear to be a way to create a duplicate
270     // resource (object) by POSTing:
271     //    
272     // 1. We can't POST to a specific resource by ID; that returns a
273     // response with a 405 Method Not Allowed status code.
274     //
275     // 2. If we POST to the container in which new resources are created,
276     // it doesn't appear that we can specify the CSID that the newly-created
277     // resource (object) will receive.
278     //
279     // If the two points above are accurate, this test is thus unneeded, until
280     // and unless, in our service(s), we begin detecting duplicates via a
281     // technique that isn't dependent on CSIDs; for instance, by checking for
282     // duplicate data in other information units (fields) whose values must be unique.
283     // 
284     // One possible example of the above: checking for duplicate Accession numbers
285     // in the "Object entry" information unit in CollectionObject resources.
286   }
287 */
288
289   // ---------------------------------------------------------------
290   // CRUD tests : READ tests
291   // ---------------------------------------------------------------
292
293   // Success outcomes
294   // ----------------
295   
296   /**
297    * Tests reading (i.e. retrieval) of a resource of the specified type.
298    */
299   @Test(dependsOnMethods = {"create"})
300   public void read() {
301
302     // Expected status code: 200 OK
303     final int EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
304
305     // Submit the request to the service and store the response.
306     ClientResponse<CollectionObject> res = 
307       client.getCollectionObject(knownObjectId);
308     int statusCode = res.getStatus();
309       
310     // Check the status code of the response: does it match the expected response(s)?
311     verbose("read: status = " + statusCode);
312     final String REQUEST_TYPE="READ";  // @TODO Consider replacing with enum.
313     Assert.assertTrue(statusCodeWithinExpectedSet(REQUEST_TYPE, statusCode),
314       "Status code '" + statusCode + "' in response is NOT within the expected set.");
315     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
316   }
317
318   // Failure outcomes
319   // ----------------
320
321   /**
322    * Tests reading (i.e. retrieval) of a resource of the specified type by a user who
323    * is not authorized to perform this action.
324    */
325 /*
326   @Test(dependsOnMethods = {"read"})
327   public void readWithoutAuthorization() {
328
329     // Expected status code: 403 Forbidden
330     final int EXPECTED_STATUS_CODE = Response.Status.FORBIDDEN.getStatusCode();
331     
332     // @TODO Currently only a stub.  This test can be implemented
333     // when the service is revised to require authorization. 
334   }
335 */
336
337   /**
338    * Tests reading (i.e. retrieval) of a non-existent object of the specified type,
339    * whose resource identifier does not exist at the specified URL.
340    */
341   @Test(dependsOnMethods = {"read"})
342   public void readNonExistent() {
343
344     // Expected status code: 404 Not Found
345     final int EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode();
346
347     // Submit the request to the service and store the response.
348     ClientResponse<CollectionObject> res = 
349       client.getCollectionObject(NON_EXISTENT_ID);
350     int statusCode = res.getStatus();
351
352     // Check the status code of the response: does it match the expected response(s)?
353     verbose("readNonExistent: status = " + res.getStatus());
354     final String REQUEST_TYPE="READ";  // @TODO Consider replacing with enum.
355     Assert.assertTrue(statusCodeWithinExpectedSet(REQUEST_TYPE, statusCode),
356       "Status code '" + statusCode + "' in response is NOT within the expected set.");
357     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
358   }
359
360
361   // ---------------------------------------------------------------
362   // CRUD tests : READ (list, or multiple) tests
363   // ---------------------------------------------------------------
364
365   // Success outcomes
366   // ----------------
367
368   /**
369    * Tests reading (i.e. retrieval) of a list of multiple objects of the specified type.
370    *
371    * Also expected: The entity body in the response contains
372    * a representation of a list of objects of the specified type.
373    */
374   @Test(dependsOnMethods = {"createMultiple"})
375   public void readList() {
376
377     // Expected status code: 200 OK
378     final int EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
379   
380     // Submit the request to the service and store the response.
381     ClientResponse<CollectionObjectList> res = client.getCollectionObjectList();
382     CollectionObjectList coList = res.getEntity();
383     int statusCode = res.getStatus();
384
385     // Check the status code of the response: does it match the expected response(s)?
386     verbose("readList: status = " + res.getStatus());
387     final String REQUEST_TYPE="READ_MULTIPLE";  // @TODO Consider replacing with enum.
388     Assert.assertTrue(statusCodeWithinExpectedSet(REQUEST_TYPE, statusCode),
389       "Status code '" + statusCode + "' in response is NOT within the expected set.");
390     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
391
392     // Optionally output additional data about list members for debugging.
393     boolean iterateThroughList = false;
394     if (iterateThroughList && logger.isDebugEnabled()) {
395       List<CollectionObjectList.CollectionObjectListItem> coItemList =
396         coList.getCollectionObjectListItem();
397       int i = 0;
398       for(CollectionObjectList.CollectionObjectListItem pli : coItemList){
399         verbose("readList: list-item[" + i + "] csid=" + pli.getCsid());
400         verbose("readList: list-item[" + i + "] objectNumber=" + pli.getObjectNumber());
401         verbose("readList: list-item[" + i + "] URI=" + pli.getUri());
402         i++;
403       }
404     }
405     
406   }
407
408   /**
409    * Tests reading (i.e. retrieval) of a list of multiple objects of the specified type
410    * when the contents of the list are expected to be empty.
411    *
412    * Also expected: The entity body in the response contains
413    * a representation of an empty list of objects of the specified type.
414    */
415 /*
416   @Test(dependsOnMethods = {"readList"})
417   public void readEmptyList() {
418
419     // Expected status code: 200 OK
420     // (NOTE: *not* 204 No Content)
421     final int EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
422
423     // @TODO Currently only a stub.  Consider how to implement this.
424   }
425 */
426   
427   // Failure outcomes
428   // ----------------
429
430   /**
431    * Tests reading (i.e. retrieval) of a list of objects of the specified type
432    * when sending unrecognized query parameters with the request.
433    */
434 /*
435   @Test(dependsOnMethods = {"readList"})
436   public void readListWithBadParams() {
437
438     // Expected status code: 400 Bad Request
439     final int EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
440
441     // @TODO This test is currently commented out, because it returns a
442     // 200 OK status code, rather than the expected status code.
443    
444     // @TODO Another variant of this test should use a URL for the service
445     // root that ends in a trailing slash.
446
447     // Submit the request to the service and store the response.
448     String url = getServiceRootURL() + "?param=nonexistent";
449     GetMethod method = new GetMethod(url);
450     int statusCode = submitRequest(method);
451     
452     // Check the status code of the response: does it match the expected response(s)?
453     verbose("readListWithBadParams: url=" + url + " status=" + statusCode);
454     final String REQUEST_TYPE="READ_MULTIPLE";  // @TODO Consider replacing with enum.
455     Assert.assertTrue(statusCodeWithinExpectedSet(REQUEST_TYPE, statusCode),
456       "Status code '" + statusCode + "' in response is NOT within the expected set.");
457     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
458   }
459 */
460
461   /**
462    * Tests reading (i.e. retrieval) of a list of objects of the specified type by a user who
463    * is not authorized to perform this action.
464    *
465    */
466 /*
467   @Test(dependsOnMethods = {"readList"})
468   public void readListWithoutAuthorization() {
469
470     // Expected status code: 403 Forbidden
471     final int EXPECTED_STATUS_CODE = Response.Status.FORBIDDEN.getStatusCode();
472
473     // @TODO Currently only a stub.  This test can be implemented
474     // when the service is revised to require authorization. 
475   }
476 */
477  
478
479   // ---------------------------------------------------------------
480   // CRUD tests : UPDATE tests
481   // ---------------------------------------------------------------
482
483   // Success outcomes
484   // ----------------
485
486   /**
487    * Tests updating the content of a resource of the specified type.
488    *
489    * Also expected: The entity body in the response contains
490    * a representation of the updated object of the specified type.
491    */
492   @Test(dependsOnMethods = {"create"})
493   public void update() {
494
495     // Expected status code: 200 OK
496     final int EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
497
498     // Retrieve an existing resource that we can update.
499     ClientResponse<CollectionObject> res = 
500       client.getCollectionObject(knownObjectId);
501     verbose("read: status = " + res.getStatus());
502     Assert.assertEquals(res.getStatus(), EXPECTED_STATUS_CODE);
503     CollectionObject collectionObject = res.getEntity();
504     verbose("Got object to update with ID: " + knownObjectId,
505         collectionObject, CollectionObject.class);
506
507     // Update the content of this resource.
508     //collectionObject.setCsid("updated-" + knownObjectId);
509     collectionObject.setObjectNumber("updated-" + collectionObject.getObjectNumber());
510     collectionObject.setObjectName("updated-" + collectionObject.getObjectName());
511
512     // Submit the request to the service and store the response.
513     res = client.updateCollectionObject(knownObjectId, collectionObject);
514     int statusCode = res.getStatus();
515     CollectionObject updatedCollectionObject = res.getEntity();
516
517     // Check the status code of the response: does it match the expected response(s)?
518     verbose("update: status = " + res.getStatus());
519     final String REQUEST_TYPE="UPDATE";  // @TODO Consider replacing with enum.
520     Assert.assertTrue(statusCodeWithinExpectedSet(REQUEST_TYPE, statusCode),
521       "Status code '" + statusCode + "' in response is NOT within the expected set.");
522     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
523     
524     // Check the contents of the response: does it match what was submitted?
525     verbose("update: ", updatedCollectionObject, CollectionObject.class);
526     Assert.assertEquals(updatedCollectionObject.getObjectName(), 
527       collectionObject.getObjectName());
528   }
529
530   /**
531    * Tests updating the content of a resource of the specified type
532    * by sending malformed XML data in the entity body of the request.
533    */
534 /*
535   @Test(dependsOnMethods = {"create", "testSubmitRequest"})
536   public void updateWithMalformedXml() {
537
538     // Expected status code: 400 Bad Request
539     final int EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
540
541     // @TODO This test is currently commented out, because it returns a
542     // 500 Internal Server Error status code, rather than the expected status code.
543
544     // Submit the request to the service and store the response.
545     String url = getResourceURL(knownObjectId);
546     PutMethod method = new PutMethod(url);
547     final String MALFORMED_XML_DATA =
548       "<malformed_xml>wrong schema contents</malformed_xml"; // Note: intentionally missing bracket.
549     StringRequestEntity entity = getXmlEntity(MALFORMED_XML_DATA);
550     int statusCode = submitRequest(method, entity);
551     
552     // Check the status code of the response: does it match the expected response(s)?
553     verbose("updateWithMalformedXml: url=" + url + " status=" + statusCode);
554     final String REQUEST_TYPE="UPDATE";  // @TODO Consider replacing with enum.
555     Assert.assertTrue(statusCodeWithinExpectedSet(REQUEST_TYPE, statusCode),
556       "Status code '" + statusCode + "' in response is NOT within the expected set.");
557     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
558   }
559 */
560
561   /**
562    * Tests updating the content of a resource of the specified type
563    * by sending data in the wrong schema (e.g. in a format that
564    * doesn't match the object's schema) in the entity body of the request.
565    */
566 /*
567   @Test(dependsOnMethods = {"create", "testSubmitRequest", "createWithMalformedXml"})
568   public void updateWithWrongSchema() {
569   
570     // Expected status code: 400 Bad Request
571     final int EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
572     
573     // @TODO This test is currently commented out, because it returns a
574     // 500 Internal Server Error status code, rather than the expected status code.
575
576     // Submit the request to the service and store the response.
577     String url = getResourceURL(knownObjectId);
578     PutMethod method = new PutMethod(url);
579     final String WRONG_SCHEMA_DATA = "<wrong_schema>wrong schema contents</wrong_schema>";
580     StringRequestEntity entity = getXmlEntity(WRONG_SCHEMA_DATA);
581     int statusCode = submitRequest(method, entity);
582     
583     // Check the status code of the response: does it match the expected response(s)?
584     verbose("updateWithWrongSchema: url=" + url + " status=" + statusCode);
585     final String REQUEST_TYPE="UPDATE";  // @TODO Consider replacing with enum.
586     Assert.assertTrue(statusCodeWithinExpectedSet(REQUEST_TYPE, statusCode),
587       "Status code '" + statusCode + "' in response is NOT within the expected set.");
588     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
589   }
590 */
591
592   /**
593    * Tests updating the content of a resource of the specified type,
594    * by a user who is not authorized to perform this action.
595    */
596 /*
597   @Test(dependsOnMethods = {"update"})
598   public void updateWithoutAuthorization() {
599
600     // Expected status code: 403 Forbidden
601     final int EXPECTED_STATUS_CODE = Response.Status.FORBIDDEN.getStatusCode();
602
603     // @TODO Currently only a stub.  This test can be implemented
604     // when the service is revised to require authorization. 
605   }
606 */
607
608   /**
609    * Tests updating the content of a non-existent object of the specified type,
610    * whose resource identifier does not exist.
611    */
612   @Test(dependsOnMethods = {"update"})
613   public void updateNonExistent() {
614
615     // Expected status code: 404 Not Found
616     final int EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode();
617
618     // Submit the request to the service and store the response.
619     // Note: The ID used in this 'create' call may be arbitrary.
620     // The only relevant ID may be the one used in updateCollectionObject(), below.
621     CollectionObject collectionObject = createCollectionObject(NON_EXISTENT_ID);
622     ClientResponse<CollectionObject> res =
623       client.updateCollectionObject(NON_EXISTENT_ID, collectionObject);
624     int statusCode = res.getStatus();
625
626     // Check the status code of the response: does it match the expected response(s)?
627     verbose("updateNonExistent: status = " + res.getStatus());
628     final String REQUEST_TYPE="UPDATE";  // @TODO Consider replacing with enum.
629     Assert.assertTrue(statusCodeWithinExpectedSet(REQUEST_TYPE, statusCode),
630       "Status code '" + statusCode + "' in response is NOT within the expected set.");
631     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
632   }
633
634
635   // ---------------------------------------------------------------
636   // CRUD tests : DELETE tests
637   // ---------------------------------------------------------------
638
639   // Success outcomes
640   // ----------------
641
642   /**
643    * Tests deleting an object of the specified type.
644    *
645    * Expected status code: 200 OK
646    */
647   @Test(dependsOnMethods = 
648     {"create", "read", "testSubmitRequest", "update"})
649   public void delete() {
650
651     // Expected status code: 200 OK
652     final int EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
653
654     // Submit the request to the service and store the response.
655     ClientResponse<Response> res = client.deleteCollectionObject(knownObjectId);
656     int statusCode = res.getStatus();
657
658     // Check the status code of the response: does it match the expected response(s)?
659     verbose("delete: status = " + res.getStatus());
660     final String REQUEST_TYPE="DELETE";  // @TODO Consider replacing with enum.
661     Assert.assertTrue(statusCodeWithinExpectedSet(REQUEST_TYPE, statusCode),
662       "Status code '" + statusCode + "' in response is NOT within the expected set.");
663     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
664   }
665
666   // Failure outcomes
667   // ----------------
668
669   /**
670    * Tests deleting an object of the specified type,
671    * by a user who is not authorized to perform this action.
672    */
673 /*
674   @Test(dependsOnMethods = {"delete"})
675   public void deleteWithoutAuthorization() {
676
677     // Expected status code: 403 Forbidden
678     final int EXPECTED_STATUS_CODE = Response.Status.FORBIDDEN.getStatusCode();
679
680     // @TODO Currently only a stub.  This test can be implemented
681     // when the service is revised to require authorization. 
682   }
683 */
684
685   /**
686    * Tests deleting a non-existent object of the specified type,
687    * whose resource identifier does not exist at the specified URL.
688    */
689   @Test(dependsOnMethods = {"delete"})
690   public void deleteNonExistent() {
691
692     // Expected status code: 404 Not Found
693     final int EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode();
694
695     // Submit the request to the service and store the response.
696     ClientResponse<Response> res =
697       client.deleteCollectionObject(NON_EXISTENT_ID);
698     int statusCode = res.getStatus();
699
700     // Check the status code of the response: does it match the expected response(s)?
701     verbose("deleteNonExistent: status = " + res.getStatus());
702     final String REQUEST_TYPE="DELETE";  // @TODO Consider replacing with enum.
703     Assert.assertTrue(statusCodeWithinExpectedSet(REQUEST_TYPE, statusCode),
704       "Status code '" + statusCode + "' in response is NOT within the expected set.");
705     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE);
706   }
707
708
709   // ---------------------------------------------------------------
710   // Utility tests : tests of code used in tests above
711   // ---------------------------------------------------------------
712
713   /**
714    * Tests the HttpClient-based code used to submit data, in various methods below.
715    */
716   @Test(dependsOnMethods = {"create", "read"})
717   public void testSubmitRequest() {
718
719     // Expected status code: 200 OK
720     final int EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
721
722     // Submit the request to the service and store the response.
723     String url = getResourceURL(knownObjectId);
724     GetMethod method = new GetMethod(url);
725     int statusCode = submitRequest(method);
726     
727     // Check the status code of the response: does it match the expected response(s)?
728     verbose("testSubmitRequest: url=" + url + " status=" + statusCode);
729     final String REQUEST_TYPE="CREATE";  // @TODO Consider replacing with enum.
730     Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE,
731       "expected " + EXPECTED_STATUS_CODE);
732
733   }
734                 
735
736   // ---------------------------------------------------------------
737   // Utility methods used by tests above
738   // ---------------------------------------------------------------
739   
740   // @TODO Add Javadoc comments to all of these methods.
741
742   // -----------------------------
743   // Methods specific to this test
744   // -----------------------------
745
746   private CollectionObject createCollectionObject(String identifier) {
747     CollectionObject collectionObject = createCollectionObject("objectNumber-" + identifier,
748         "objectName-" + identifier);
749     return collectionObject;
750   }
751
752   private CollectionObject createCollectionObject(String objectNumber, String objectName) {
753     CollectionObject collectionObject = new CollectionObject();
754     collectionObject.setObjectNumber(objectNumber);
755     collectionObject.setObjectName(objectName);
756     return collectionObject;
757   }
758   
759   private String getServicePathComponent() {
760     // @TODO Determine if it is possible to obtain this value programmatically.
761     // We set this in an annotation in the CollectionObjectProxy interface, for instance.
762     final String SERVICE_PATH_COMPONENT = "collectionobjects";
763     return SERVICE_PATH_COMPONENT;
764   }
765   
766   // -------------------------------------------------------------
767   // Methods common to all entity service test classes.
768   //
769   // These can be moved out of individual service test classes
770   // into a common class, perhaps at the top-level 'client' module.
771   // -------------------------------------------------------------
772
773 /*
774 Intent of the utility method below, per Sanjay:
775 Utility that asserts various HTTP status codes received for a given type of a request.
776 This should be used from any test we write for a service. For example, this utility
777 could take the following two params.
778 import javax.ws.rs.core.Response;
779 final static public boolean checkStatus(String method, Response.Status status);
780 Perhaps you could find about method used from org.jboss.resteasy.client.ClientResponse metadata.
781
782 See comments in the 'create' test, above, regarding finding the method used in the request.
783 */
784
785   private boolean statusCodeWithinExpectedSet(String requestType, int statusCode) {
786   
787     // @TODO Consider implementing this via enums for request types,
788     // rather than Strings.  This might also allow us to define their
789     // values in the enum definition class.
790     
791     if (requestType.equalsIgnoreCase("CREATE")) {
792       int[] validStatusCodes = {
793         Response.Status.CREATED.getStatusCode(),
794         Response.Status.BAD_REQUEST.getStatusCode(),
795         Response.Status.FORBIDDEN.getStatusCode(),
796         Response.Status.CONFLICT.getStatusCode(),
797         Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() };
798       if (Arrays.binarySearch(validStatusCodes, statusCode) >= 0) {
799         verbose("status code is IN expected set");
800         return true;
801       } else {
802         verbose("status code is NOT in expected set");
803         return false;
804       }
805     } else if (requestType.equalsIgnoreCase("READ")) {
806       int[] validStatusCodes = {
807         Response.Status.OK.getStatusCode(),
808         Response.Status.FORBIDDEN.getStatusCode(),
809         Response.Status.NOT_FOUND.getStatusCode(),
810         Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() };
811       if (Arrays.binarySearch(validStatusCodes, statusCode) >= 0) {
812         verbose("status code is IN expected set");
813         return true;
814       } else {
815         verbose("status code is NOT in expected set");
816         return false;
817       }
818     } else if (requestType.equalsIgnoreCase("READ_MULTIPLE")) {
819       int[] validStatusCodes = {
820         Response.Status.OK.getStatusCode(),
821         Response.Status.BAD_REQUEST.getStatusCode(),
822         Response.Status.FORBIDDEN.getStatusCode(),
823         Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() };
824       if (Arrays.binarySearch(validStatusCodes, statusCode) >= 0) {
825         verbose("status code is IN expected set");
826         return true;
827       } else {
828         verbose("status code is NOT in expected set");
829         return false;
830       }
831     } else if (requestType.equalsIgnoreCase("UPDATE")) {
832       int[] validStatusCodes = {
833         Response.Status.OK.getStatusCode(),
834         Response.Status.BAD_REQUEST.getStatusCode(),
835         Response.Status.FORBIDDEN.getStatusCode(),
836         Response.Status.NOT_FOUND.getStatusCode(),
837         Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() };
838       if (Arrays.binarySearch(validStatusCodes, statusCode) >= 0) {
839         verbose("status code is IN expected set");
840         return true;
841       } else {
842         verbose("status code is NOT in expected set");
843         return false;
844       }
845     } else if (requestType.equalsIgnoreCase("DELETE")) {
846       int[] validStatusCodes = {
847         Response.Status.OK.getStatusCode(),
848         Response.Status.FORBIDDEN.getStatusCode(),
849         Response.Status.NOT_FOUND.getStatusCode(),
850         Response.Status.INTERNAL_SERVER_ERROR.getStatusCode() };
851       if (Arrays.binarySearch(validStatusCodes, statusCode) >= 0) {
852         verbose("status code is IN expected set");
853         return true;
854       } else {
855         verbose("status code is NOT in expected set");
856         return false;
857       }
858     } else {
859       logger.error("Request type '" + requestType + " must match a recognized type.");
860       return false;
861     }
862   }
863   
864   private String getServiceRootURL() {
865     return serviceClient.getBaseURL() + getServicePathComponent();
866   }
867
868   private String getResourceURL(String resourceIdentifier) {
869     return getServiceRootURL() + "/" + resourceIdentifier;
870   }
871
872   private int submitRequest(HttpMethod method) {
873    int statusCode = 0;
874     try {
875       statusCode = httpClient.executeMethod(method);
876     } catch(HttpException e) {
877       logger.error("Fatal protocol violation: ", e);
878     } catch(IOException e) {
879       logger.error("Fatal transport error: ", e);
880     } catch(Exception e) {
881       logger.error("Unknown exception: ", e);
882     } finally {
883       // Release the connection.
884       method.releaseConnection();
885     }
886     return statusCode;
887   }
888   
889   private int submitRequest(EntityEnclosingMethod method, RequestEntity entity) {
890     int statusCode = 0;
891     try {
892       method.setRequestEntity(entity);
893       statusCode = httpClient.executeMethod(method);
894     } catch(HttpException e) {
895       logger.error("Fatal protocol violation: ", e);
896     } catch(IOException e) {
897       logger.error("Fatal transport error: ", e);
898     } catch(Exception e) {
899       logger.error("Unknown exception: ", e);
900     } finally {
901       // Release the connection.
902       method.releaseConnection();
903     }
904     return statusCode;
905   }
906   
907   private StringRequestEntity getXmlEntity(String contents) {
908     if (contents == null) {
909       contents = "";
910     }
911     StringRequestEntity entity = null;
912     final String XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
913     final String XML_CONTENT_TYPE=MediaType.APPLICATION_XML;
914     final String UTF8_CHARSET_NAME = "UTF-8";
915     try {
916       entity =
917         new StringRequestEntity(XML_DECLARATION + contents, XML_CONTENT_TYPE, UTF8_CHARSET_NAME);
918     } catch (UnsupportedEncodingException e) {
919       logger.error("Unsupported character encoding error: ", e);
920     }
921     return entity;
922   }
923
924   private String extractId(ClientResponse<Response> res) {
925     MultivaluedMap mvm = res.getMetadata();
926     String uri = (String) ((ArrayList) mvm.get("Location")).get(0);
927     verbose("extractId:uri=" + uri);
928     String[] segments = uri.split("/");
929     String id = segments[segments.length - 1];
930     verbose("id=" + id);
931     return id;
932   }
933
934   private void verbose(String msg) {
935     if (logger.isDebugEnabled()) {
936       logger.debug(msg);
937     }
938   }
939
940   private void verbose(String msg, Object o, Class clazz) {
941     try{
942       verbose(msg);
943       JAXBContext jc = JAXBContext.newInstance(clazz);
944       Marshaller m = jc.createMarshaller();
945       m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
946           Boolean.TRUE);
947       m.marshal(o, System.out);
948     }catch(Exception e){
949       e.printStackTrace();
950     }
951   }
952
953   private void verboseMap(MultivaluedMap map) {
954     for(Object entry : map.entrySet()){
955       MultivaluedMap.Entry mentry = (MultivaluedMap.Entry) entry;
956       verbose("  name=" + mentry.getKey() + " value=" + mentry.getValue());
957     }
958   }
959
960   private String createIdentifier() {
961     long identifier = System.currentTimeMillis();
962     return Long.toString(identifier);
963   }
964
965   private String createNonExistentIdentifier() {
966     return Long.toString(Long.MAX_VALUE);
967   }
968   
969 }