org.springframework.batch.item.adapter.AbstractMethodInvokingDelegator.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.batch.item.adapter.AbstractMethodInvokingDelegator.java

Source

/*
 * Copyright 2006-2018 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
 *
 *      https://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.springframework.batch.item.adapter;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MethodInvoker;

/**
 * Superclass for delegating classes which dynamically call a custom method of
 * injected object. Provides convenient API for dynamic method invocation
 * shielding subclasses from low-level details and exception handling.
 *
 * {@link Exception}s thrown by a successfully invoked delegate method are
 * re-thrown without wrapping. In case the delegate method throws a
 * {@link Throwable} that doesn't subclass {@link Exception} it will be wrapped
 * by {@link InvocationTargetThrowableWrapper}.
 *
 * @author Robert Kasanicky
 * @author Mahmoud Ben Hassine
 */
public abstract class AbstractMethodInvokingDelegator<T> implements InitializingBean {

    private Object targetObject;

    private String targetMethod;

    private Object[] arguments;

    /**
     * Invoker the target method with arguments set by
     * {@link #setArguments(Object[])}.
     *
     * @return object returned by invoked method
     *
     * @throws Exception exception thrown when executing the delegate method.
     */
    protected T invokeDelegateMethod() throws Exception {
        MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
        invoker.setArguments(arguments);
        return doInvoke(invoker);
    }

    /**
     * Invokes the target method with given argument.
     *
     * @param object argument for the target method
     * @return object returned by target method
     *
     * @throws Exception exception thrown when executing the delegate method.
     */
    protected T invokeDelegateMethodWithArgument(Object object) throws Exception {
        MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
        invoker.setArguments(new Object[] { object });
        return doInvoke(invoker);
    }

    /**
     * Invokes the target method with given arguments.
     *
     * @param args arguments for the invoked method
     * @return object returned by invoked method
     *
     * @throws Exception exception thrown when executing the delegate method.
     */
    protected T invokeDelegateMethodWithArguments(Object[] args) throws Exception {
        MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
        invoker.setArguments(args);
        return doInvoke(invoker);
    }

    /**
     * Create a new configured instance of {@link MethodInvoker}.
     */
    private MethodInvoker createMethodInvoker(Object targetObject, String targetMethod) {
        HippyMethodInvoker invoker = new HippyMethodInvoker();
        invoker.setTargetObject(targetObject);
        invoker.setTargetMethod(targetMethod);
        invoker.setArguments(arguments);
        return invoker;
    }

    /**
     * Prepare and invoke the invoker, rethrow checked exceptions as unchecked.
     * @param invoker configured invoker
     * @return return value of the invoked method
     */
    @SuppressWarnings("unchecked")
    private T doInvoke(MethodInvoker invoker) throws Exception {
        try {
            invoker.prepare();
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            throw new DynamicMethodInvocationException(e);
        }

        try {
            return (T) invoker.invoke();
        } catch (InvocationTargetException e) {
            if (e.getCause() instanceof Exception) {
                throw (Exception) e.getCause();
            } else {
                throw new InvocationTargetThrowableWrapper(e.getCause());
            }
        } catch (IllegalAccessException e) {
            throw new DynamicMethodInvocationException(e);
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(targetObject, "targetObject must not be null");
        Assert.hasLength(targetMethod, "targetMethod must not be empty");
        Assert.state(targetClassDeclaresTargetMethod(),
                "target class must declare a method with matching name and parameter types");
    }

    /**
     * @return true if target class declares a method matching target method
     * name with given number of arguments of appropriate type.
     */
    private boolean targetClassDeclaresTargetMethod() {
        MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);

        Method[] memberMethods = invoker.getTargetClass().getMethods();
        Method[] declaredMethods = invoker.getTargetClass().getDeclaredMethods();

        List<Method> allMethods = new ArrayList<>();
        allMethods.addAll(Arrays.asList(memberMethods));
        allMethods.addAll(Arrays.asList(declaredMethods));

        String targetMethodName = invoker.getTargetMethod();

        for (Method method : allMethods) {
            if (method.getName().equals(targetMethodName)) {
                Class<?>[] params = method.getParameterTypes();
                if (arguments == null) {
                    // don't check signature, assume arguments will be supplied
                    // correctly at runtime
                    return true;
                }
                if (arguments.length == params.length) {
                    boolean argumentsMatchParameters = true;
                    for (int j = 0; j < params.length; j++) {
                        if (arguments[j] == null) {
                            continue;
                        }
                        if (!(ClassUtils.isAssignableValue(params[j], arguments[j]))) {
                            argumentsMatchParameters = false;
                        }
                    }
                    if (argumentsMatchParameters) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    /**
     * @param targetObject the delegate - bean id can be used to set this value
     * in Spring configuration
     */
    public void setTargetObject(Object targetObject) {
        this.targetObject = targetObject;
    }

    /**
     * @param targetMethod name of the method to be invoked on
     * {@link #setTargetObject(Object)}.
     */
    public void setTargetMethod(String targetMethod) {
        this.targetMethod = targetMethod;
    }

    /**
     * @param arguments arguments values for the {
     * {@link #setTargetMethod(String)}. These will be used only when the
     * subclass tries to invoke the target method without providing explicit
     * argument values.
     *
     * If arguments are set to not-null value {@link #afterPropertiesSet()} will
     * check the values are compatible with target method's signature. In case
     * arguments are null (not set) method signature will not be checked and it
     * is assumed correct values will be supplied at runtime.
     */
    public void setArguments(Object[] arguments) {
        this.arguments = arguments == null ? null : Arrays.asList(arguments).toArray();
    }

    /**
     * Return arguments.
     * @return arguments
     */
    protected Object[] getArguments() {
        return arguments;
    }

    /**
     * Used to wrap a {@link Throwable} (not an {@link Exception}) thrown by a
     * reflectively-invoked delegate.
     *
     * @author Robert Kasanicky
     */
    @SuppressWarnings("serial")
    public static class InvocationTargetThrowableWrapper extends RuntimeException {

        public InvocationTargetThrowableWrapper(Throwable cause) {
            super(cause);
        }

    }
}