/*
* Copyright 2013 Cloud4SOA, www.cloud4soa.eu
*
* 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 eu.cloud4soa.adapter.rest.impl;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.UnknownHostException;
import javax.ws.rs.core.MediaType;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.transport.http.HTTPConduit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.cloud4soa.adapter.rest.aop.Method;
import eu.cloud4soa.adapter.rest.aop.Method.HttpMethod;
import eu.cloud4soa.adapter.rest.aop.Version;
import eu.cloud4soa.adapter.rest.auth.AuthenticationPropertyExclusionProvider;
import eu.cloud4soa.adapter.rest.auth.Credentials;
import eu.cloud4soa.adapter.rest.common.HttpStatus;
import eu.cloud4soa.adapter.rest.exception.AdapterClientException;
import eu.cloud4soa.adapter.rest.request.Request;
import eu.cloud4soa.adapter.rest.response.Response;
import eu.cloud4soa.adapter.rest.struct.AbstractSerializer;
import eu.cloud4soa.adapter.rest.util.ClassUtil;
import eu.cloud4soa.adapter.rest.util.EncryptionUtil;
import eu.cloud4soa.adapter.rest.util.RequestUtil;
import eu.cloud4soa.adapter.rest.util.Timer;
import java.net.URI;
/**
* TODO implement authentication at the adapter use {@link #getCredentials()} to
* access credentials (public/private key)
*
* @author Denis Neuling (dn@cloudcontrol.de)
*/
public abstract class AbstractAdapterClientCXF extends AbstractSerializer {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@SuppressWarnings({ "unchecked" })
protected <T> T request(Request<T> request, Credentials credentials) throws AdapterClientException, UnknownHostException {
request = verify(request, credentials);
return ((T) invokeByRequestDefinition(request));
}
private <T> Request<T> verify(Request<T> request, Credentials credentials) throws AdapterClientException {
try {
return EncryptionUtil.encipher(request, credentials);
} catch (Exception e) {
throw new AdapterClientException(e.getMessage());
}
}
private <T> Response<T> invokeByRequestDefinition(Request<T> t) throws AdapterClientException, UnknownHostException {
try {
HttpMethod method = getHttpMethod(t);
switch (method) {
case POST:
return POST(t);
case PUT:
return PUT(t);
case GET:
return GET(t);
case DELETE:
return DELETE(t);
default:
throw new AdapterClientException("Method:{" + method + "} not implemented.");
}
} catch (UnknownHostException uhe) {
uhe.printStackTrace();
throw uhe;
//throw new AdapterClientException(uhe.getMessage());
} catch (org.apache.cxf.jaxrs.client.ClientWebApplicationException cwae) {
throw new AdapterClientException(cwae.getMessage());
} catch (IOException ioe) {
throw new AdapterClientException(ioe.getMessage());
}
}
/**
* HTTP1.1 get method invocation
*
* @param t
* the request to send
* @return Response the response
* @throws IOException
*/
private <T> Response<T> GET(Request<T> t) throws IOException {
String targetUrl = inquireUrl(t);
WebClient client = createCXFInstance(targetUrl);
client = setParams(client, t);
client = setHeaders(client, t);
Timer timer = Timer.tic();
javax.ws.rs.core.Response cxfResponse = client.get();
timer.toc();
log.debug(getHttpMethod(t) + " " + inquireUrl(t) + " " + cxfResponse.getStatus() + " (took " + timer.getDifference() + " ms)");
String serializedResponse = IOUtils.readStringFromStream((InputStream) cxfResponse.getEntity());
Response<T> response = deserialize(serializedResponse, t);
response.setResponseTime(timer.getDifference());
response.setStatusCode(HttpStatus.getStatus(cxfResponse.getStatus()));
if(cxfResponse.getStatus()<200 || cxfResponse.getStatus()>300){
throw new IOException(serializedResponse);
}
return response;
}
/**
* HTTP1.1 post method invocation
*
* @param t
* the request to send
* @return Response the response
* @throws IOException
*/
private <T> Response<T> POST(Request<T> t) throws IOException {
String targetUrl = inquireUrl(t);
WebClient client = setHeaders(createCXFInstance(targetUrl), t);
String serialized = serialize(t);
Timer timer = Timer.tic();
javax.ws.rs.core.Response cxfResponse = client.post(serialized);
timer.toc();
log.debug(getHttpMethod(t) + " " + inquireUrl(t) + " " + cxfResponse.getStatus() + " (took " + timer.getDifference() + " ms)");
String serializedResponse = IOUtils.readStringFromStream((InputStream) cxfResponse.getEntity());
Response<T> response = deserialize(serializedResponse, t);
response.setResponseTime(timer.getDifference());
response.setStatusCode(HttpStatus.getStatus(cxfResponse.getStatus()));
if(cxfResponse.getStatus()<200 || cxfResponse.getStatus()>300){
throw new IOException(serializedResponse);
}
return response;
}
/**
* HTTP1.1 put method invocation
*
* @param t
* the request to send
* @return Response the response
* @throws IOException
*/
private <T> Response<T> PUT(Request<T> t) throws IOException {
String targetUrl = inquireUrl(t);
WebClient client = setHeaders(createCXFInstance(targetUrl), t);
String serialized = serialize(t);
Timer timer = Timer.tic();
javax.ws.rs.core.Response cxfResponse = client.put(serialized);
timer.toc();
log.debug(getHttpMethod(t) + " " + inquireUrl(t) + " " + cxfResponse.getStatus() + " (took " + timer.getDifference() + " ms)");
String serializedResponse = IOUtils.readStringFromStream((InputStream) cxfResponse.getEntity());
Response<T> response = deserialize(serializedResponse, t);
response.setResponseTime(timer.getDifference());
response.setStatusCode(HttpStatus.getStatus(cxfResponse.getStatus()));
if(cxfResponse.getStatus()<200 || cxfResponse.getStatus()>300){
throw new IOException(serializedResponse);
}
return response;
}
/**
* HTTP1.1 delete method invocation
*
* @param t
* the request to send
* @return Response the response
* @throws IOException
*/
private <T> Response<T> DELETE(Request<T> t) throws IOException {
String targetUrl = inquireUrl(t);
WebClient client = createCXFInstance(targetUrl);
client = setParams(client, t);
client = setHeaders(client, t);
Timer timer = Timer.tic();
javax.ws.rs.core.Response cxfResponse = client.delete();
timer.toc();
log.debug(getHttpMethod(t) + " " + inquireUrl(t) + " " + cxfResponse.getStatus() + " (took " + timer.getDifference() + " ms)");
String serializedResponse = IOUtils.readStringFromStream((InputStream) cxfResponse.getEntity());
Response<T> response = deserialize(serializedResponse, t);
response.setResponseTime(timer.getDifference());
response.setStatusCode(HttpStatus.getStatus(cxfResponse.getStatus()));
if(cxfResponse.getStatus()<200 || cxfResponse.getStatus()>300){
throw new IOException(serializedResponse);
}
return response;
}
private WebClient createCXFInstance(String baseAddress) {
WebClient client = WebClient.create(baseAddress).type("application/json").accept(MediaType.TEXT_PLAIN).accept(MediaType.APPLICATION_JSON);
HTTPConduit conduit = WebClient.getConfig(client).getHttpConduit();
conduit.getClient().setReceiveTimeout(600000);
conduit.getClient().setConnectionTimeout(600000);
return client;
}
private <T> WebClient setHeaders(WebClient client, Request<T> request) {
if(client == null){
throw new IllegalArgumentException("WebClient cannot be null.");
}
if(request != null){
client.header("apiKey", request.getApiKey());
client.header("apiVersion", ClassUtil.getClassAnnotationValue(request.getClass(), Version.class, "value", String.class));
client.header("hash", request.getHash());
}
return client;
}
private <T> WebClient setParams(WebClient client, Request<T> request) {
if(client == null){
throw new IllegalArgumentException("WebClient cannot be null.");
}
if(request != null){
Field[] fields = ClassUtil.getAllDeclaredFields(request.getClass(), AuthenticationPropertyExclusionProvider.EXCLUSIONS);
for(Field field : fields){
Object value = ClassUtil.getValueOfField(field, request);
if(value != null){
client.header(field.getName(), value);
}
}
}
return client;
}
private <T> String getUrlPath(Request<T> t) {
StringBuffer urlBuffer = new StringBuffer();
String adapterRoot = getAdapterRootPath();
String urlPath = RequestUtil.resolveResourcePath(t);
if (adapterRoot != null) {
urlBuffer.append(adapterRoot);
}
if (urlPath != null) {
urlBuffer.append(urlPath);
}
return urlBuffer.toString();
}
private <T> String inquireUrl(Request<T> request) {
StringBuffer stringAppender = new StringBuffer();
String baseUrl = interpolateProtocol(request.getBaseUrl());
stringAppender.append(baseUrl);
String path = getUrlPath(request);
stringAppender.append(path);
return stringAppender.toString();
}
private <T> String interpolateProtocol(String baseUrl) {
if (baseUrl.startsWith("http://") || baseUrl.startsWith("https://")) {
return baseUrl;
}
return "http://" + baseUrl;
}
private <T> HttpMethod getHttpMethod(Request<T> t) {
HttpMethod method = ClassUtil.getClassAnnotationValue(t.getClass(), Method.class, "value", Method.HttpMethod.class);
return method;
}
/**
* The (*nix) path on what the adapter can be found.
*
* @return String the path on what the adapter lies
*/
public abstract String getAdapterRootPath();
/**
* Validates the request.
*
* @param request
* the request to validate
* @return bool true if all fields annotated with <code>@NotNull</code> are
* not null or not empty false otherwise
*/
public abstract <T> boolean validateAndInfixDefaultsIfPossible(Request<T> request);
}