/* * Copyright 2008-2011 the original author or authors. * * 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 com.nominanuda.hyperapi; import static com.nominanuda.zen.io.Uris.URIS; import static com.nominanuda.zen.obj.JsonPath.JPATH; import java.io.UnsupportedEncodingException; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpDelete; 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.message.BasicHeader; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.nominanuda.springmvc.HttpContext; import com.nominanuda.urispec.URISpec; import com.nominanuda.web.http.Http500Exception; import com.nominanuda.web.http.HttpProtocol; import com.nominanuda.web.mvc.ObjURISpec; import com.nominanuda.zen.common.Check; import com.nominanuda.zen.obj.Arr; import com.nominanuda.zen.obj.Obj; import com.nominanuda.zen.obj.Stru; public class HyperApiHttpInvocationHandler implements InvocationHandler { private final static String USER_AGENT = "curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3"; private final RequestConfig requestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES).build(); private final EntityCodec entityCodec = EntityCodec.createBasic(); private final Logger log = LoggerFactory.getLogger(getClass()); private final IHttpAppExceptionRenderer exceptionRenderer; private final HttpClient client; private final String uriPrefix; private final String userAgent; public HyperApiHttpInvocationHandler(HttpClient client, String uriPrefix, String userAgent, IHttpAppExceptionRenderer exceptionRenderer) { this.client = client; this.uriPrefix = uriPrefix; this.userAgent = Check.ifNullOrEmpty(userAgent, USER_AGENT); this.exceptionRenderer = exceptionRenderer; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { HttpRequestBase request = null; try { request = encode(uriPrefix, method, args); } catch (Exception e) { log.error(method.getName() + e); return null; } return executeDecodeAndRelease(method, request); } private HttpRequestBase encode(String uriPrefix, Method method, Object[] args) { HttpEntity entity = null; Obj uriParams = Obj.make(); List<Header> requestHeaders = new ArrayList<>(); List<NameValuePair> formParams = new ArrayList<>(); Class<?>[] parameterTypes = method.getParameterTypes(); Annotation[][] parameterAnnotations = method.getParameterAnnotations(); for (int i = 0; i < parameterTypes.length; i++) { Class<?> parameterType = parameterTypes[i]; Annotation[] annotations = parameterAnnotations[i]; Object arg = args[i]; boolean annotationFound = false; for (Annotation annotation : annotations) { if (annotation instanceof HeaderParam) { annotationFound = true; if (arg != null) { requestHeaders.add(new BasicHeader(((HeaderParam) annotation).value(), arg.toString())); } break; } else if (annotation instanceof PathParam) { annotationFound = true; if (arg != null) { uriParams.put(((PathParam) annotation).value(), arg.toString()); } break; } else if (annotation instanceof QueryParam) { annotationFound = true; if (arg != null) { uriParams.put(((QueryParam) annotation).value(), arg instanceof Collection ? toDataArray((Collection<?>) arg) : arg.toString() ); } break; } else if (annotation instanceof FormParam) { annotationFound = true; if (arg != null) { String name = ((FormParam) annotation).value(); if (arg instanceof Obj) { Map<String, Object> map = new HashMap<String, Object>(); JPATH.toFlatMap(Obj.make(name, arg), map); for (Entry<String, Object> entry : map.entrySet()) { if (entry.getValue() != null) { formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString())); } } } else if (arg instanceof Collection) { for (Object v : (Collection<?>) arg) { if (v != null) { formParams.add(new BasicNameValuePair(name, v.toString())); } } } else { formParams.add(new BasicNameValuePair(name, arg.toString())); } } break; } } if (!annotationFound) { Check.unsupportedoperation.assertNull(entity); entity = entityCodec.encode(arg, new AnnotatedType(parameterType, annotations)); } } String httpMethod = null; URISpec<Obj> spec = null; for (Annotation a : method.getAnnotations()) { if (a instanceof POST) { httpMethod = "POST"; } else if (a instanceof GET) { httpMethod = "GET"; } else if (a instanceof PUT) { httpMethod = "PUT"; } else if (a instanceof DELETE) { httpMethod = "DELETE"; } else if (a instanceof Path) { spec = new ObjURISpec(URIS.pathJoin(uriPrefix, ((Path) a).value())); // } else if (a instanceof Consumes) { // consumedMediaTypes = ((Consumes) a).value(); } } if (!formParams.isEmpty()) { try { httpMethod = "POST"; entity = new UrlEncodedFormEntity(formParams, HttpProtocol.UTF_8); } catch (UnsupportedEncodingException e) { throw new Http500Exception("Unsupported parameter encoding"); } } HttpRequestBase request = buildRequest(spec.template(uriParams), httpMethod, requestHeaders, method.getReturnType()); if (entity != null && request instanceof HttpEntityEnclosingRequest) { ((HttpEntityEnclosingRequest) request).setEntity(entity); } return request; } private Arr toDataArray(Collection<?> arg) { Arr arr = Arr.make(); for (Object obj : arg) { if (obj != null) { arr.add(obj.toString()); } } return arr; } private HttpRequestBase buildRequest(String uri, String httpMethod, List<Header> headers, Class<?> returnType) { HttpRequestBase request = createRequest(uri, httpMethod); request.setConfig(requestConfig); request.setHeaders(headers.toArray(new Header[headers.size()])); if (Stru.class.isAssignableFrom(returnType)) { request.setHeader("Accept", HttpProtocol.CT_APPLICATION_JSON); } else { request.setHeader("Accept", "*/*"); } request.setHeader("Cache-Control", "no-cache"); request.setHeader("Connection", "close"); // TODO remove? request.setHeader("Pragma", "no-cache"); request.setHeader("User-Agent", userAgent); return request; } private HttpRequestBase createRequest(String uri, String httpMethod) { switch (httpMethod) { case "GET": return new HttpGet(uri); case "POST": return new HttpPost(uri); case "PUT": return new HttpPut(uri); case "DELETE": return new HttpDelete(uri); } throw new IllegalArgumentException("unknown http method " + httpMethod); } private Object executeDecodeAndRelease(Method method, HttpRequestBase request) throws Exception { log.info(request.getMethod() + " " + request.getURI().toString()); HttpContext httpContext = HttpContext.getInstance(); // long startTime = System.currentTimeMillis(); HttpResponse response = null; try { Object result = null; httpContext.writeTo(request); response = client.execute(request); httpContext.update(response); HttpEntity entity = response.getEntity(); if (entity != null && entity.getContent() != null) { result = entityCodec.decode(entity, new AnnotatedType(method.getReturnType(), method.getAnnotations())); } if (exceptionRenderer != null) { // can be null if we don't want to throw exceptions exceptionRenderer.parseAndThrow(response.getStatusLine().getStatusCode(), result); } return result; } catch (Exception e) { log.error(e.toString()); throw e; } finally { if (response != null) { EntityUtils.consume(response.getEntity()); } request.releaseConnection(); } } }