All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kohsuke.stapler.Function Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2004-2010, Kohsuke Kawaguchi
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
 * that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice, this list of
 *       conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.kohsuke.stapler;

import com.google.common.collect.MapMaker;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Map;

/**
 * Abstracts the difference between normal instance methods and
 * static duck-typed methods.
 *
 * @author Kohsuke Kawaguchi
 */
abstract class Function {
    /**
     * Gets the method name.
     */
    abstract String getName();

    /**
     * Gets the human readable name of this function. Used to assist debugging.
     */
    abstract String getDisplayName();

    /**
     * Gets "className.methodName"
     */
    abstract String getQualifiedName();

    /**
     * Gets the type of parameters in a single array.
     */
    abstract Class[] getParameterTypes();

    abstract Type[] getGenericParameterTypes();

    /**
     * Gets the annotations on parameters.
     */
    abstract Annotation[][] getParameterAnnotations();

    /**
     * Gets the list of parameter names.
     */
    abstract String[] getParameterNames();

    /**
     * Return type of the method.
     */
    abstract Class getReturnType();

    /**
     * Calls {@link #bindAndInvoke(Object, StaplerRequest, StaplerResponse, Object...)} and then
     * optionally serve the response object.
     */
    void bindAndInvokeAndServeResponse(Object node, RequestImpl req, ResponseImpl rsp, Object... headArgs) throws IllegalAccessException, InvocationTargetException, ServletException, IOException {
        try {
            Object r = bindAndInvoke(node, req, rsp, headArgs);
            if (getReturnType()!=void.class)
                renderResponse(req,rsp,node, r);
        } catch (InvocationTargetException e) {
            // exception as an HttpResponse
            Throwable te = e.getTargetException();
            if (!renderResponse(req,rsp,node,te))
                throw e;    // unprocessed exception
        }
    }

    private boolean renderResponse(RequestImpl req, ResponseImpl rsp, Object node, Object ret) throws IOException, ServletException {
        for (HttpResponseRenderer r : req.stapler.getWebApp().getResponseRenderers())
            if (r.generateResponse(req,rsp,node,ret))
                return true;
        return false;
    }

    /**
     * Use the given arguments as the first N arguments,
     * then figure out the rest of the arguments by looking at parameter annotations,
     * then finally call {@link #invoke}.
     */
    Object bindAndInvoke(Object o, StaplerRequest req, StaplerResponse rsp, Object... headArgs) throws IllegalAccessException, InvocationTargetException, ServletException {
        Class[] types = getParameterTypes();
        Annotation[][] annotations = getParameterAnnotations();
        String[] parameterNames = getParameterNames();

        Object[] arguments = new Object[types.length];

        // fill in the first N arguments
        System.arraycopy(headArgs,0,arguments,0,headArgs.length);

        try {
            // find the rest of the arguments. either known types, or with annotations
            for( int i=headArgs.length; i PARSE_METHODS;
    private static final Function RETURN_NULL;

    static {
        try {
            RETURN_NULL = new StaticFunction(Function.class.getMethod("returnNull"));
        } catch (NoSuchMethodException e) {
            throw new AssertionError(e);    // impossible
        }

        PARSE_METHODS = new MapMaker().weakKeys().makeComputingMap(new com.google.common.base.Function() {
            public Function apply(Class from) {
                // MethdFunction for invoking a static method as a static method
                FunctionList methods = new ClassDescriptor(from).methods.name("fromStapler");
                switch (methods.size()) {
                case 0: return RETURN_NULL;
                default:
                    throw new IllegalArgumentException("Too many 'fromStapler' methods on "+from);
                case 1:
                    Method m = ((MethodFunction)methods.get(0)).m;
                    return new MethodFunction(m) {
                        @Override
                        Class[] getParameterTypes() {
                            return m.getParameterTypes();
                        }

                        @Override
                        Type[] getGenericParameterTypes() {
                            return m.getGenericParameterTypes();
                        }

                        @Override
                        Annotation[][] getParameterAnnotations() {
                            return m.getParameterAnnotations();
                        }

                        @Override
                        Object invoke(HttpServletRequest req, Object o, Object... args) throws IllegalAccessException, InvocationTargetException {
                            return m.invoke(null,args);
                        }
                    };
                }
            }
        });
    }

    public static Object returnNull() { return null; }

    /**
     * Invokes the method.
     */
    abstract Object invoke(HttpServletRequest req, Object o, Object... args) throws IllegalAccessException, InvocationTargetException;

    final Function protectBy(Method m) {
        try {
            LimitedTo a = m.getAnnotation(LimitedTo.class);
            if(a==null)
                return this;    // not protected
            else
                return new ProtectedFunction(this,a.value());
        } catch (LinkageError e) {
            // running in JDK 1.4
            return this;
        }
    }

    public abstract  A getAnnotation(Class annotation);

    private abstract static class MethodFunction extends Function {
        protected final Method m;
        private volatile String[] names;

        public MethodFunction(Method m) {
            this.m = m;
        }

        public final String getName() {
            return m.getName();
        }

        final String getDisplayName() {
            return m.toGenericString();
        }

        @Override
        String getQualifiedName() {
            return m.getDeclaringClass().getName()+'.'+getName();
        }

        public final  A getAnnotation(Class annotation) {
            return m.getAnnotation(annotation);
        }

        final String[] getParameterNames() {
            if(names==null)
                names = ClassDescriptor.loadParameterNames(m);
            return names;
        }

        @Override
        Class getReturnType() {
            return m.getReturnType();
        }
    }
    /**
     * Normal instance methods.
     */
    static final class InstanceFunction extends MethodFunction {
        public InstanceFunction(Method m) {
            super(m);
        }

        public Class[] getParameterTypes() {
            return m.getParameterTypes();
        }

        @Override
        Type[] getGenericParameterTypes() {
            return m.getGenericParameterTypes();
        }

        Annotation[][] getParameterAnnotations() {
            return m.getParameterAnnotations();
        }

        public Object invoke(HttpServletRequest req, Object o, Object... args) throws IllegalAccessException, InvocationTargetException {
            return m.invoke(o,args);
        }
    }

    /**
     * Static methods on the wrapper type.
     */
    static final class StaticFunction extends MethodFunction {
        public StaticFunction(Method m) {
            super(m);
        }

        public Class[] getParameterTypes() {
            Class[] p = m.getParameterTypes();
            Class[] r = new Class[p.length-1];
            System.arraycopy(p,1,r,0,r.length);
            return r;
        }

        @Override
        Type[] getGenericParameterTypes() {
            Type[] p = m.getGenericParameterTypes();
            Type[] r = new Type[p.length-1];
            System.arraycopy(p,1,r,0,r.length);
            return r;
        }

        Annotation[][] getParameterAnnotations() {
            Annotation[][] a = m.getParameterAnnotations();
            Annotation[][] r = new Annotation[a.length-1][];
            System.arraycopy(a,1,r,0,r.length);
            return r;
        }

        public Object invoke(HttpServletRequest req, Object o, Object... args) throws IllegalAccessException, InvocationTargetException {
            Object[] r = new Object[args.length+1];
            r[0] = o;
            System.arraycopy(args,0,r,1,args.length);
            return m.invoke(null,r);
        }
    }

    /**
     * Function that's protected by the role access check.
     */
    static final class ProtectedFunction extends Function {
        private final String role;
        private final Function core;

        public ProtectedFunction(Function core, String role) {
            this.role = role;
            this.core = core;
        }

        public String getName() {
            return core.getName();
        }

        String getDisplayName() {
            return core.getDisplayName();
        }

        @Override
        String getQualifiedName() {
            return core.getQualifiedName();
        }

        public Class[] getParameterTypes() {
            return core.getParameterTypes();
        }

        @Override
        Class getReturnType() {
            return core.getReturnType();
        }

        @Override
        Type[] getGenericParameterTypes() {
            return core.getGenericParameterTypes();
        }

        Annotation[][] getParameterAnnotations() {
            return core.getParameterAnnotations();
        }

        String[] getParameterNames() {
            return core.getParameterNames();
        }

        public Object invoke(HttpServletRequest req, Object o, Object... args) throws IllegalAccessException, InvocationTargetException {
            if(req.isUserInRole(role))
                return core.invoke(req, o, args);
            else
                throw new IllegalAccessException("Needs to be in role "+role);
        }

        public  A getAnnotation(Class annotation) {
            return core.getAnnotation(annotation);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy