The c:forEach fails with an "java.security.AccessControlException: access denied (java.lang.RuntimePermission accessDeclaredMembers)". This is with the nightly build from 30-Apr-2002. Environment: J2SDK 1.4, J2EE 1.3.1_02 ========= <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%@ page import="java.util.HashMap" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html> <head> <title>ForEach Map-Test</title> </head> <body> <jsp:useBean id="myMap" scope="page" class="java.util.HashMap"/> <c:set target="${myMap}" property="key1" value="value1" /> <c:set target="${myMap}" property="key2" value="value2" /> <c:set target="${myMap}" property="key3" value="value3" /> <c:forEach var="entry" items="${myMap}"> <c:set var="key" value="${entry.key}" /> <c:set var="value" value="${entry.value}" /> <c:out value="${key}" /> = <c:out value="${value}" /> </c:forEach> </body> </html> ========= java.security.AccessControlException: access denied (java.lang.RuntimePermission accessDeclaredMembers) at java.security.AccessControlContext.checkPermission(AccessControlContext.java:270) at java.security.AccessController.checkPermission(AccessController.java:401) at java.lang.SecurityManager.checkPermission(SecurityManager.java:542) at java.lang.SecurityManager.checkMemberAccess(SecurityManager.java:1662) at java.lang.Class.checkMemberAccess(Class.java:1401) at java.lang.Class.getDeclaredMethod(Class.java:1218) at org.apache.taglibs.standard.lang.jstl.BeanInfoManager.getPublicMethod(BeanInfoManager.java:376) at org.apache.taglibs.standard.lang.jstl.BeanInfoManager.getPublicMethod(BeanInfoManager.java:390) at org.apache.taglibs.standard.lang.jstl.BeanInfoManager.getPublicMethod(BeanInfoManager.java:353) at org.apache.taglibs.standard.lang.jstl.BeanInfoManager.initialize(BeanInfoManager.java:245) at org.apache.taglibs.standard.lang.jstl.BeanInfoManager.checkInitialized(BeanInfoManager.java:209) at org.apache.taglibs.standard.lang.jstl.BeanInfoManager.getProperty(BeanInfoManager.java:294) at org.apache.taglibs.standard.lang.jstl.BeanInfoManager.getBeanInfoProperty(BeanInfoManager.java:178) at org.apache.taglibs.standard.lang.jstl.ArraySuffix.evaluate(ArraySuffix.java:304) at org.apache.taglibs.standard.lang.jstl.ComplexValue.evaluate(ComplexValue.java:141) at org.apache.taglibs.standard.lang.jstl.ELEvaluator.evaluate(ELEvaluator.java:235) at org.apache.taglibs.standard.lang.jstl.ELEvaluator.evaluate(ELEvaluator.java:198) at org.apache.taglibs.standard.lang.jstl.Evaluator.evaluate(Evaluator.java:134) at org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager.evaluate(ExpressionEvaluatorManager.java:112) at org.apache.taglibs.standard.tag.el.core.ExpressionUtil.evalNotNull(ExpressionUtil.java:85) at org.apache.taglibs.standard.tag.el.core.SetTag.evaluateExpressions(SetTag.java:147) at org.apache.taglibs.standard.tag.el.core.SetTag.doStartTag(SetTag.java:95) at org.apache.jsp.test$jsp._jspService(test$jsp.java:230) at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:107) at javax.servlet.http.HttpServlet.service(HttpServlet.java:853) at org.apache.jasper.servlet.JspServlet$JspServletWrapper.service(JspServlet.java:202) at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:382) at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:474) at javax.servlet.http.HttpServlet.service(HttpServlet.java:853) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:247) at org.apache.catalina.core.ApplicationFilterChain.access$0(ApplicationFilterChain.java:197) at org.apache.catalina.core.ApplicationFilterChain$1.run(ApplicationFilterChain.java:176) at java.security.AccessController.doPrivileged(Native Method) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:172) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:243) at org.apache.catalina.core.StandardPipeline.invokeNext(StandardPipeline.java:566) at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:472) at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:943) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:201) at org.apache.catalina.core.StandardPipeline.invokeNext(StandardPipeline.java:566) at org.apache.catalina.valves.CertificatesValve.invoke(CertificatesValve.java:246) at org.apache.catalina.core.StandardPipeline.invokeNext(StandardPipeline.java:564) at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:472) at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:943) at org.apache.catalina.core.StandardContext.invoke(StandardContext.java:2343) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164) at org.apache.catalina.core.StandardPipeline.invokeNext(StandardPipeline.java:566) at org.apache.catalina.valves.ErrorDispatcherValve.invoke(ErrorDispatcherValve.java:170) at org.apache.catalina.core.StandardPipeline.invokeNext(StandardPipeline.java:564) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:170) at org.apache.catalina.core.StandardPipeline.invokeNext(StandardPipeline.java:564) at org.apache.catalina.authenticator.SingleSignOn.invoke(SingleSignOn.java:368) at org.apache.catalina.core.StandardPipeline.invokeNext(StandardPipeline.java:564) at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:472) at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:943) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:174) at org.apache.catalina.core.StandardPipeline.invokeNext(StandardPipeline.java:566) at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:472) at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:943) at org.apache.catalina.connector.http.HttpProcessor.process(HttpProcessor.java:1012) at org.apache.catalina.connector.http.HttpProcessor.run(HttpProcessor.java:1107) at java.lang.Thread.run(Thread.java:536)
I believe this is a container bug; Nathan Abramson, the implementor of the EL agrees. This works for me on Tomcat 4.0.4B2, the most recent Tomcat milestone.
By the way, it could also just be a configuration issue: can your security manager be made less restrictive?
The security settings are the standard ones of a J2EE 1.3.1 RI installation. In 1.3.1 those were made a lot tighter than before, but not unreasonable except for the fact that they don't allow writing to the servlet temp directory. BTW, I put all JSTL libraries into the lib directory of the web application, could have added them as extensions but I'd assume this scenario is not unreasonable. ===== // Standard extensions get all permissions by default grant codeBase "file:${java.home}/lib/ext/-" { permission java.security.AllPermission; }; grant codeBase "file:${java.home}/../lib/tools.jar" { permission java.security.AllPermission; }; grant codeBase "file:${com.sun.enterprise.home}/lib/-" { permission java.security.AllPermission; }; grant codeBase "file:${jms.home}/classes/" { permission java.security.AllPermission; }; // Drivers and other system classes should be stored in this // code base. grant codeBase "file:${com.sun.enterprise.home}/lib/system/-" { permission java.security.AllPermission; }; // additional permissions for EJBs grant codeBase "file:${com.sun.enterprise.home}/ejb_impls/-" { permission java.lang.RuntimePermission "queuePrintJob"; permission java.io.FilePermission "${com.sun.enterprise.home}${/}repository${/}-", "read"; }; // additional permissions for servlets grant codeBase "file:${com.sun.enterprise.home}/public_html/-" { permission java.lang.RuntimePermission "loadLibrary.*"; permission java.lang.RuntimePermission "accessClassInPackage.*"; permission java.lang.RuntimePermission "queuePrintJob"; permission java.lang.RuntimePermission "modifyThreadGroup"; permission java.io.FilePermission "<<ALL FILES>>", "read,write"; permission java.io.FilePermission "${com.sun.enterprise.home}${/}repository${/}-", "read,write,delete"; }; // additional permissions for standalone resource adapters grant codeBase "file:${com.sun.enterprise.home}/connector/adapters/-" { permission javax.security.auth.PrivateCredentialPermission "* * \"*\"", "read"; permission java.io.FilePermission "${com.sun.enterprise.home}${/}logs${/}-", "read,write"; }; // permissions for other classes grant codeBase "file:${com.sun.enterprise.home}/repository/-" { permission java.lang.RuntimePermission "loadLibrary.*"; permission java.lang.RuntimePermission "accessClassInPackage.*"; permission java.lang.RuntimePermission "queuePrintJob"; permission java.lang.RuntimePermission "modifyThreadGroup"; permission java.io.FilePermission "<<ALL FILES>>", "read,write"; permission javax.security.auth.PrivateCredentialPermission "* * \"*\"", "read"; }; // permissions for default domain grant { permission java.net.SocketPermission "*", "connect"; permission java.util.PropertyPermission "*", "read"; // workaround missing doPrivileged blocks in javamail permission java.io.FilePermission "${com.sun.enterprise.home}${/}lib${/}j2ee.jar", "read"; permission javax.security.auth.PrivateCredentialPermission "javax.resource.spi.security.PasswordCredential * \"*\"", "read"; permission javax.security.auth.PrivateCredentialPermission "javax.resource.spi.security.GenericCredential * \"*\"", "read"; };
I'm having the same problem here. I very much hope this will be fixed: the JSTL RI should pretty please work out of the box with the J2EE RI, which it currently doesn't when trying to get the 'key' or 'value' properties of Map.Entry objects from an EL expression (as sufficiently demonstrated in this bug report). The core of the problem is that the BeanInfoManager in the EL is inappropriately assuming it has full reflection permissions, and uses Class.getDeclaredMethod() without even trying Class.getMethod() first. I'll attach a patch containing a possible remedy to this problem.
Created attachment 4331 [details] Patch to make BeanInfoManager try Class.getMethod() before Class.getDeclaredMethod()
BTW, I'm not at all an expert about reflection, and the patch I've attached is quite certainly incorrect. I just wanted to make the point that with the patch applied my web-app works on J2EE-RI 1.3.1. So somehow calling getMethod() before of getDeclaredMethod(), or resorting to getMethod() when getDeclaredMethod() fails with a SecurityException, would be the right direction to go.
Created attachment 4339 [details] Improved/fixed patch
I've attached a fix that preserves the current behaviour, and only resorts to calling getMethod() when getDeclaredMethod() throws an AccessControlException. Essentially, the current functionality should continue to work in all cases, but errors like the one described in this report should be fixed.
I believe the patch you've submitted is too general; note that if the declared methods fail, we check methods declared through the interface and superclass (recursively). The solution, as far as my analysis suggests, is simply to ignore security-related exceptions received while focusing on the declared methods of the present class (as distinct from its interfaces and parent classes). Let me know if that more restricted change would work for you (simply a try { ... } catch (AccessControlException ex) { // no methods here; pass through to next check } ). If it does, and once I think about it some more, I'll change the EL code.
This does not work. The problem as I understand it is that however far recursion goes, or whether it's searching interfaces and super-classes, the code *always* calls getDeclaredMembers(), and that is simply not allowed under J2EE-RI 1.3.1 (thanks to a denied 'java.lang.RuntimePermission accessDeclaredMembers'). At some place, the routine would need to resort to a simple getMethod(), which will work without problems under the described restrictions. I guess I don't quite understand why the BeanInfoManger.getPublicMethod needs to call getDeclaredMethod() in all cases... however I'll do some more experimenting.
Okay, some more research... I actually believe the patch I proposed is pretty okay. I've inserted print statements all over the place in the GetPublicMethod() methods of BeanInfoManager, and this is what I get when trying to access the 'key' property of a HashMap entry: > getPublicMethod(getKey) Declaring class is java.util.HashMap$Entry Class modifiers are 'private static' > getPublicMethod(class java.util.HashMap$Entry, public java.lang.Object java.util.HashMap$Entry.getKey()) See if this is a public class declaring the method [no] Searching interfaces Searching interface java.util.Map$Entry > getPublicMethod(interface java.util.Map$Entry, public java.lang.Object java.util.HashMap$Entry.getKey()) See if this is a public class declaring the method [yes] Trying with getDeclaredMethod() [my patch:] AccessControlException -> trying a simple getMethod() [success] Found method 'getKey' Found method 'getKey' [indentation and comments in array notation added manually for better clarity] What I've learned from this: BeanInfoManager delegates tothe recursive getPublicMethod(Class,Method) because the class HashMap$Entry is private. Correctly, it looks for the interfaces/superclasses, and finds java.util.Map$Entry. The interface is public, but now the lookup of the 'getKey' method fails because we don't have permissions to 'accessDeclaredMembers'... IMHO this is a good place to try a simple Class.getMethod(). I have been playing with several alternatives, but none of them seem as clean as what I proposed in the last patch (sorry it's marked as text/plain BTW, should have been tar-gzip). If you have any concerns, I'd really be interested in a scenario where the proposed change would alter behaviour for the worse. BTW, I'm testing this change with a fairly large web-app, and everything is working as expected (though I might not have any real weirdo-beans flying around ;-) ).
Okay, you've convinced me. The JSTL spec doesn't specifically ask for getDeclaredMethod(); I'm actually now thinking of simply replacing the call to getDeclaredMethod() with one to getMethod(). But for now, I've adopted a slightly modified version of your patch that implements the logic you're suggesting: drop back to getMethod() on an AccessControlException. Thanks for the report, and let me know if you continue to experience problems.
This change works for me. Agree it's not really elegant :-P