/*
* ModeShape (http://www.modeshape.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.modeshape.web.jcr.rest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.ws.rs.core.MediaType;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.modeshape.common.text.UrlEncoder;
import org.modeshape.common.util.IoUtil;
import org.modeshape.common.util.StringUtil;
import org.modeshape.jcr.api.JcrConstants;
import org.modeshape.web.jcr.rest.handler.AbstractHandler;
import junit.framework.AssertionFailedError;
/**
* Base class used for testing the interaction with {@link org.modeshape.web.jcr.rest.ModeShapeRestService}
*
* @author Horia Chiorean (hchiorea@redhat.com)
*/
@SuppressWarnings( "deprecation" )
public abstract class AbstractRestTest {
private static final List<String> JSON_PROPERTIES_IGNORE_EQUALS = Arrays.asList("jcr:uuid", "jcr:score",
AbstractHandler.NODE_ID_CUSTOM_PROPERTY);
private static final UrlEncoder URL_ENCODER = new UrlEncoder().setSlashEncoded(false);
protected CloseableHttpClient httpClient;
protected HttpClientContext httpContext;
protected abstract String getServerContext();
protected abstract HttpHost getHost();
@Before
public void beforeEach() throws Exception {
httpClient = HttpClientBuilder.create().build();
httpContext = HttpClientContext.create();
}
@After
public void afterEach() throws Exception {
httpClient.getConnectionManager().shutdown();
}
protected void setAuthCredentials( String authUsername,
String authPassword ) {
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(new AuthScope(getHost()),
new UsernamePasswordCredentials(authUsername, authPassword));
httpContext.setCredentialsProvider(credentialsProvider);
}
protected Response doGet() throws Exception {
return new Response(newDefaultRequest(HttpGet.class, null, null));
}
protected Response doGet( String url ) throws Exception {
return new Response(newDefaultRequest(HttpGet.class, null, null, url));
}
protected Response doPost( String payloadFile,
String url ) throws Exception {
InputStream is = null;
if (payloadFile != null) {
is = fileStream(payloadFile);
assertNotNull(is);
}
return postStream(is, url, MediaType.APPLICATION_JSON);
}
protected Response doPost( InputStream is,
String url ) throws Exception {
return postStream(is, url, null);
}
protected Response doPost( JSONObject object,
String url ) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(byteArrayOutputStream);
object.write(writer);
writer.flush();
writer.close();
HttpPost post = newDefaultRequest(HttpPost.class, new ByteArrayInputStream(byteArrayOutputStream.toByteArray()),
MediaType.APPLICATION_JSON, url);
return new Response(post);
}
protected InputStream fileStream( String file ) {
return getClass().getClassLoader().getResourceAsStream(file);
}
protected JSONObject readJson( String file ) throws Exception {
String fileContent = IoUtil.read(fileStream(file));
return new JSONObject(fileContent);
}
protected Response xpathQuery( String query,
String url ) throws Exception {
return postStream(new ByteArrayInputStream(query.getBytes()), url, "application/jcr+xpath");
}
protected Response jcrSQL2Query( String query,
String url ) throws Exception {
return postStream(new ByteArrayInputStream(query.getBytes()), url, "application/jcr+sql2");
}
protected Response jcrSQL2QueryPlan( String query,
String url ) throws Exception {
return postStream(new ByteArrayInputStream(query.getBytes()), url, "application/jcr+sql2");
}
protected Response jcrSQL2QueryPlanAsText( String query,
String url ) throws Exception {
return postStreamForTextResponse(new ByteArrayInputStream(query.getBytes()), url, "application/jcr+sql2");
}
protected Response postStream( InputStream is,
String url,
String contentType ) throws Exception {
HttpPost post = newDefaultRequest(HttpPost.class, is, contentType, url);
return new Response(post);
}
protected Response postStreamForTextResponse( InputStream is,
String url,
String contentType ) throws Exception {
HttpPost post = newRequest(HttpPost.class, is, contentType, MediaType.TEXT_PLAIN, url);
return new Response(post);
}
protected Response doPostMultiPart( String filePath,
String elementName,
String url,
String contentType ) {
try {
if (StringUtil.isBlank(contentType)) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
url = URL_ENCODER.encode(RestHelper.urlFrom(getServerContext(), url));
HttpPost post = new HttpPost(url);
post.setHeader("Accept", MediaType.APPLICATION_JSON);
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IoUtil.write(fileStream(filePath), baos);
entityBuilder.addPart(elementName, new ByteArrayBody(baos.toByteArray(), "test_file"));
post.setEntity(entityBuilder.build());
return new Response(post);
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
return null;
}
}
protected Response doPut( String payloadFile,
String url ) throws Exception {
HttpPut put = newDefaultRequest(HttpPut.class, fileStream(payloadFile), MediaType.APPLICATION_JSON, url);
return new Response(put);
}
protected Response doPut( InputStream is,
String url ) throws Exception {
HttpPut put = newDefaultRequest(HttpPut.class, is, MediaType.APPLICATION_JSON, url);
return new Response(put);
}
protected Response doPut( JSONObject request,
String url ) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(byteArrayOutputStream);
request.write(writer);
writer.flush();
writer.close();
HttpPut put = newDefaultRequest(HttpPut.class, new ByteArrayInputStream(byteArrayOutputStream.toByteArray()),
MediaType.APPLICATION_JSON, url);
return new Response(put);
}
protected Response doDelete( String url ) throws Exception {
HttpDeleteWithBody delete = newDefaultRequest(HttpDeleteWithBody.class, null, null, url);
return new Response(delete);
}
protected Response doDelete( String payloadFile,
String url ) throws Exception {
InputStream is = null;
if (payloadFile != null) {
is = fileStream(payloadFile);
}
HttpDeleteWithBody delete = newDefaultRequest(HttpDeleteWithBody.class, is, MediaType.APPLICATION_JSON, url);
return new Response(delete);
}
private <T extends HttpRequestBase> T newDefaultRequest( Class<T> clazz,
InputStream inputStream,
String contentType,
String... pathSegments ) {
return newRequest(clazz, inputStream, contentType, MediaType.APPLICATION_JSON, pathSegments);
}
private <T extends HttpRequestBase> T newRequest( Class<T> clazz,
InputStream inputStream,
String contentType,
String accepts,
String... pathSegments ) {
String url = RestHelper.urlFrom(getServerContext(), pathSegments);
try {
URIBuilder uriBuilder;
try {
uriBuilder = new URIBuilder(url);
} catch (URISyntaxException e) {
uriBuilder = new URIBuilder(URL_ENCODER.encode(url));
}
T result = clazz.getConstructor(URI.class).newInstance(uriBuilder.build());
result.setHeader("Accept", accepts);
result.setHeader("Content-Type", contentType);
if (inputStream != null) {
assertTrue("Invalid request clazz (requires an entity)", result instanceof HttpEntityEnclosingRequestBase);
InputStreamEntity inputStreamEntity = new InputStreamEntity(inputStream, inputStream.available());
((HttpEntityEnclosingRequestBase)result).setEntity(new BufferedHttpEntity(inputStreamEntity));
}
return result;
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
return null;
}
}
protected void assertJSON( Object expected,
Object actual ) throws JSONException {
if (expected instanceof JSONObject) {
assert (actual instanceof JSONObject);
JSONObject expectedJSON = (JSONObject)expected;
JSONObject actualJSON = (JSONObject)actual;
for (Iterator<?> keyIterator = expectedJSON.keys(); keyIterator.hasNext();) {
String key = keyIterator.next().toString();
assertTrue("Actual JSON object does not contain key '" + key + "': " + actualJSON, actualJSON.has(key));
Object expectedValueAtKey = expectedJSON.get(key);
Object actualValueAtKey = actualJSON.get(key);
if (shouldNotAssertEquality(key)) {
assertNotNull(actualValueAtKey);
} else {
assertJSON(expectedValueAtKey, actualValueAtKey);
}
}
} else if (expected instanceof JSONArray) {
assert (actual instanceof JSONArray);
JSONArray expectedArray = (JSONArray)expected;
JSONArray actualArray = (JSONArray)actual;
Assert.assertEquals("Arrays don't match. \nExpected:" + expectedArray.toString() + "\nActual :" + actualArray,
expectedArray.length(), actualArray.length());
for (int i = 0; i < expectedArray.length(); i++) {
assertJSON(expectedArray.get(i), actualArray.get(i));
}
} else {
assertEquals("Values don't match", expected.toString(), actual.toString());
}
}
private boolean shouldNotAssertEquality( String propertyName ) {
for (String propertyToIgnore : JSON_PROPERTIES_IGNORE_EQUALS) {
if (propertyName.toLowerCase().contains(propertyToIgnore.toLowerCase())) {
return true;
}
}
return false;
}
protected static class HttpDeleteWithBody extends HttpPost {
public HttpDeleteWithBody( URI uri ) {
super(uri);
}
@Override
public String getMethod() {
return "DELETE";
}
}
protected class Response {
private final HttpResponse response;
private byte[] content;
private String contentString;
private JSONObject contentJSON;
protected Response( HttpRequestBase request ) {
try {
response = httpClient.execute(getHost(), request, httpContext);
HttpEntity entity = response.getEntity();
if (entity != null) {
ByteArrayOutputStream baous = new ByteArrayOutputStream();
entity.writeTo(baous);
EntityUtils.consumeQuietly(entity);
content = baous.toByteArray();
} else {
content = new byte[0];
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
request.releaseConnection();
}
}
private Response hasCode( int responseCode ) throws Exception {
assertEquals(responseCode, response.getStatusLine().getStatusCode());
return this;
}
private Response hasHeader( String name,
String value ) {
assertEquals(value, response.getFirstHeader(name).getValue());
return this;
}
protected String getContentTypeHeader() {
return response.getFirstHeader("Content-Type").getValue();
}
protected Response hasMimeType( String mimeType ) {
hasHeader("Content-Type", mimeType);
return this;
}
protected Response hasContentDisposition( String contentDisposition ) {
hasHeader("Content-Disposition", contentDisposition);
return this;
}
protected Response isOk() throws Exception {
return hasCode(HttpURLConnection.HTTP_OK);
}
protected Response isCreated() throws Exception {
return hasCode(HttpURLConnection.HTTP_CREATED);
}
protected Response isDeleted() throws Exception {
return hasCode(HttpURLConnection.HTTP_NO_CONTENT);
}
protected Response isNotFound() throws Exception {
return hasCode(HttpURLConnection.HTTP_NOT_FOUND);
}
protected Response isUnauthorized() throws Exception {
return hasCode(HttpURLConnection.HTTP_UNAUTHORIZED);
}
protected Response isBadRequest() throws Exception {
return hasCode(HttpURLConnection.HTTP_BAD_REQUEST);
}
protected Response isJSON() throws Exception {
assertTrue(getContentTypeHeader().toLowerCase().contains(MediaType.APPLICATION_JSON.toLowerCase()));
return this;
}
protected Response isJSONObjectLikeFile( String pathToExpectedJSON ) throws Exception {
isJSON();
String expectedJSONString = IoUtil.read(fileStream(pathToExpectedJSON));
JSONObject expectedObject = new JSONObject(expectedJSONString);
JSONObject responseObject = new JSONObject(contentAsString());
try {
assertJSON(expectedObject, responseObject);
} catch (AssertionFailedError e) {
System.out.println("expected: " + expectedObject);
System.out.println("response: " + responseObject);
throw e;
}
return this;
}
protected Response isJSONObjectLike( Response otherResponse ) throws Exception {
isJSON();
JSONObject expectedObject = otherResponse.json();
JSONObject responseObject = new JSONObject(contentAsString());
assertJSON(expectedObject, responseObject);
return this;
}
protected Response isJSONArrayLikeFile( String pathToExpectedJSON ) throws Exception {
isJSON();
String expectedJSONString = IoUtil.read(fileStream(pathToExpectedJSON));
JSONArray expectedArray = new JSONArray(expectedJSONString);
JSONArray responseObject = new JSONArray(contentAsString());
assertJSON(expectedArray, responseObject);
return this;
}
protected String hasNodeIdentifier() throws Exception {
JSONObject responseObject = new JSONObject(contentAsString());
String id = responseObject.getString("id");
assertNotNull(id);
assertTrue(id.trim().length() != 0);
return id;
}
protected JSONObject json() throws Exception {
if (contentJSON == null) {
contentJSON = new JSONObject(contentAsString());
}
return contentJSON;
}
protected String contentAsString() {
if (contentString == null) {
contentString = new String(content);
}
return contentString;
}
protected Response hasPrimaryType(String primaryType) throws Exception {
JSONObject json = json();
assertTrue("jcr:primary type property not found", json.has(JcrConstants.JCR_PRIMARY_TYPE));
assertEquals(primaryType, json().getString(JcrConstants.JCR_PRIMARY_TYPE));
return this;
}
protected byte[] contentAsBytes() {
return content;
}
@Override
public String toString() {
return contentAsString();
}
}
}