ch.ralscha.extdirectspring.util.MethodInfo.java Source code

Java tutorial

Introduction

Here is the source code for ch.ralscha.extdirectspring.util.MethodInfo.java

Source

/**
 * Copyright 2010-2016 Ralph Schaer <ralphschaer@gmail.com>
 *
 * 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 ch.ralscha.extdirectspring.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;

import ch.ralscha.extdirectspring.annotation.ExtDirectDocParameters;
import ch.ralscha.extdirectspring.annotation.ExtDirectDocReturn;
import ch.ralscha.extdirectspring.annotation.ExtDirectMethod;
import ch.ralscha.extdirectspring.annotation.ExtDirectMethodDocumentation;
import ch.ralscha.extdirectspring.annotation.ExtDirectMethodType;
import ch.ralscha.extdirectspring.bean.api.Action;
import ch.ralscha.extdirectspring.bean.api.ActionDoc;
import ch.ralscha.extdirectspring.bean.api.PollingProvider;

/**
 * Object holds information about a method like the method itself and a list of
 * parameters.
 */
public final class MethodInfo {

    private final String group;

    private final ExtDirectMethodType type;

    private final Class<?> jsonView;

    private final boolean synchronizeOnSession;

    private final boolean streamResponse;

    private List<ParameterInfo> parameters;

    private Method method;

    private String forwardPath;

    private HandlerMethod handlerMethod;

    private Class<?> collectionType;

    private Action action;

    private PollingProvider pollingProvider;

    public MethodInfo(Class<?> clazz, ApplicationContext context, String beanName, Method method) {

        ExtDirectMethod extDirectMethodAnnotation = AnnotationUtils.findAnnotation(method, ExtDirectMethod.class);

        this.type = extDirectMethodAnnotation.value();

        if (extDirectMethodAnnotation.jsonView() != ExtDirectMethod.NoJsonView.class) {
            this.jsonView = extDirectMethodAnnotation.jsonView();
        } else {
            this.jsonView = null;
        }

        if (StringUtils.hasText(extDirectMethodAnnotation.group())) {
            this.group = extDirectMethodAnnotation.group().trim();
        } else {
            this.group = null;
        }

        this.synchronizeOnSession = extDirectMethodAnnotation.synchronizeOnSession();
        this.streamResponse = extDirectMethodAnnotation.streamResponse();

        if (this.type != ExtDirectMethodType.FORM_POST) {
            this.method = method;
            this.parameters = buildParameterList(clazz, method);

            this.collectionType = extDirectMethodAnnotation.entryClass() == Object.class ? null
                    : extDirectMethodAnnotation.entryClass();

            if (this.collectionType == null) {
                for (ParameterInfo parameter : this.parameters) {
                    Class<?> collType = parameter.getCollectionType();
                    if (collType != null) {
                        this.collectionType = collType;
                        break;
                    }
                }
            }
        } else {
            if (method.getReturnType().equals(Void.TYPE)) {

                RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
                RequestMapping classAnnotation = AnnotationUtils.findAnnotation(clazz, RequestMapping.class);

                String path = null;
                if (hasValue(classAnnotation)) {
                    path = classAnnotation.value()[0];
                }

                if (hasValue(methodAnnotation)) {
                    String methodPath = methodAnnotation.value()[0];
                    if (path != null) {
                        path = path + methodPath;
                    } else {
                        path = methodPath;
                    }
                }

                if (path != null) {
                    if (path.charAt(0) == '/' && path.length() > 1) {
                        path = path.substring(1, path.length());
                    }
                    this.forwardPath = "forward:" + path;
                }
            } else {
                this.handlerMethod = new HandlerMethod(beanName, context, method).createWithResolvedBean();
            }
        }

        switch (this.type) {
        case SIMPLE:
            int paramLength = 0;
            for (ParameterInfo parameter : this.parameters) {
                if (parameter.isClientParameter()) {
                    paramLength++;
                }
            }
            this.action = Action.create(method.getName(), paramLength, extDirectMethodAnnotation.batched());
            break;
        case SIMPLE_NAMED:
            int noOfClientParameters = 0;
            Class<?> parameterType = null;

            List<String> parameterNames = new ArrayList<String>();
            for (ParameterInfo parameter : this.parameters) {
                if (parameter.isClientParameter()) {
                    noOfClientParameters++;
                    parameterType = parameter.getType();
                    parameterNames.add(parameter.getName());
                }
            }

            if (noOfClientParameters == 1 && Map.class.isAssignableFrom(parameterType)) {
                this.action = Action.createNamed(method.getName(), Collections.<String>emptyList(), Boolean.FALSE,
                        extDirectMethodAnnotation.batched());
            } else {
                this.action = Action.createNamed(method.getName(), Collections.unmodifiableList(parameterNames),
                        null, extDirectMethodAnnotation.batched());
            }
            break;
        case FORM_LOAD:
            this.action = Action.create(method.getName(), 1, extDirectMethodAnnotation.batched());
            break;
        case STORE_READ:
        case STORE_MODIFY:
        case TREE_LOAD:
            List<String> metadataParams = new ArrayList<String>();
            for (ParameterInfo parameter : this.parameters) {
                if (parameter.hasMetadataParamAnnotation()) {
                    metadataParams.add(parameter.getName());
                }
            }
            this.action = Action.createTreeLoad(method.getName(), 1, metadataParams,
                    extDirectMethodAnnotation.batched());
            break;
        case FORM_POST:
            this.action = Action.createFormHandler(method.getName(), 0);
            break;
        case FORM_POST_JSON:
            this.action = Action.create(method.getName(), 1, extDirectMethodAnnotation.batched());
            break;
        case POLL:
            this.pollingProvider = new PollingProvider(beanName, method.getName(),
                    extDirectMethodAnnotation.event());
            break;
        default:
            throw new IllegalStateException("ExtDirectMethodType: " + this.type + " does not exists");
        }

        this.action = extractDocumentationAnnotations(extDirectMethodAnnotation.documentation());

    }

    /**
     * The rule is: whatever has been given is taken as is the API documentation is non
     * critical, so any discrepancies will be silently ignored
     *
     * @param documentation
     * @return ActionDoc
     */
    private Action extractDocumentationAnnotations(ExtDirectMethodDocumentation documentation) {
        if (!documentation.value().isEmpty()) {
            ActionDoc actionDoc = new ActionDoc(getAction(), documentation.value(), documentation.author(),
                    documentation.version(), documentation.deprecated());
            ExtDirectDocParameters docParameters = documentation.parameters();
            if (null != docParameters) {
                String[] params = docParameters.params();
                String[] descriptions = docParameters.descriptions() == null ? new String[params.length]
                        : docParameters.descriptions();
                if (params.length == descriptions.length) {
                    for (int i = 0; i < params.length; i++) {
                        actionDoc.getParameters().put(params[i],
                                descriptions[i] == null ? "No description" : descriptions[i]);
                    }
                } else {
                    LogFactory.getLog(MethodInfo.class).info(
                            "Documentation: skip generation of parameters, params size is different from descriptions size");
                }
            }
            ExtDirectDocReturn docReturn = documentation.returnMethod();
            if (null != docReturn) {
                String[] properties = docReturn.properties();
                String[] descriptions = docReturn.descriptions() == null ? new String[properties.length]
                        : docReturn.descriptions();
                if (properties.length == descriptions.length) {
                    for (int i = 0; i < properties.length; i++) {
                        actionDoc.getReturnMethod().put(properties[i],
                                descriptions[i] == null ? "No description" : descriptions[i]);
                    }
                } else {
                    LogFactory.getLog(MethodInfo.class).info(
                            "Documentation: skip generation of return method properties, properties size is different from descriptions size");
                }
            }
            return actionDoc;
        }
        return this.action;
    }

    private static boolean hasValue(RequestMapping requestMapping) {
        return requestMapping != null && requestMapping.value() != null && requestMapping.value().length > 0
                && StringUtils.hasText(requestMapping.value()[0]);
    }

    private static List<ParameterInfo> buildParameterList(Class<?> clazz, Method method) {
        List<ParameterInfo> params = new ArrayList<ParameterInfo>();

        Class<?>[] parameterTypes = method.getParameterTypes();

        Method methodWithAnnotation = findMethodWithAnnotation(method, ExtDirectMethod.class);
        if (methodWithAnnotation == null) {
            methodWithAnnotation = method;
        }

        for (int paramIndex = 0; paramIndex < parameterTypes.length; paramIndex++) {
            params.add(new ParameterInfo(clazz, methodWithAnnotation, paramIndex));
        }

        return params;
    }

    public Method getMethod() {
        return this.method;
    }

    public String getForwardPath() {
        return this.forwardPath;
    }

    public HandlerMethod getHandlerMethod() {
        return this.handlerMethod;
    }

    public List<ParameterInfo> getParameters() {
        return this.parameters;
    }

    public Class<?> getCollectionType() {
        return this.collectionType;
    }

    public boolean isType(ExtDirectMethodType methodType) {
        return this.type == methodType;
    }

    public Class<?> getJsonView() {
        return this.jsonView;
    }

    public boolean isSynchronizeOnSession() {
        return this.synchronizeOnSession;
    }

    public boolean isStreamResponse() {
        return this.streamResponse;
    }

    public PollingProvider getPollingProvider() {
        return this.pollingProvider;
    }

    public Action getAction() {
        return this.action;
    }

    public String getGroup() {
        return this.group;
    }

    /**
     * Find a method that is annotated with a specific annotation. Starts with the method
     * and goes up to the superclasses of the class.
     *
     * @param method the starting method
     * @param annotation the annotation to look for
     * @return the method if there is a annotated method, else null
     */
    public static Method findMethodWithAnnotation(Method method, Class<? extends Annotation> annotation) {
        if (method.isAnnotationPresent(annotation)) {
            return method;
        }

        Class<?> cl = method.getDeclaringClass();
        while (cl != null && cl != Object.class) {
            try {
                Method equivalentMethod = cl.getDeclaredMethod(method.getName(), method.getParameterTypes());
                if (equivalentMethod.isAnnotationPresent(annotation)) {
                    return equivalentMethod;
                }
            } catch (NoSuchMethodException e) {
                // do nothing here
            }
            cl = cl.getSuperclass();
        }

        return null;
    }

}