It is possible to run, at least, a simple Struts 2 application on GAE with a little work. Namely, you need to tell OGNL to not do security manager permission checks, which will fail since GAE has a security manager and you don't have the ability to add the OGNL-specific permissions. Therefore, somewhere in your initialization code, add this:

OgnlRuntime.setSecurityManager(null);

The easiest place for this is in a Servlet context listener, executing when the context is initialized.

For Struts 2.1.8, Jeromy Evans did some work to get things running on GAE. The following post on the mailing list reports both the issues and the workarounds.

Issues when deploying on GAE

Jeromy Evans: I've put in some more effort to get Struts 2.1.8 snapshot running in live GAE environment. I made progress moving to Sitemesh 2.4.2, disabling OgnlRuntime SecurityManager and moving to convention-2.1.8-SNAPSHOT (WW-3114)

The next issue is that XWorks XMLConfigurationProvider throws a SecurityException on the following line (211):

// Force loading of class to detect no class def found exceptions
cimpl.getDeclaredConstructors();

SecurityException: Unable to get members for o.a.s.c.PackageBasedActionConfigBuilder

cimpl is the Class of the ActionConfigBuilder bean specified in struts-plugin.xml and loaded by ClassLoaderTools. I believe this is a technique to eagerly load the class.

I'm not sure why that's access is not permitted in the sandbox. It's not documented anywhere I can see and It only occurs in the live environment.

As a work-around (guess), I changed it to cimpl.getDeclaredClasses() instead which is permitted (I don't know if this has the same effect on the ClassLoader). That got me past the issue above, but the same SecurityException occurs in XWork's ContainerImpl$ConstructorInjector.findConstructorIn():
SecurityException: Unable to get members for Class o.a.s.v.v.VelocityManager

which seems to be a fundamental problem with XWork's constructor injection in GAE.

As a side note, Guice 2's constructor injection works okay in GAE. I imagine the Guice 2 codebase is now very different beast than XWork's IOC though.

I guess I need to approach the GAE forum now. Has anyone got any ideas about what to attempt next on the S2/Xwork side? Stack traces for the two cases are below.

 
Failed startup of context com.google.apphosting.utils.jetty.RuntimeAppEngineWebAppContext@67fe80{/,/base/data/home/apps/{appname}/1.335834217966711427}
Unable to load configuration. - bean - jar:file:/base/data/home/apps/{appname}/1.335834217966711427/WEB-INF/lib/struts2-convention-plugin-2.1.8-SNAPSHOT.jar!/struts-plugin.xml:32:155
       at org.apache.struts2.dispatcher.Dispatcher.init(Dispatcher.java:431)
       at org.apache.struts2.dispatcher.ng.InitOperations.initDispatcher(InitOperations.java:69)
       at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareFilter.init(StrutsPrepareFilter.java:50)
       at com.google.inject.servlet.FilterDefinition.init(FilterDefinition.java:81)
       at com.google.inject.servlet.ManagedFilterPipeline.initPipeline(ManagedFilterPipeline.java:102)
       at com.google.inject.servlet.GuiceFilter.init(GuiceFilter.java:168)
       at org.mortbay.jetty.servlet.FilterHolder.doStart(FilterHolder.java:99)
       at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:40)
       at org.mortbay.jetty.servlet.ServletHandler.initialize(ServletHandler.java:589)
       at org.mortbay.jetty.servlet.Context.startContext(Context.java:139)
       at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1218)
       at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:500)
       at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:448)
       at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:40)
       at com.google.apphosting.runtime.jetty.AppVersionHandlerMap.createHandler(AppVersionHandlerMap.java:190)
       at com.google.apphosting.runtime.jetty.AppVersionHandlerMap.getHandler(AppVersionHandlerMap.java:167)
       at com.google.apphosting.runtime.jetty.JettyServletEngineAdapter.serviceRequest(JettyServletEngineAdapter.java:127)
       at com.google.apphosting.runtime.JavaRuntime.handleRequest(JavaRuntime.java:235)
       at com.google.apphosting.base.RuntimePb$EvaluationRuntime$6.handleBlockingRequest(RuntimePb.java:4823)
       at com.google.apphosting.base.RuntimePb$EvaluationRuntime$6.handleBlockingRequest(RuntimePb.java:4821)
       at com.google.net.rpc.impl.BlockingApplicationHandler.handleRequest(BlockingApplicationHandler.java:24)
       at com.google.net.rpc.impl.RpcUtil.runRpcInApplication(RpcUtil.java:359)
       at com.google.net.rpc.impl.Server$2.run(Server.java:820)
       at com.google.tracing.LocalTraceSpanRunnable.run(LocalTraceSpanRunnable.java:56)
       at com.google.tracing.LocalTraceSpanBuilder.internalContinueSpan(LocalTraceSpanBuilder.java:516)
       at com.google.net.rpc.impl.Server.startRpc(Server.java:775)
       at com.google.net.rpc.impl.Server.processRequest(Server.java:348)
       at com.google.net.rpc.impl.ServerConnection.messageReceived(ServerConnection.java:436)
       at com.google.net.rpc.impl.RpcConnection.parseMessages(RpcConnection.java:319)
       at com.google.net.rpc.impl.RpcConnection.dataReceived(RpcConnection.java:290)
       at com.google.net.async.Connection.handleReadEvent(Connection.java:428)
       at com.google.net.async.EventDispatcher.processNetworkEvents(EventDispatcher.java:762)
       at com.google.net.async.EventDispatcher.internalLoop(EventDispatcher.java:207)
       at com.google.net.async.EventDispatcher.loop(EventDispatcher.java:101)
       at com.google.net.rpc.RpcService.runUntilServerShutdown(RpcService.java:251)
       at com.google.apphosting.runtime.JavaRuntime$RpcRunnable.run(JavaRuntime.java:374)
       at java.lang.Thread.run(Unknown Source)
Caused by: Unable to load configuration. - bean - jar:file:/base/data/home/apps/{appname}/1.335834217966711427/WEB-INF/lib/struts2-convention-plugin-2.1.8-SNAPSHOT.jar!/struts-plugin.xml:32:155
       at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:58)
       at org.apache.struts2.dispatcher.Dispatcher.init_PreloadConfiguration(Dispatcher.java:374)
       at org.apache.struts2.dispatcher.Dispatcher.init(Dispatcher.java:418)
       ... 36 more
Caused by: Unable to load bean: type:org.apache.struts2.convention.ActionConfigBuilder class:org.apache.struts2.convention.PackageBasedActionConfigBuilder - bean - jar:file:/base/data/home/apps/{appname}/1.335834217966711427/WEB-INF/lib/struts2-convention-plugin-2.1.8-SNAPSHOT.jar!/struts-plugin.xml:32:155
       at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.register(XmlConfigurationProvider.java:222)
       at org.apache.struts2.config.StrutsXmlConfigurationProvider.register(StrutsXmlConfigurationProvider.java:101)
       at com.opensymphony.xwork2.config.impl.DefaultConfiguration.reloadContainer(DefaultConfiguration.java:165)
       at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:55)
       ... 38 more
Caused by: java.lang.SecurityException: Unable to get members for class org.apache.struts2.convention.PackageBasedActionConfigBuilder
       at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_$10.run(Class_.java:357)
       at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_$10.run(Class_.java:347)
       at java.security.AccessController.doPrivileged(Native Method)
       at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_.getMembers(Class_.java:347)
       at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_.getDeclaredConstructors(Class_.java:192)
       at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.register(XmlConfigurationProvider.java:212)
       ... 41 more

— case two —

 
{
[{appname}/1.335835094531171610].<stdout>: 642  [Runtime Network Thread] ERROR org.apache.struts2.dispatcher.Dispatcher  - Dispatcher initialization failed
java.lang.RuntimeException: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.SecurityException: Unable to get members for class org.apache.struts2.views.velocity.VelocityManager
       at com.opensymphony.xwork2.inject.ContainerImpl$MethodInjector.inject(ContainerImpl.java:295)
       at com.opensymphony.xwork2.inject.ContainerImpl$2.call(ContainerImpl.java:104)
       at com.opensymphony.xwork2.inject.ContainerImpl$2.call(ContainerImpl.java:102)
       at com.opensymphony.xwork2.inject.ContainerImpl.callInContext(ContainerImpl.java:574)
       at com.opensymphony.xwork2.inject.ContainerImpl.injectStatics(ContainerImpl.java:101)
       at com.opensymphony.xwork2.inject.ContainerBuilder.create(ContainerBuilder.java:493)
       at com.opensymphony.xwork2.config.impl.DefaultConfiguration.reloadContainer(DefaultConfiguration.java:184)
       at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:55)
       at org.apache.struts2.dispatcher.Dispatcher.init_PreloadConfiguration(Dispatcher.java:374)
       at org.apache.struts2.dispatcher.Dispatcher.init(Dispatcher.java:418)
       at org.apache.struts2.dispatcher.ng.InitOperations.initDispatcher(InitOperations.java:69)
       at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareFilter.init(StrutsPrepareFilter.java:50)
       at com.google.inject.servlet.FilterDefinition.init(FilterDefinition.java:81)
       at com.google.inject.servlet.ManagedFilterPipeline.initPipeline(ManagedFilterPipeline.java:102)
       at com.google.inject.servlet.GuiceFilter.init(GuiceFilter.java:168)
       at org.mortbay.jetty.servlet.FilterHolder.doStart(FilterHolder.java:99)
       at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:40)
       at org.mortbay.jetty.servlet.ServletHandler.initialize(ServletHandler.java:589)
       at org.mortbay.jetty.servlet.Context.startContext(Context.java:139)
       at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1218)
       at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:500)
       at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:448)
       at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:40)
       at com.google.apphosting.runtime.jetty.AppVersionHandlerMap.createHandler(AppVersionHandlerMap.java:190)
       at com.google.apphosting.runtime.jetty.AppVersionHandlerMap.getHandler(AppVersionHandlerMap.java:167)
       at com.google.apphosting.runtime.jetty.JettyServletEngineAdapter.serviceRequest(JettyServletEngineAdapter.java:127)
       at com.google.apphosting.runtime.JavaRuntime.handleRequest(JavaRuntime.java:235)
       at com.google.apphosting.base.RuntimePb$EvaluationRuntime$6.handleBlockingRequest(RuntimePb.java:4823)
       at com.google.apphosting.base.RuntimePb$EvaluationRuntime$6.handleBlockingRequest(RuntimePb.java:4821)
       at com.google.net.rpc.impl.BlockingApplicationHandler.handleRequest(BlockingApplicationHandler.java:24)
       at com.google.net.rpc.impl.RpcUtil.runRpcInApplication(RpcUtil.java:359)
       at com.google.net.rpc.impl.Server$2.run(Server.java:820)
       at com.google.tracing.LocalTraceSpanRunnable.run(LocalTraceSpanRunnable.java:56)
       at com.google.tracing.LocalTraceSpanBuilder.internalContinueSpan(LocalTraceSpanBuilder.java:516)
       at com.google.net.rpc.impl.Server.startRpc(Server.java:775)
       at com.google.net.rpc.impl.Server.processRequest(Server.java:348)
       at com.google.net.rpc.impl.ServerConnection.messageReceived(ServerConnection.java:436)
       at com.google.net.rpc.impl.RpcConnection.parseMessages(RpcConnection.java:319)
       at com.google.net.rpc.impl.RpcConnection.dataReceived(RpcConnection.java:290)
       at com.google.net.async.Connection.handleReadEvent(Connection.java:428)
       at com.google.net.async.EventDispatcher.processNetworkEvents(EventDispatcher.java:762)
       at com.google.net.async.EventDispatcher.internalLoop(EventDispatcher.java:207)
       at com.google.net.async.EventDispatcher.loop(EventDispatcher.java:101)
       at com.google.net.rpc.RpcService.runUntilServerShutdown(RpcService.java:251)
       at com.google.apphosting.runtime.JavaRuntime$RpcRunnable.run(JavaRuntime.java:374)
       at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.SecurityException: Unable to get members for class org.apache.struts2.views.velocity.VelocityManager
       at com.opensymphony.xwork2.inject.ContainerBuilder$4.create(ContainerBuilder.java:132)
       at com.opensymphony.xwork2.inject.Scope$2$1.create(Scope.java:51)
       at com.opensymphony.xwork2.inject.ContainerImpl$ParameterInjector.inject(ContainerImpl.java:462)
       at com.opensymphony.xwork2.inject.ContainerImpl.getParameters(ContainerImpl.java:477)
       at com.opensymphony.xwork2.inject.ContainerImpl.access$000(ContainerImpl.java:34)
       at com.opensymphony.xwork2.inject.ContainerImpl$MethodInjector.inject(ContainerImpl.java:293)
       ... 45 more
Caused by: java.lang.RuntimeException: java.lang.SecurityException: Unable to get members for class org.apache.struts2.views.velocity.VelocityManager
       at com.opensymphony.xwork2.inject.ContainerImpl.inject(ContainerImpl.java:495)
       at com.opensymphony.xwork2.inject.ContainerImpl$7.call(ContainerImpl.java:532)
       at com.opensymphony.xwork2.inject.ContainerImpl.callInContext(ContainerImpl.java:581)
       at com.opensymphony.xwork2.inject.ContainerImpl.inject(ContainerImpl.java:530)
       at com.opensymphony.xwork2.config.impl.LocatableFactory.create(LocatableFactory.java:32)
       at com.opensymphony.xwork2.inject.ContainerBuilder$4.create(ContainerBuilder.java:130)
       ... 50 more
Caused by: java.lang.SecurityException: Unable to get members for class org.apache.struts2.views.velocity.VelocityManager
       at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_$10.run(Class_.java:357)
       at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_$10.run(Class_.java:347)
       at java.security.AccessController.doPrivileged(Native Method)
       at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_.getMembers(Class_.java:347)
       at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_.getDeclaredConstructors(Class_.java:192)
       at com.opensymphony.xwork2.inject.ContainerImpl$ConstructorInjector.findConstructorIn(ContainerImpl.java:366)
       at com.opensymphony.xwork2.inject.ContainerImpl$ConstructorInjector.<init>(ContainerImpl.java:319)
       at com.opensymphony.xwork2.inject.ContainerImpl$5.create(ContainerImpl.java:305)
       at com.opensymphony.xwork2.inject.ContainerImpl$5.create(ContainerImpl.java:304)
       at com.opensymphony.xwork2.inject.util.ReferenceCache$CallableCreate.call(ReferenceCache.java:150)
       at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)
       at java.util.concurrent.FutureTask.run(Unknown Source)
       at com.opensymphony.xwork2.inject.util.ReferenceCache.internalCreate(ReferenceCache.java:76)
       at com.opensymphony.xwork2.inject.util.ReferenceCache.get(ReferenceCache.java:116)
       at com.opensymphony.xwork2.inject.ContainerImpl.getConstructor(ContainerImpl.java:594)
       at com.opensymphony.xwork2.inject.ContainerImpl.inject(ContainerImpl.java:491)
       ... 55 more

Workarounds

As a work-around (guess), I changed it to cimpl.getDeclaredClasses() instead which is permitted (I don't know if this has the same effect on the ClassLoader). That got me past the issue above, but the same SecurityException occurs in XWork's ContainerImpl$ConstructorInjector.findConstructorIn():

SecurityException: Unable to get members for Class o.a.s.v.v.VelocityManager

This exception occurs within Google App Engine because XWork eagerly loads the VelocityManager Class for the bean struts-default.xml. VelocityManager uses the VelocityToolbox optional dependency (in velocity-tools) which is not deployed with the application by default. I presume the GAE ClassLoader checks all imported classes against the whitelist and fails if the class is not found.
It's overcome by deploying the application velocity.

I now have Struts 2.1.8-snapshot with Convention, Sitemesh and JSON, within a Guice2 servlet filter for IOC, running within GAE.

The mandatory work-around are:

  • to still use a ServletContextListener to disable the OgnlRuntime security manager. If not done, an IllegalAccessException occurs in OgnlUtil.setProperty(String) at run-time. This exception is swallowed, but it typically results in an NPE in ServletRedirectResult.isPathUrl(String) because location cannot be set.
  • the velocity dependencies need to be deployed with the application even if not in use. If not done, a security exception occurs while getting the members of VelocityManager because VelocityManager imports VelocityToolbox and VelocityEngine.

I don't think any S2 code changes are required at this time.

  • No labels