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:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright © 2009 Regents of the University of California
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 package org.collectionspace.services.client.test;
26 import java.io.IOException;
27 import java.io.UnsupportedEncodingException;
28 import java.util.ArrayList;
29 import javax.xml.bind.JAXBContext;
30 import javax.xml.bind.Marshaller;
31 import javax.ws.rs.core.MediaType;
32 import javax.ws.rs.core.MultivaluedMap;
33 import javax.ws.rs.core.Response;
35 import org.apache.commons.httpclient.Header;
36 import org.apache.commons.httpclient.HttpClient;
37 import org.apache.commons.httpclient.HttpException;
38 import org.apache.commons.httpclient.HttpMethod;
39 import org.apache.commons.httpclient.HttpStatus;
40 import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
41 import org.apache.commons.httpclient.methods.GetMethod;
42 import org.apache.commons.httpclient.methods.PostMethod;
43 import org.apache.commons.httpclient.methods.PutMethod;
44 import org.apache.commons.httpclient.methods.RequestEntity;
45 import org.apache.commons.httpclient.methods.StringRequestEntity;
47 import org.collectionspace.services.client.TestServiceClient;
48 import org.collectionspace.services.client.test.ServiceRequestType;
50 import org.jboss.resteasy.client.ClientResponse;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
56 * AbstractServiceTest, abstract base class for the client tests to be performed
57 * to test an entity or relation service.
59 * For Javadoc descriptions of this class's methods, see the ServiceTest interface.
61 public abstract class AbstractServiceTest implements ServiceTest {
63 final Logger logger = LoggerFactory.getLogger(AbstractServiceTest.class);
65 private final TestServiceClient serviceClient = new TestServiceClient();
66 protected HttpClient httpClient = new HttpClient();
68 // The status code expected to be returned by a test method (where relevant).
69 int EXPECTED_STATUS_CODE = 0;
71 // The generic type of service request being tested (e.g. CREATE, UPDATE, DELETE).
73 // This makes it possible to check behavior specific to that type of request,
74 // such as the set of valid status codes that may be returned.
75 ServiceRequestType REQUEST_TYPE = ServiceRequestType.NON_EXISTENT;
78 // ---------------------------------------------------------------
79 // CRUD tests : CREATE tests
80 // ---------------------------------------------------------------
85 public abstract void create();
87 protected void setupCreate() {
89 // Expected status code: 201 Created
90 EXPECTED_STATUS_CODE = Response.Status.CREATED.getStatusCode();
91 // Type of service request being tested
92 REQUEST_TYPE = ServiceRequestType.CREATE;
96 public abstract void createMultiple();
98 // No setup required for createMultiple()
103 public void createNull() {
106 // No setup required for createNull()
109 public abstract void createWithMalformedXml();
111 protected void setupCreateWithMalformedXml() {
113 // Expected status code: 400 Bad Request
114 EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
115 REQUEST_TYPE = ServiceRequestType.CREATE;
119 public abstract void createWithWrongXmlSchema();
121 protected void setupCreateWithWrongXmlSchema() {
123 // Expected status code: 400 Bad Request
124 EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
125 REQUEST_TYPE = ServiceRequestType.CREATE;
129 // ---------------------------------------------------------------
130 // CRUD tests : READ tests
131 // ---------------------------------------------------------------
136 public abstract void read();
138 protected void setupRead() {
140 // Expected status code: 200 OK
141 EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
142 REQUEST_TYPE = ServiceRequestType.READ;
148 public abstract void readNonExistent();
150 protected void setupReadNonExistent() {
152 // Expected status code: 404 Not Found
153 EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode();
154 REQUEST_TYPE = ServiceRequestType.READ;
158 // ---------------------------------------------------------------
159 // CRUD tests : READ (list, or multiple) tests
160 // ---------------------------------------------------------------
165 public abstract void readList();
167 protected void setupReadList() {
169 // Expected status code: 200 OK
170 EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
171 REQUEST_TYPE = ServiceRequestType.READ_MULTIPLE;
176 // None tested at present.
179 // ---------------------------------------------------------------
180 // CRUD tests : UPDATE tests
181 // ---------------------------------------------------------------
187 public abstract void update();
189 protected void setupUpdate() {
191 // Expected status code: 200 OK
192 EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
193 REQUEST_TYPE = ServiceRequestType.UPDATE;
199 public abstract void updateWithMalformedXml();
201 protected void setupUpdateWithMalformedXml() {
203 // Expected status code: 400 Bad Request
204 EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
205 REQUEST_TYPE = ServiceRequestType.UPDATE;
209 public abstract void updateWithWrongXmlSchema();
211 protected void setupUpdateWithWrongXmlSchema() {
213 // Expected status code: 400 Bad Request
214 EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode();
215 REQUEST_TYPE = ServiceRequestType.UPDATE;
219 public abstract void updateNonExistent();
221 protected void setupUpdateNonExistent() {
223 // Expected status code: 404 Not Found
224 EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode();
225 REQUEST_TYPE = ServiceRequestType.UPDATE;
229 // ---------------------------------------------------------------
230 // CRUD tests : DELETE tests
231 // ---------------------------------------------------------------
236 public abstract void delete();
238 protected void setupDelete() {
240 // Expected status code: 200 OK
241 EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode();
242 REQUEST_TYPE = ServiceRequestType.DELETE;
248 public abstract void deleteNonExistent();
250 protected void setupDeleteNonExistent() {
252 // Expected status code: 404 Not Found
253 EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode();
254 REQUEST_TYPE = ServiceRequestType.DELETE;
258 // ---------------------------------------------------------------
259 // Abstract utility methods
261 // Must be implemented by classes that extend
262 // this abstract base class.
263 // ---------------------------------------------------------------
266 * Returns the URL path component of the service.
268 * This component will follow directly after the
271 protected abstract String getServicePathComponent();
274 // ---------------------------------------------------------------
276 // ---------------------------------------------------------------
279 * Reinitializes setup values to guard against unintended reuse
282 protected void clearSetup() {
283 EXPECTED_STATUS_CODE = 0;
284 REQUEST_TYPE = ServiceRequestType.NON_EXISTENT;
287 // @TODO Add Javadoc comments to all methods requiring them, below.
289 protected String invalidStatusCodeMessage(ServiceRequestType requestType, int statusCode) {
291 "Status code '" + statusCode + "' in response is NOT within the expected set: " +
292 requestType.validStatusCodesAsString();
295 protected String getServiceRootURL() {
296 return serviceClient.getBaseURL() + getServicePathComponent();
299 protected String getResourceURL(String resourceIdentifier) {
300 return getServiceRootURL() + "/" + resourceIdentifier;
303 protected int submitRequest(HttpMethod method) {
306 statusCode = httpClient.executeMethod(method);
307 } catch(HttpException e) {
308 logger.error("Fatal protocol violation: ", e);
309 } catch(IOException e) {
310 logger.error("Fatal transport error: ", e);
311 } catch(Exception e) {
312 logger.error("Unknown exception: ", e);
314 // Release the connection.
315 method.releaseConnection();
320 protected int submitRequest(EntityEnclosingMethod method, RequestEntity entity) {
323 method.setRequestEntity(entity);
324 statusCode = httpClient.executeMethod(method);
325 } catch(HttpException e) {
326 logger.error("Fatal protocol violation: ", e);
327 } catch(IOException e) {
328 logger.error("Fatal transport error: ", e);
329 } catch(Exception e) {
330 logger.error("Unknown exception: ", e);
332 // Release the connection.
333 method.releaseConnection();
338 protected StringRequestEntity getXmlEntity(String contents) {
339 if (contents == null) {
342 StringRequestEntity entity = null;
343 final String XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
344 final String XML_CONTENT_TYPE=MediaType.APPLICATION_XML;
345 final String UTF8_CHARSET_NAME = "UTF-8";
348 new StringRequestEntity(XML_DECLARATION + contents, XML_CONTENT_TYPE, UTF8_CHARSET_NAME);
349 } catch (UnsupportedEncodingException e) {
350 logger.error("Unsupported character encoding error: ", e);
355 protected String extractId(ClientResponse<Response> res) {
356 MultivaluedMap mvm = res.getMetadata();
357 String uri = (String) ((ArrayList) mvm.get("Location")).get(0);
358 verbose("extractId:uri=" + uri);
359 String[] segments = uri.split("/");
360 String id = segments[segments.length - 1];
365 protected void verbose(String msg) {
366 if (logger.isDebugEnabled()) {
371 protected void verbose(String msg, Object o, Class clazz) {
374 JAXBContext jc = JAXBContext.newInstance(clazz);
375 Marshaller m = jc.createMarshaller();
376 m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
378 m.marshal(o, System.out);
384 protected void verboseMap(MultivaluedMap map) {
385 for(Object entry : map.entrySet()){
386 MultivaluedMap.Entry mentry = (MultivaluedMap.Entry) entry;
387 verbose(" name=" + mentry.getKey() + " value=" + mentry.getValue());
391 protected String createIdentifier() {
392 long identifier = System.currentTimeMillis();
393 return Long.toString(identifier);
396 protected String createNonExistentIdentifier() {
397 return Long.toString(Long.MAX_VALUE);