2

I recognize this might be an XY Problem, so I'll explain the context in case there's a better solution further up the chain than what I'm trying to do.

I am trying to add the ability to access a class's static methods from within EL expressions in a JSP.

My solution so far is to instantiate the specified class and put it on the page (or specified) scope. Since you can access static methods from an instance, it seemed like the simplest and most straightforward solution. I have written a tag class for this purpose, StaticMethodAccessTag, and added it to one of our tld's as constants:staticMethodAccess.

The problem with this solution is that the non-static methods can also be called, with all the potential for problems and abuse that that means.

Is there a way to use reflection to instantiate an instance of a class, while preventing (eg by throwing an exception) any of the non-static methods from being called?

Or better yet, is there a way to "call" any arbitrarily-named and -parametered method through EL and have one java method handle all those calls? Something like ${MyStaticUtil.foo(bar1, bar2)} and ${MyStaticUtil.baz(bar1)} where both would come into StaticMethodAccessTag.processArbitraryMethod(String methodName, Object... args) which could then use reflection to call the appropriate static method on MyStaticUtil.

Or better yet, is there a way to call static methods in EL without needing an instance of a class to call it through?


StaticMethodAccessTag.java:

import java.lang.reflect.Constructor;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

import org.apache.log4j.Logger;

public class StaticMethodAccessTag extends SimpleTagSupport {

    private static Logger log = Logger.getLogger(StaticMethodAccessTag.class);

    private String varName;
    private String className;
    private String scope;
    private Object[] args;

    @Override
    public void doTag() throws JspException {
        try {
            if (scope == null || "".equals(scope)) {
                scope = "page";
            }
            int requestedScope = TagUtils.getScope(scope);

            Class<?> declaringClass = Class.forName(this.className);
            Object instance = null;
            Class<?>[] argTypes = null;

            if (args == null || args.length == 0) {
                // no args; use default constructor
                instance = declaringClass.newInstance();
            } else {
                // args provided, find the matching constructor and use it
                argTypes = new Class<?>[args.length];
                for (int i = 0; i < args.length; i++) {
                    if (args[i] == null) {
                        // unknown type, assume Object. This can lead to ambiguity when picking a constructor.
                        argTypes[i] = Object.class;
                    } else {
                        argTypes[i] = args[i].getClass();
                    }
                }

                // check each constructor
                Constructor<?> matchedConstructor = null;
                nextConstructor: for (Constructor<?> constructor : declaringClass.getConstructors()) {
                    Class<?>[] expectedTypes = constructor.getParameterTypes();

                    // check the provided arguments against the current constructor
                    if (expectedTypes == null || expectedTypes.length != args.length) {
                        // mismatch; move on to the next constructor
                        continue;
                    }
                    for (int i = 0; i < argTypes.length; i++) {
                        if (!argTypes[i].isAssignableFrom(expectedTypes[i])) {
                            // mismatch; move on to the next constructor
                            continue nextConstructor;
                        }
                    }

                    // if another match was already found, that means we have ambiguous arguments.
                    if (matchedConstructor != null) {
                        outputArgs(argTypes);
                        throw new Exception("Given the provided arguments, there are multiple eligible constructors for " + declaringClass.getName());
                    }

                    matchedConstructor = constructor;
                }

                if (matchedConstructor != null) {
                    instance = matchedConstructor.newInstance(args);
                }
            }

            if (instance == null) {
                outputArgs(argTypes);
                throw new NullPointerException("Failed to find a matching constructor for the provided args for " + this.className + ".");
            }

            getJspContext().setAttribute(varName, instance, requestedScope);
        } catch (Exception e) {
            // TODO output jsp name, other helpful information, if possible.
            throw new JspException("Exception setting up static method access for " + this.className, e);
        }
    }

    private void outputArgs(Class<?>[] argTypes) {
        log.debug("Provided " + args.length + " arguments: ");
        for (int i = 0; i < argTypes.length; i++) {
            String argStr = "null";
            if (args[i] != null) {
                argStr = args[i].toString();
            }
            log.debug("[" + i + "] expected type: " + argTypes[i].getName() + " toString: " + argStr);
        }
    }

    public String getVar() {
        return varName;
    }

    public void setVar(String varName) {
        this.varName = varName;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public Object[] getArgs() {
        return args;
    }

    public void setArgs(Object... args) {
        this.args = args;
    }
}

Tag definition in constants.tld:

<tag>
    <name>staticMethodAccess</name>
    <tagclass>foopackage.presentation.resource.tag.StaticMethodAccessTag</tagclass>
    <bodycontent>empty</bodycontent>
    <attribute>
        <name>className</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
        <name>var</name>
        <required>true</required>
        <rtexprvalue>false</rtexprvalue>
    </attribute>
    <attribute>
        <name>scope</name>
        <required>false</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
        <name>args</name>
        <required>false</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
</tag>

Example usage in a JSP:

<%@ taglib prefix="constants" uri="/constants" %>

<%-- debug, delete this block --%>
<constants:staticMethodAccess var="MyJSPUtils" className="foopackage.presentation.resource.tag.MyJSPUtils"/>
<c:forEach items="${MyJSPUtils.args('one', 'two', 'three')}" var="v">
    ${v};
</c:forEach>
<constants:staticMethodAccess var="type1" className="foopackage.integration.value.Type" args="${jspUtils.args(DBConstants.STATUS_TYPE_ID_ACTIVE)}"/>
<constants:staticMethodAccess var="type2" className="foopackage.integration.value.Type" args="${jspUtils.args(DBConstants.STATUS_TYPE_ID_ACTIVE, 'Active')}"/>
<constants:staticMethodAccess var="type3" className="foopackage.integration.value.Type" args="${jspUtils.args(type1)}"/>
<constants:staticMethodAccess var="type4" className="foopackage.integration.value.Type" args="${jspUtils.args(DBConstants.STATUS_TYPE_ID_ACTIVE, 'Active', 'desc')}"/>
<constants:staticMethodAccess var="type5" className="foopackage.integration.value.Type" args="${jspUtils.args(DBConstants.STATUS_TYPE_ID_ACTIVE, null, 'desc')}"/>
<constants:staticMethodAccess var="type3" className="foopackage.integration.value.Type" args="${jspUtils.args(null)}"/><%-- this line will throw an error because it matches 2 possible constructors --%>

<br/>${type1.id} ${type1.name} ${type1.description}
<br/>${type2.id} ${type2.name} ${type2.description}
<br/>${type3.id} ${type3.name} ${type3.description}
<br/>${type4.id} ${type4.name} ${type4.description}
<br/>${type5.id} ${type5.name} ${type5.description}
<br/>
<%-- end debug block --%>
Community
  • 1
  • 1
Mar
  • 7,765
  • 9
  • 48
  • 82

1 Answers1

0

I think you're looking for the modifiers functionality used in Java reflection.

Is there a way to use reflection to instantiate an instance of a class, while preventing (eg by throwing an exception) any of the non-static methods from being called?

If you only want to call static methods, why do you need to instantiate the class? You could do something like the following, for example.

public Object callOnlyStaticMethods(String name, Class [] parameterTypes, Object [] parameters) throws Exception {

    // use getDeclaredMethod and setAccessible if you want to call non-public methods
    Method method =  this.getClass().getMethod(name, parameterTypes);
    if( ! Modifier.isStatic(method.getModifiers()) ) {
        throw new UnsupportedOperationException("Only static methods may be called, " + method.getName() + " is not static!");
    }
    return method.invoke(null, parameters);
}

Or better yet, is there a way to "call" any arbitrarily-named and -parametered method through EL and have one java method handle all those calls? Something like ${MyStaticUtil.foo(bar1, bar2)} and ${MyStaticUtil.baz(bar1)} where both would come into StaticMethodAccessTag.processArbitraryMethod(String methodName, Object... args) which could then use reflection to call the appropriate static method on MyStaticUtil.

In this case, the best solution for this is the Proxy/InvocationHandler functionality in Java. However, in order to do that, you need to create an interface for your StaticMethodAccessTag class -- and then move your static methods to that interface.

(Well, I hope you're using Java 8, because then you can do that -- otherwise, create non-static methods that call the static methods?) For example:

public class MyStaticUtil implements InvocationHandler {

    /* (non-Javadoc)
     * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // it's just a lot easier if all method names in StaticMethodAccessTag are unique..
        String methodName = method.getName();

        Method methodToCall = null;
        for( Method realMethod : StaticMethodAccessTagInterface.class.getMethods() ) {
           if( realMethod.getName().equals(methodName) )  {
               methodToCall = realMethod;
               break;
           }
        }
        // check if the method has actually been found or not, otherwise possible NPE's..

        return methodToCall.invoke(null, args);
    }

    private final static MyStaticUtil _instance = new MyStaticUtil();

    public static Object getInstance() {
        Class [] proxyInterfaces = {
          StaticMethodAccessTagInterface.class
        };
        return Proxy.newProxyInstance(
                MyStaticUtil.class.getClassLoader(),
                proxyInterfaces,
                _instance);

    }

}

Or better yet, is there a way to call static methods in EL without needing an instance of a class to call it through?

Good question: it looks like it's already been answered: How to call a static method in JSP/EL?

Community
  • 1
  • 1
Marco
  • 8,958
  • 1
  • 36
  • 56