org.akita.proxy.ProxyInvocationHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.akita.proxy.ProxyInvocationHandler.java

Source

/*
 * Copyright 1999-2101 Alibaba Group.
 *
 * 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.akita.proxy;

import org.akita.annotation.*;
import org.akita.exception.AkInvokeException;
import org.akita.io.HttpInvoker;
import org.akita.util.JsonMapper;
import org.akita.util.Log;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URLEncoder;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Dynamic Proxy Invocation Handler
 * @author zhe.yangz 2011-12-28 ?04:47:56
 */
public class ProxyInvocationHandler implements InvocationHandler {

    private static final String TAG = "ProxyInvocationHandler";

    public Object bind(Class<?> clazz) {
        Class<?>[] clazzs = { clazz };
        Object newProxyInstance = Proxy.newProxyInstance(clazz.getClassLoader(), clazzs, this);
        return newProxyInstance;
    }

    /** 
     * Dynamic proxy invoke
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        AkPOST akPost = method.getAnnotation(AkPOST.class);
        AkGET akGet = method.getAnnotation(AkGET.class);

        AkAPI akApi = method.getAnnotation(AkAPI.class);
        Annotation[][] annosArr = method.getParameterAnnotations();
        String invokeUrl = akApi.url();
        ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();

        // AkApiParams to hashmap, filter out of null-value
        HashMap<String, File> filesToSend = new HashMap<String, File>();
        HashMap<String, String> paramsMapOri = new HashMap<String, String>();
        HashMap<String, String> paramsMap = getRawApiParams2HashMap(annosArr, args, filesToSend, paramsMapOri);
        // Record this invocation
        ApiInvokeInfo apiInvokeInfo = new ApiInvokeInfo();
        apiInvokeInfo.apiName = method.getName();
        apiInvokeInfo.paramsMap.putAll(paramsMapOri);
        apiInvokeInfo.url = invokeUrl;
        // parse '{}'s in url
        invokeUrl = parseUrlbyParams(invokeUrl, paramsMap);
        // cleared hashmap to params, and filter out of the null value
        Iterator<Entry<String, String>> iter = paramsMap.entrySet().iterator();
        while (iter.hasNext()) {
            Entry<String, String> entry = iter.next();
            params.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
        }

        // get the signature string if using
        AkSignature akSig = method.getAnnotation(AkSignature.class);
        if (akSig != null) {
            Class<?> clazzSignature = akSig.using();
            if (clazzSignature.getInterfaces().length > 0 // TODO: NEED VERIFY WHEN I HAVE TIME
                    && InvokeSignature.class.getName().equals(clazzSignature.getInterfaces()[0].getName())) {
                InvokeSignature is = (InvokeSignature) clazzSignature.getConstructors()[0].newInstance();
                String sigValue = is.signature(akSig, invokeUrl, params, paramsMapOri);
                String sigParamName = is.getSignatureParamName();
                if (sigValue != null && sigParamName != null && sigValue.length() > 0
                        && sigParamName.length() > 0) {
                    params.add(new BasicNameValuePair(sigParamName, sigValue));
                }
            }
        }

        // choose POST GET PUT DELETE to use for this invoke
        String retString = "";
        if (akGet != null) {
            StringBuilder sbUrl = new StringBuilder(invokeUrl);
            if (!(invokeUrl.endsWith("?") || invokeUrl.endsWith("&"))) {
                sbUrl.append("?");
            }
            for (NameValuePair nvp : params) {
                sbUrl.append(nvp.getName());
                sbUrl.append("=");
                sbUrl.append(nvp.getValue());
                sbUrl.append("&");
            } // now default using UTF-8, maybe improved later
            retString = HttpInvoker.get(sbUrl.toString());
        } else if (akPost != null) {
            if (filesToSend.isEmpty()) {
                retString = HttpInvoker.post(invokeUrl, params);
            } else {
                retString = HttpInvoker.postWithFilesUsingURLConnection(invokeUrl, params, filesToSend);
            }
        } else { // use POST for default
            retString = HttpInvoker.post(invokeUrl, params);
        }

        // invoked, then add to history
        //ApiStats.addApiInvocation(apiInvokeInfo);

        //Log.d(TAG, retString);

        // parse the return-string
        final Class<?> returnType = method.getReturnType();
        try {
            if (String.class.equals(returnType)) { // the result return raw string
                return retString;
            } else { // return object using json decode
                return JsonMapper.json2pojo(retString, returnType);
            }
        } catch (Exception e) {
            Log.e(TAG, retString, e); // log can print the error return-string
            throw new AkInvokeException(AkInvokeException.CODE_JSONPROCESS_EXCEPTION, e.getMessage(), e);
        }
    }

    /**
     * Replace all the {} block in url to the actual params, 
     * clear the params used in {block}, return cleared params HashMap and replaced url.
     * @param url such as http://server/{namespace}/1/do
     * @param params such as hashmap include (namespace->'mobile')
     * @return the parsed param will be removed in HashMap (params)
     */
    private String parseUrlbyParams(String url, HashMap<String, String> params) throws AkInvokeException {

        StringBuffer sbUrl = new StringBuffer();
        Pattern pattern = Pattern.compile("\\{(.+?)\\}");
        Matcher matcher = pattern.matcher(url);

        while (matcher.find()) {
            String paramValue = params.get(matcher.group(1));
            if (paramValue != null) {
                matcher.appendReplacement(sbUrl, paramValue);
            } else { // {name}?
                throw new AkInvokeException(AkInvokeException.CODE_PARAM_IN_URL_NOT_FOUND,
                        "Parameter {" + matcher.group(1) + "}'s value not found of url " + url + ".");
            }
            params.remove(matcher.group(1));
        }
        matcher.appendTail(sbUrl);

        return sbUrl.toString();
    }

    /**
     * AkApiParams to hashmap, filter out of null-value
     *
     * @param annosArr Method's params' annotation array[][]
     * @param args Method's params' values
     * @param filesToSend
     * @return HashMap all (paramName -> paramValue)
     */
    private HashMap<String, String> getRawApiParams2HashMap(Annotation[][] annosArr, Object[] args,
            HashMap<String, File> filesToSend, HashMap<String, String> paramsMapOri) {
        HashMap<String, String> paramsMap = new HashMap<String, String>();
        for (int idx = 0; idx < args.length; idx++) {
            String paramName = null;
            String encode = "none";
            for (Annotation a : annosArr[idx]) {
                if (AkParam.class.equals(a.annotationType())) {
                    AkParam ap = (AkParam) a;
                    paramName = ap.value();
                    encode = ap.encode();
                }
            }
            if (paramName != null) {
                Object arg = args[idx];
                if (arg != null) { // filter out of null-value param
                    if ("$paramMap".equals(paramName)) {
                        Map<String, String> paramMap = (Map<String, String>) arg;
                        paramsMapOri.putAll(paramMap);
                        if (encode != null && !"none".equals(encode)) {
                            HashMap<String, String> encodedMap = new HashMap<String, String>();
                            for (Entry<String, String> entry : paramMap.entrySet()) {
                                try {
                                    encodedMap.put(entry.getKey(), URLEncoder.encode(entry.getValue(), encode));
                                } catch (Exception e) {
                                    Log.w(TAG, "UnsupportedEncodingException:" + encode);
                                    encodedMap.put(entry.getKey(), entry.getValue());
                                }
                            }
                            paramsMap.putAll(encodedMap);
                        } else {
                            paramsMap.putAll(paramMap);
                        }
                    } else if ("$filesToSend".equals(paramName)) {
                        if (arg instanceof Map) {
                            Map<String, File> files = (Map<String, File>) arg;
                            filesToSend.putAll(files);
                        }
                    } else if (encode != null && !"none".equals(encode)) {
                        try {
                            paramsMap.put(paramName, URLEncoder.encode(arg.toString(), encode));
                        } catch (UnsupportedEncodingException e) {
                            Log.w(TAG, "UnsupportedEncodingException:" + encode);
                            paramsMap.put(paramName, arg.toString());
                        }
                        paramsMapOri.put(paramName, arg.toString());
                    } else {
                        paramsMap.put(paramName, arg.toString());
                        paramsMapOri.put(paramName, arg.toString());
                    }
                }
            }
        }
        return paramsMap;
    }
}