com.magnet.android.mms.controller.RequestSchema.java Source code

Java tutorial

Introduction

Here is the source code for com.magnet.android.mms.controller.RequestSchema.java

Source

/*
 * Copyright (c) 2014 Magnet Systems, Inc.
 * All rights reserved.
 *
 *  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.magnet.android.mms.controller;

import com.magnet.android.core.MagnetRestRequestType;
import com.magnet.android.mms.connection.ConnectionService;
import com.magnet.android.mms.connection.ConnectionService.Request.Method;

import org.json.JSONObject;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class RequestSchema {

    public final static String DEFAULT_CONTENT_TYPE = "application/json";
    public final static String MULTIPART_RELATED_CONTENT_TYPE = "multipart/related";

    public static boolean isMultiPart(Collection<String> types) {
        if (types == null) {
            return false;
        }
        for (String type : types) {
            if (type.equals(MULTIPART_RELATED_CONTENT_TYPE)) {
                return true;
            }
        }
        return false;
    }

    // describes the schema for how to construct a controller request over the wire from Java class
    public static class JMethod {
        JMeta metaInfo;
        List<JParam> params = new ArrayList<JParam>();
        private final Class<?> returnType; // return type of the method. if collection, the type of the element
        private final Type[] returnComponentTypes; // return component or templated types if returnType is array or generic class

        public JMethod() {
            returnType = Void.class;
            returnComponentTypes = null;
        }

        /**
         * Constructs a new JMethod object
         * @param returnType return type of the method. Use this if returnType is not a generic or collection
         */
        public JMethod(Class<?> returnType) {
            this.returnType = returnType;
            this.returnComponentTypes = null;
        }

        /**
         * Constructs a new JMethod object
         * @param returnType return type of the method
         * @param returnComponentType if returnType is generic or array/collection, the type of the actual objects
         */
        public JMethod(Class<?> returnType, Class<?> returnComponentType) {
            this.returnType = returnType;
            if (returnComponentType != null) {
                this.returnComponentTypes = new Type[1];
                this.returnComponentTypes[0] = returnComponentType;
            } else {
                this.returnComponentTypes = null;
            }
        }

        public JMethod setBaseUrl(String baseUrl) {
            this.metaInfo.baseUrl = baseUrl;
            return this;
        }

        public boolean equals(Object method) {
            boolean result;
            JMethod m = (JMethod) method;
            result = this.metaInfo.methodName.equals(m.metaInfo.methodName);
            if (result) {
                result = this.metaInfo.restApiPath.equals(m.metaInfo.restApiPath);
            }
            if (result) {
                result = this.metaInfo.restMethod.compareTo(m.metaInfo.restMethod) == 0;
            }
            if (result) {
                result = this.params.size() == m.params.size();
            }
            if (result) {
                // compare each parameter and see if it's the same
                for (int i = 0; result && i < this.params.size(); i++) {
                    JParam param = this.params.get(i);
                    JParam compParam = m.params.get(i);
                    result = param.equals(compParam);
                }
            }
            return result;
        }

        public Class<?> getReturnType() {
            return returnType;
        }

        public JMethod setMetaInfo(JMeta meta) {
            this.metaInfo = meta;
            return this;
        }

        public JParam addParam(String name, String style, Class<?> paramClass, Class<?> componentClass,
                String typeName, boolean optional) {
            JParam param = new JParam(name, style, paramClass, componentClass, typeName, optional);
            this.params.add(param);
            return param;
        }

        public List<JParam> getParams() {
            return Collections.unmodifiableList(params);
        }

        public JMeta getMetaInfo() {
            return metaInfo;
        }

        public Type[] getReturnComponentTypes() {
            return returnComponentTypes;
        }

        /**
         * Return the actual class that the method returns. 
         * For generic or array/collection, returns the class of the component objects.
         * @return
         */
        public Class<?> getActualReturnTypeAsClass() {
            if (returnComponentTypes == null) {
                return returnType;
            } else {
                return Class.class.cast(returnComponentTypes[0]);
            }
        }
    }

    public static class JMeta {
        public final ConnectionService.Request.Method restMethod; // "POST","GET"
        public final String restApiPath; // "/<rest-api-path>"
        private String methodName; // Java method name
        private Collection<String> produces; // "produces" : [ "multipart/related" ]
        private Collection<String> consumes; // "consumes" : [ "multipart/related" ]
        private String baseUrl; // base URL, typically the host URL to the 3rd party Rest service

        public JMeta(String methodName, String path, String restMethod) {
            this.methodName = methodName;
            this.restApiPath = path;
            this.restMethod = Method.valueOf(restMethod);

        }

        public void setMethodName(String methodName) {
            this.methodName = methodName;
        }

        public String getMethodName() {
            return methodName;
        }

        public void setProduces(List<String> produces) {
            this.produces = produces;
        }

        public Collection<String> getProduces() {
            return produces;
        }

        public void setConsumes(List<String> consumes) {
            this.consumes = consumes;
        }

        public Collection<String> getConsumes() {
            return consumes;
        }

        public void setBaseUrl(String path) {
            baseUrl = path;
        }

        public String getBaseUrl() {
            return baseUrl;
        }
    }

    /**
     * PLAIN style:
     * Payload for primitive parameter is constructed based:
     * <name>: value
     *
     * Entity aClass: "magnet-aClass" is added to Each JSON rep of the entity
     *
     * "magnet-class": "<typeName>"
     * "<prop-name>": <prop-value>
     *
     * QUERY style (primitive class only):
     * value is added to the URL
     * /<rest-api-path>/<cmeta.name>?<cparam.name>=value
     *
     * FORM style - form-urlencoded
     * <cparam.nama>=value&<cparam.nama>=value
     *
     * TEMPLATE style:
     *
     */
    public static class JParam {

        public final String name; // "name"  (the label used in constructing label in json)
        public final MagnetRestRequestType.ParamStyle style; // "style" (QUERY,PLAIN,FORM,TEMPLATE)
        private final Class<?> paramClz; // Java class of the parameter;
        private final Type[] actualClz; // if paramClz is generic

        public final boolean optional; // true,false
        private String typeName; // magnet-type

        public JParam(String name, String style, Class<?> paramClz, Class<?> componentClz, String typeName,
                boolean optional) {
            this.name = name;
            this.style = MagnetRestRequestType.ParamStyle.valueOf(style);
            this.optional = optional;
            this.typeName = typeName;
            if (componentClz != null) {
                this.actualClz = new Type[1];
                this.actualClz[0] = componentClz;
            } else {
                this.actualClz = null;
            }
            this.paramClz = paramClz;
        }

        public boolean equals(Object param) {
            JParam p = (JParam) param;
            boolean result = this.getParamType().equals(p.getParamType());
            if (result) {
                result = this.getActualTypeAsClass().equals(p.getActualTypeAsClass());
            }
            return result;
        }

        /*
         *  return true if the instance can be considered the same type as the parameter spec
         */
        public boolean isInstanceOf(Object actualObj) {
            boolean result;
            Class<?> actualType = actualObj.getClass();
            // since there is no way to get the actual type of for generics based on object instance,
            // compare the type only
            result = this.getParamType().equals(actualType);
            return result;
        }

        public void setTypeName() {
            this.typeName = typeName;
        }

        public String getTypeName() {
            return typeName;
        }

        public Class<?> getParamType() {
            return paramClz;
        }

        public Type[] getActualTypes() {
            return actualClz;
        }

        /**
         * Return the actual type of the parameter.
         * If paramType is generic, returns the templated type from the first item in the type list
         * @return
         */
        public Class<?> getActualTypeAsClass() {
            if (actualClz == null) {
                return paramClz;
            } else {
                return Class.class.cast(actualClz[0]);
            }
        }
    }

    private Map<String, List<JMethod>> overloadMethodMap;

    private final Map<String, JMethod> methods = new HashMap<String, JMethod>();
    private String apiRootPath;

    public RequestSchema() {
    }

    /**
     * TODO Construct a JMethod suitable for Java proxy construction
     * @param schema Schema of the class in JSON representation
     * @return instance of JMethod
     * @throws java.lang.RuntimeException if schema is invalid
     */
    public static RequestSchema fromJson(JSONObject schema) {
        return new RequestSchema();
    }

    /**
     * TODO Create JSON representation of the schema
     * @return schema in JSON
     */
    public static JSONObject toJson() {
        return new JSONObject();
    }

    // root path to the controller API; could be useful for diff. versions
    // e.g. v1/postHello, v2/postHello, etc
    public void setRootPath(String path) {
        this.apiRootPath = path;
    }

    public String getRootPath() {
        return apiRootPath;
    }

    public void putMethod(String methodName, JMethod method) {
        JMethod existingMethod = methods.get(methodName);
        if (existingMethod == null || existingMethod != null && existingMethod.equals(method)) {
            methods.put(methodName, method);
            return;
        }
        // add to list of existing methods
        if (overloadMethodMap == null) {
            overloadMethodMap = new HashMap<String, List<JMethod>>();
        }
        List<JMethod> methodList = overloadMethodMap.get(methodName);
        if (methodList == null) {
            methodList = new ArrayList<JMethod>();
            if (existingMethod != null) {
                methodList.add(existingMethod);
                // remove it from normal map
                methods.remove(methodName);
            }
            overloadMethodMap.put(methodName, methodList);
        }
        // add it to the method list
        methodList.add(method);
    }

    public JMethod getMethod(String methodName) {
        return methods.get(methodName);
    }

    // find match for method based on methodName and parameters for overloaded methods
    public JMethod getMethod(String methodName, Object[] args) {
        JMethod method = methods.get(methodName);
        if (method != null) {
            return method;
        }
        List<JMethod> overloadMethods = overloadMethodMap.get(methodName);
        if (overloadMethods != null) {
            for (JMethod m : overloadMethods) {
                // compare each parameter
                if ((args == null || args.length == 0) && m.params.size() == 0) {
                    return m;
                }
                if (m.params.size() != args.length) {
                    continue;
                }
                int i = 0;
                boolean same = false;
                for (JParam param : m.params) {
                    // see if it's the same type
                    same = param.isInstanceOf(args[i++]);
                    if (!same) {
                        continue;
                    }
                }
                if (same) {
                    return m;
                }
            }
            // should have found match in the method list
            return null;
        }
        return null;
    }

    public Map<String, JMethod> getMethodMap() {
        return Collections.unmodifiableMap(methods);
    }

    // use it when code is obfuscated to rebind method names to method meta map
    public void bindMethodName(String plainName, String encodedName) {
        JMethod method = methods.get(plainName);
        if (method != null) {
            methods.remove(plainName);
            methods.put(encodedName, method);
        }
    }

    // use it when code is obfuscated to rebind method names to method meta map
    public void bindMethodNameMap(HashMap<String, String> encNameMap) {
        for (String key : encNameMap.keySet()) {
            bindMethodName(key, encNameMap.get(key));
        }
    }
}