/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.olingo.odata2.fit.ref.contentnegotiation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
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.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.olingo.odata2.api.ODataService;
import org.apache.olingo.odata2.api.commons.HttpContentType;
import org.apache.olingo.odata2.api.commons.HttpHeaders;
import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
import org.apache.olingo.odata2.api.edm.provider.EdmProvider;
import org.apache.olingo.odata2.api.exception.ODataException;
import org.apache.olingo.odata2.api.processor.ODataSingleProcessor;
import org.apache.olingo.odata2.core.commons.ContentType;
import org.apache.olingo.odata2.core.processor.ODataSingleProcessorService;
import org.apache.olingo.odata2.core.uri.UriType;
import org.apache.olingo.odata2.ref.edm.ScenarioEdmProvider;
import org.apache.olingo.odata2.ref.model.DataContainer;
import org.apache.olingo.odata2.ref.processor.ListsProcessor;
import org.apache.olingo.odata2.ref.processor.ScenarioDataSource;
import org.apache.olingo.odata2.testutil.fit.AbstractFitTest;
import org.apache.olingo.odata2.testutil.helper.StringHelper;
import org.apache.olingo.odata2.testutil.server.ServletType;
import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*/
public abstract class AbstractContentNegotiationTest extends AbstractFitTest {
public AbstractContentNegotiationTest(final ServletType servletType) {
super(servletType);
}
private static final Logger LOG = LoggerFactory.getLogger(AbstractContentNegotiationTest.class);
protected final static List<String> ACCEPT_HEADER_VALUES = Arrays.asList(
"", // for requests with no 'Accept' header set
HttpContentType.TEXT_PLAIN,
HttpContentType.TEXT_PLAIN_UTF8,
HttpContentType.APPLICATION_JSON,
HttpContentType.APPLICATION_JSON_UTF8,
HttpContentType.APPLICATION_XML,
HttpContentType.APPLICATION_XML_UTF8,
HttpContentType.APPLICATION_ATOM_XML,
HttpContentType.APPLICATION_ATOM_XML_UTF8,
HttpContentType.APPLICATION_ATOM_SVC,
HttpContentType.APPLICATION_ATOM_SVC_UTF8
);
protected final static List<String> QUERY_OPTIONS = Arrays.asList(
"",
"?$format=xml",
"?$format=atom",
"?$format=json"
);
public static final List<String> CONTENT_TYPE_VALUES = Arrays.asList(
HttpContentType.TEXT_PLAIN,
HttpContentType.TEXT_PLAIN_UTF8,
HttpContentType.APPLICATION_JSON,
HttpContentType.APPLICATION_JSON_UTF8,
HttpContentType.APPLICATION_XML,
HttpContentType.APPLICATION_XML_UTF8,
HttpContentType.APPLICATION_ATOM_XML,
HttpContentType.APPLICATION_ATOM_XML_UTF8
);
@Override
protected ODataService createService() throws ODataException {
DataContainer dataContainer = new DataContainer();
dataContainer.init();
final ODataSingleProcessor processor = new ListsProcessor(new ScenarioDataSource(dataContainer));
final EdmProvider provider = new ScenarioEdmProvider();
return new ODataSingleProcessorService(provider, processor) {};
}
protected static class FitTestSetBuilder {
private final FitTestSet testSet;
public FitTestSetBuilder(final FitTestSet testSet) {
this.testSet = testSet;
}
public FitTestSetBuilder queryOptions(final List<String> queryOptions) {
testSet.queryOptions = queryOptions;
return this;
}
public FitTestSetBuilder acceptHeader(final List<String> acceptHeader) {
testSet.acceptHeader = acceptHeader;
return this;
}
public FitTestSetBuilder expectedStatusCode(final HttpStatusCodes expectedStatusCode) {
testSet.expectedStatusCode = expectedStatusCode;
return this;
}
public FitTestSetBuilder expectedContentType(final String expectedContentType) {
testSet.expectedContentType = expectedContentType;
return this;
}
public FitTestSet init() {
return init(true);
}
public FitTestSet init(final boolean populate) {
if (populate) {
testSet.populate();
}
return testSet;
}
public FitTestSetBuilder content(final String content) {
testSet.content = content;
return this;
}
public FitTestSetBuilder httpMethod(final String httpMethod) {
testSet.httpMethod = httpMethod;
return this;
}
public FitTestSetBuilder requestContentTypes(final List<String> contentTypes) {
testSet.requestContentTypes = contentTypes;
return this;
}
}
/**
* A set of {@link FitTest} which can be executed against a service endpoint.
*
*/
protected static class FitTestSet {
private static final int DEFAULT_WAIT_BETWEEN_TESTCALLS_IN_MS = 25;
private final Set<FitTest> testParameters = new HashSet<AbstractContentNegotiationTest.FitTest>();
private final UriType uriType;
private final String path;
private List<String> queryOptions = Arrays.asList("");
private List<String> acceptHeader = Arrays.asList("");
private List<String> requestContentTypes = Arrays.asList("");
private HttpStatusCodes expectedStatusCode = HttpStatusCodes.OK;
private String expectedContentType = null;
private String httpMethod = "GET";
private String content = null;
private FitTestSet(final UriType uriType, final String path) {
super();
this.uriType = uriType;
this.path = path;
}
public static FitTestSetBuilder create(final UriType uriType, final String path) {
return create(uriType, path, true, true, false);
}
public static FitTestSetBuilder create(final UriType uriType, final String path,
final boolean defaultQueryOptions, final boolean defaultAcceptHeaders,
final boolean defaultRequestContentTypeHeaders) {
FitTestSetBuilder builder = new FitTestSetBuilder(new FitTestSet(uriType, path));
if (defaultQueryOptions) {
builder.queryOptions(QUERY_OPTIONS);
}
if (defaultAcceptHeaders) {
builder.acceptHeader(ACCEPT_HEADER_VALUES);
}
if (defaultRequestContentTypeHeaders) {
builder.requestContentTypes(CONTENT_TYPE_VALUES);
}
return builder;
}
public void populate() {
testParameters.addAll(FitTest.create(this));
}
public void setTestParam(final FitTest fitTest) {
testParameters.remove(fitTest);
testParameters.add(fitTest);
}
public void modifyRequestContentTypes(final List<String> requestContentTypes,
final HttpStatusCodes expectedStatusCode, final String expectedContentType) {
FitTestSet fts = new FitTestSetBuilder(this)
.requestContentTypes(requestContentTypes)
.expectedStatusCode(expectedStatusCode)
.expectedContentType(expectedContentType).init(false);
replaceTestParameters(FitTest.create(fts));
}
public void setTestParam(final List<String> acceptHeader, final HttpStatusCodes expectedStatusCode,
final String expectedContentType) {
setTestParam(queryOptions, acceptHeader, expectedStatusCode, expectedContentType);
}
public void setTestParam(final List<String> queryOptions, final List<String> acceptHeader,
final HttpStatusCodes expectedStatusCode, final String expectedContentType) {
List<FitTest> tp = FitTest.create(this, queryOptions, acceptHeader, expectedStatusCode, expectedContentType);
replaceTestParameters(tp);
}
private void replaceTestParameters(final List<FitTest> tp) {
testParameters.removeAll(tp);
testParameters.addAll(tp);
}
/**
* Execute all {@link FitTest}s with a default wait time between the calls (of
* {@value #DEFAULT_WAIT_BETWEEN_TESTCALLS_IN_MS} ms).
*
* For more information see @see #execute(URI, long)
*
* @param serviceEndpoint
* @throws Exception
*/
public void execute(final URI serviceEndpoint) throws Exception {
execute(serviceEndpoint, DEFAULT_WAIT_BETWEEN_TESTCALLS_IN_MS);
}
public void execute(final URI serviceEndpoint, final long sleepTimeInMs) throws Exception {
Map<FitTest, AssertionError> test2Failure = new HashMap<AbstractContentNegotiationTest.FitTest, AssertionError>();
List<FitTest> successTests = new ArrayList<AbstractContentNegotiationTest.FitTest>();
for (FitTest testParam : testParameters) {
try {
testParam.execute(serviceEndpoint);
successTests.add(testParam);
} catch (AssertionError e) {
test2Failure.put(testParam, e);
} finally {
if (sleepTimeInMs > 0) {
TimeUnit.MILLISECONDS.sleep(sleepTimeInMs);
}
}
}
// System.out.println("#########################################");
// System.out.println("# Success: '" + successTests.size() + "', failed '" + test2Failure.size() +
// "', total '" + testParameters.size() + "'.");
// System.out.println("#########################################");
if (!test2Failure.isEmpty()) {
Set<Entry<FitTest, AssertionError>> failedTests = test2Failure.entrySet();
List<AssertionError> errors = new ArrayList<AssertionError>();
for (Entry<FitTest, AssertionError> entry : failedTests) {
errors.add(entry.getValue());
}
Assert.fail("Found '" + test2Failure.size() + "' test failures. See [\n" + errors + "]");
}
}
}
static class FitTestBuilder {
private FitTest test;
public FitTestBuilder(final FitTestSet testSet) {
test = new FitTest(testSet);
}
public FitTestBuilder(final UriType uriType, final String httpMethod, final String path,
final HttpStatusCodes expectedStatusCode, final String expectedContentType) {
test = new FitTest(uriType, httpMethod, path, expectedStatusCode, expectedContentType);
}
public FitTestBuilder queryOptions(final String queryOptions) {
test.queryOptions = queryOptions;
return this;
}
public FitTestBuilder content(final String content) {
test.request.content = content;
return this;
}
/**
* Set header with name to given value without any sort of checking or validation of value.
* @param name
* @param value
* @return FitTestBuilder
*/
public FitTestBuilder header(final String name, final String value) {
test.request.headers.put(name, value);
return this;
}
/**
* Set the accept header if value is not <code>NULL</code> and has a <code>length > 0</code>
* @param value
* @return FitTestBuilder
*/
public FitTestBuilder acceptHeader(final String value) {
if (value != null && value.length() > 0) {
return header(HttpHeaders.ACCEPT, value);
}
return this;
}
/**
* Set the accept header if value is not <code>NULL</code> and has a <code>length > 0</code>
* @param value
* @return FitTestBuilder
*/
public FitTestBuilder contentTypeHeader(final String value) {
if (value != null && value.length() > 0) {
return header(HttpHeaders.CONTENT_TYPE, value);
}
return this;
}
public FitTestBuilder isResponseContentExpected(final boolean isExpected) {
test.isContentExpected = isExpected;
return this;
}
public FitTest build() {
return test;
}
}
/**
* Combination of test parameters and expected test result which can be tested/executed against a service endpoint.
*/
protected static class FitTest {
private final UriType uriType;
private final String path;
private final FitTestRequest request;
private String queryOptions;
private String requestLine;
private HttpStatusCodes expectedStatusCode;
private String expectedContentType;
private boolean isContentExpected;
public FitTest(final FitTestSet testSet) {
this(testSet.uriType, testSet.httpMethod, testSet.path, testSet.expectedStatusCode, testSet.expectedContentType);
}
public FitTest(final UriType uriType, final String httpMethod, final String path,
final HttpStatusCodes expectedStatusCode, final String expectedContentType) {
super();
this.uriType = uriType;
this.path = path;
this.expectedStatusCode = expectedStatusCode;
this.expectedContentType = expectedContentType;
isContentExpected = false;
//
request = new FitTestRequest();
request.type = httpMethod;
}
@Override
public String toString() {
return "TestParam [testUrl=" + requestLine + ", \n\turiType=" + uriType + ", path=" + path +
", queryOption=" + queryOptions + ", request=" + request +
", expectedStatusCode=" + expectedStatusCode + ", expectedContentType="
+ expectedContentType + ", isContentExpected=" + isContentExpected + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
String acceptHeader = request.headers.get(HttpHeaders.ACCEPT);
result = prime * result + ((acceptHeader == null) ? 0 : acceptHeader.hashCode());
String contentType = request.headers.get(HttpHeaders.CONTENT_TYPE);
result = prime * result + ((contentType == null) ? 0 : contentType.hashCode());
result = prime * result + ((path == null) ? 0 : path.hashCode());
result = prime * result + ((queryOptions == null) ? 0 : queryOptions.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
FitTest other = (FitTest) obj;
if (request == null) {
if (other.request != null) {
return false;
}
} else if (!request.equals(other.request)) {
return false;
}
if (path == null) {
if (other.path != null) {
return false;
}
} else if (!path.equals(other.path)) {
return false;
}
if (queryOptions == null) {
if (other.queryOptions != null) {
return false;
}
} else if (!queryOptions.equals(other.queryOptions)) {
return false;
}
return true;
}
public void execute(final URI serviceEndpoint) throws Exception {
HttpRequestBase request = null;
try {
String endpoint = serviceEndpoint.toASCIIString();
String requestUrl = endpoint.substring(0, endpoint.length() - 1) + path;
if (queryOptions != null) {
requestUrl += queryOptions;
}
request = this.request.createRequest(requestUrl);
requestLine = request.getRequestLine().toString();
HttpClient httpClient = new DefaultHttpClient();
LOG.debug("Execute test for [" + requestLine + "]");
final HttpResponse response = httpClient.execute(request);
LOG.debug("Got response for request [" + requestLine + "]");
int resultStatusCode = response.getStatusLine().getStatusCode();
assertEquals("Unexpected status code for " + toString(), expectedStatusCode.getStatusCode(), resultStatusCode);
final String contentType = response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue();
assertEquals("Unexpected content type for " + toString(), ContentType.create(expectedContentType), ContentType
.create(contentType));
if (isContentExpected) {
assertNotNull("Unexpected content for " + toString(), StringHelper.inputStreamToString(response.getEntity()
.getContent()));
}
LOG.trace("Test passed [" + toString() + "]");
} finally {
if (request != null) {
request.releaseConnection();
LOG.debug("Released connection [" + requestLine + "]");
}
}
}
public static FitTestBuilder init(final UriType uriType, final String httpMethod, final String path,
final HttpStatusCodes expectedStatusCode, final String expectedContentType) {
return new FitTestBuilder(uriType, httpMethod, path, expectedStatusCode, expectedContentType);
}
public static FitTestBuilder init(final FitTestSet fitTestSet) {
return new FitTestBuilder(fitTestSet);
}
public static FitTest create(final UriType uriType, final String httpMethod, final String path,
final String queryOption, final String acceptHeader,
final String content, final String requestContentType,
final HttpStatusCodes expectedStatusCode, final String expectedContentType) {
return init(uriType, httpMethod, path, expectedStatusCode, expectedContentType)
.queryOptions(queryOption)
.content(content).contentTypeHeader(requestContentType)
.acceptHeader(acceptHeader)
.build();
}
private static List<FitTest> create(final FitTestSet fitTestSet,
final List<String> queryOptions, final List<String> acceptHeaders,
final HttpStatusCodes expectedStatusCode, final String expectedContentType) {
Map<String, ContentType> acceptHeader2ContentType = new HashMap<String, ContentType>();
for (String acceptHeader : acceptHeaders) {
acceptHeader2ContentType.put(acceptHeader, ContentType.create(expectedContentType));
}
String content = fitTestSet.content;
List<String> reqContentTypes = fitTestSet.requestContentTypes;
return create(fitTestSet.uriType, fitTestSet.httpMethod, fitTestSet.path,
queryOptions, acceptHeaders, acceptHeader2ContentType,
content, reqContentTypes, expectedStatusCode);
}
public static List<FitTest> create(final FitTestSet fitTestSet) {
Map<String, ContentType> acceptHeader2ContentType = new HashMap<String, ContentType>();
if (fitTestSet.expectedContentType != null) {
for (String acceptHeader : fitTestSet.acceptHeader) {
acceptHeader2ContentType.put(acceptHeader, ContentType.create(fitTestSet.expectedContentType));
}
}
return create(fitTestSet, acceptHeader2ContentType);
}
public static List<FitTest> create(final FitTestSet fitTestSet,
final Map<String, ContentType> acceptHeader2ContentType) {
UriType uriType = fitTestSet.uriType;
String httpMethod = fitTestSet.httpMethod;
String path = fitTestSet.path;
List<String> queryOptions = fitTestSet.queryOptions;
List<String> acceptHeaders = fitTestSet.acceptHeader;
String content = fitTestSet.content;
List<String> requestContentTypeHeaders = fitTestSet.requestContentTypes;
HttpStatusCodes expectedStatusCode = fitTestSet.expectedStatusCode;
return create(uriType, httpMethod, path, queryOptions, acceptHeaders, acceptHeader2ContentType,
content, requestContentTypeHeaders, expectedStatusCode);
}
/**
*
*/
private static List<FitTest> create(final UriType uriType, final String httpMethod, final String path,
final List<String> queryOptions,
final List<String> acceptHeaders, final Map<String, ContentType> acceptHeader2ContentType,
final String content, final List<String> requestContentTypeHeaders, final HttpStatusCodes expectedStatusCode) {
List<FitTest> testParameters = new ArrayList<AbstractContentNegotiationTest.FitTest>();
for (String queryOption : queryOptions) {
for (String acceptHeader : acceptHeaders) {
for (String requestContentType : requestContentTypeHeaders) {
String expectedContentType = getExpectedResponseContentType(acceptHeader2ContentType, acceptHeader);
FitTest tp = init(uriType, httpMethod, path, expectedStatusCode, expectedContentType)
.queryOptions(queryOption)
.acceptHeader(acceptHeader)
.content(content).contentTypeHeader(requestContentType)
.build();
testParameters.add(tp);
}
}
}
return testParameters;
}
private static String getExpectedResponseContentType(final Map<String, ContentType> acceptHeader2ContentType,
final String acceptHeader) {
String expectedContentType = null;
if (acceptHeader != null) {
ContentType tmpContentType = acceptHeader2ContentType.get(acceptHeader);
if (tmpContentType == null) {
tmpContentType = ContentType.create(ContentType.create(acceptHeader),
ContentType.PARAMETER_CHARSET, ContentType.CHARSET_UTF_8);
}
expectedContentType = tmpContentType.toContentTypeString();
}
return expectedContentType;
}
}
static class FitTestRequest {
String type;
Map<String, String> headers = new HashMap<String, String>();
String content;
String requestUrl;
HttpRequestBase createRequest(final String requestUrl) {
this.requestUrl = requestUrl;
URI uri = URI.create(requestUrl);
HttpRequestBase request;
// first try read (GET)
if ("GET".equals(type)) {
request = new HttpGet(uri);
} else { // then try write
HttpEntityEnclosingRequestBase writeRequest;
if ("POST".equals(type)) {
writeRequest = new HttpPost(uri);
} else if ("PUT".equals(type)) {
writeRequest = new HttpPut(uri);
} else {
throw new IllegalArgumentException("Unsupported HttpMethod of type '" + type + "'.");
}
// common write parts
HttpEntity entity = createEntity();
writeRequest.setEntity(entity);
request = writeRequest;
}
// common request parts
Set<Entry<String, String>> entries = headers.entrySet();
for (Entry<String, String> entry : entries) {
request.addHeader(entry.getKey(), entry.getValue());
}
return request;
}
private HttpEntity createEntity() {
if (content == null) {
throw new IllegalArgumentException("Found NULL content for '" + toFullString() + "' request.");
}
String contentType = headers.get(HttpHeaders.CONTENT_TYPE);
if (contentType == null) {
throw new IllegalArgumentException("Found NONE Content-Type header for '" + toFullString() + "' request.");
}
org.apache.http.entity.ContentType ct = org.apache.http.entity.ContentType.create(contentType);
return new StringEntity(content, ct);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((content == null) ? 0 : content.hashCode());
result = prime * result + ((headers == null) ? 0 : headers.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
FitTestRequest other = (FitTestRequest) obj;
if (content == null) {
if (other.content != null) {
return false;
}
} else if (!content.equals(other.content)) {
return false;
}
if (headers == null) {
if (other.headers != null) {
return false;
}
} else if (!headers.equals(other.headers)) {
return false;
}
if (type == null) {
if (other.type != null) {
return false;
}
} else if (!type.equals(other.type)) {
return false;
}
return true;
}
@Override
public String toString() {
return "FTR [type=" + type + ", headers=" + headers + "]";
}
public String toFullString() {
return "FitTestRequest [type=" + type + ", requestUrl=" + requestUrl + ", headers=" + headers + ", content=\n{"
+ content + "\n}]";
}
}
}