Apache Struts 2 Documentation > Home > FAQs > Google App Engine (GAE) |
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.
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
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:
I don't think any S2 code changes are required at this time.