Google App Engine + Struts 2 example
In this tutorial, we will show you how to develop Struts 2 web application in Google App Engine (GAE) environment.
Tools and technologies used :
- JDK 1.6
- Eclipse 3.7 + Google Plugin for Eclipse
- Google App Engine Java SDK 1.6.3.1
- Struts 2.3.1.2
Before proceed on this tutorial, make sure you read this – GAE + Java example and Struts 2 hello world example.
1. New Web Application Project
In Eclipse, create a new Web Application project, named as “Struts2GoogleAppEngine”.
Google Plugin for Eclipse will generate a sample of GAE project structure. Later, we will show you how to integrate Struts2 with this generated GAE project.
2. Integrate Struts 2 Libraries
Get following Struts 2 dependency libraries, download Struts2 here.
- asm-3.3.jar
- asm-commons-3.3.jar
- asm-tree-3.3.jar
- commons-fileupload-1.2.2.jar
- commons-io-2.0.1.jar
- commons-lang-2.5.jar
- freemarker-2.3.18.jar
- javassist-3.11.0.GA.jar
- ognl-3.0.4.jar
- struts2-core-2.3.1.2.jar
- xwork-core-2.3.1.2.jar
Put all in “war/WEB-INF/lib” folder.
Right click on the project folder, select “Properties” -> “Java Build Path” -> “Libraries” tab, click “Add Jars” button and select above 11 jars from “war/WEB-INF/lib” folder into the build path.
3. Integrate Struts 2 Code
3.1 Delete the generated Struts2GoogleAppEngineServlet.java
, you don’t need this.
3.2 Create a new Struts 2 Action class.
File : src/com/mkyong/user/action/WelcomeUserAction.java
package com.mkyong.user.action;
public class WelcomeUserAction {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String execute() {
return "SUCCESS";
}
}
3.3 Create a listener class, and set ognl security to null.
Struts 2 need this listener to run in GAE environment. Read this – Issues when deploying Struts 2 on GAE and Error: result ‘null’ not found
File : src/com/mkyong/listener/Struts2ListenerOnGAE.java
package com.mkyong.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import ognl.OgnlRuntime;
public class Struts2ListenerOnGAE implements ServletContextListener,
HttpSessionListener, HttpSessionAttributeListener {
public void contextInitialized(ServletContextEvent sce) {
OgnlRuntime.setSecurityManager(null);
}
@Override
public void contextDestroyed(ServletContextEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void sessionCreated(HttpSessionEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void sessionDestroyed(HttpSessionEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void attributeAdded(HttpSessionBindingEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void attributeRemoved(HttpSessionBindingEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void attributeReplaced(HttpSessionBindingEvent arg0) {
// TODO Auto-generated method stub
}
}
3.4 To run Struts2 project in local GAE environment, you have to create a TextBlock
class and overload the original TextBlok
class, otherwise you will hit “javax.swing.tree.TreeNode is a restricted class
” error message. Hope Struts2 team can fix this in future released.
Go this URL to download TextBlock source code.
3.5 Review project directory structure.
4. Integrate Struts 2 Pages
4.1 Create a login.jsp
page, to accept user input.
File : war/User/pages/login.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head></head>
<body>
<h1>GAE + Struts 2 Example</h1>
<s:form action="Welcome">
<s:textfield name="username" label="Username"/>
<s:password name="password" label="Password"/>
<s:submit/>
</s:form>
</body>
</html>
4.2 Create a welcome_user.jsp
page.
File : war/User/pages/welcome_user.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head></head>
<body>
<h1>GAE + Struts 2 Example</h1>
<h2>Hello <s:property value="username"/></h2>
</body>
</html>
4.3 Review project directory structure again.
5. Struts XML configuration
Create a struts.xml
file, and put in “src/struts.xml“.
File : struts.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="user" namespace="/User" extends="struts-default">
<action name="Login">
<result>pages/login.jsp</result>
</action>
<action name="Welcome" class="com.mkyong.user.action.WelcomeUserAction">
<result name="SUCCESS">pages/welcome_user.jsp</result>
</action>
</package>
</struts>
6. web.xml
Update web.xml
, integrate Struts2 and configure ognl security listener.
File : web.xml
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>
com.mkyong.listener.Struts2ListenerOnGAE
</listener-class>
</listener>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
7. Directory Structure
Review the final directory structure.
8. Run on Local
Done, time to perform testing. Right click on the project, run as “Web Application“.
URL : http://localhost:8888/User/Login.action
9. Deploy on GAE
Update appengine-web.xml
, put your App Engine application ID.
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>mkyong-struts2gae</application>
<version>1</version>
<!-- Configure java.util.logging -->
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
</system-properties>
</appengine-web-app>
Select project, click on Google icon, “Deploy to App Engine“. Google Plugin for Eclipse will deploy all necessary files to GAE production automatically.
During deployment, following similar messages will be displayed in Eclipse console view.
------------ Deploying frontend ------------
Preparing to deploy:
Created staging directory at: 'C:\Users\mkyong\AppData\Local\Temp\appcfg7432687551.tmp'
Scanning for jsp files.
Compiling jsp files.
Scanning files on local disk.
Initiating update.
Cloning 2 static files.
Cloning 46 application files.
Deploying:
Uploading 12 files.
Uploaded 3 files.
Uploaded 6 files.
Uploaded 9 files.
Uploaded 12 files.
Initializing precompilation...
Sending batch containing 11 file(s) totaling 44KB.
Sending batch containing 1 blob(s) totaling 1KB.
Deploying new version.
Verifying availability:
Will check again in 1 seconds.
Will check again in 2 seconds.
Will check again in 4 seconds.
Closing update: new version is ready to start serving.
Updating datastore:
Uploading index definitions.
Deployment completed successfully
URL : http://mkyong-struts2gae.appspot.com/User/Login.action
Finally, finished this long article. The overall integration is not much difficult, just need to fix Struts2 ognl security and TextBlock issues, hope Struts2’s team can fix this in future.
Download Source Code
Due to large file size, all Struts2 jars are excluded, you need to download it manually.
How to integrate existing struts2 app with google app engine…plz help ([email protected])
Anyone familiar with this exception?
org.apache.jasper.JasperException: File “/struts-tags” not found
yes, this exception occurs when particular file is not found or Mapping of the file is not correct.
Hi mkyoung,
Thank you for your tutorial. I like this article but i have a error. Could you help me?
java.security.AccessControlException: access denied (“java.io.FilePermission” “jar:file:\D:\workspace\Guestbook\war\WEB-INF\lib\struts2-core-2.3.15.2.jar” “read”)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:372)
at java.security.AccessController.checkPermission(AccessController.java:559)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
at com.google.appengine.tools.development.DevAppServerFactory$CustomSecurityManager.checkPermission(DevAppServerFactory.java:383)
at java.lang.SecurityManager.checkRead(SecurityManager.java:888)
at java.util.zip.ZipFile.(ZipFile.java:206)
at java.util.zip.ZipFile.(ZipFile.java:145)
at java.util.jar.JarFile.(JarFile.java:153)
at java.util.jar.JarFile.(JarFile.java:90)
at com.opensymphony.xwork2.util.fs.JarEntryRevision.needsReloading(JarEntryRevision.java:76)
at com.opensymphony.xwork2.util.fs.DefaultFileManager.fileNeedsReloading(DefaultFileManager.java:66)
at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.needsReload(XmlConfigurationProvider.java:394)
at org.apache.struts2.config.StrutsXmlConfigurationProvider.needsReload(StrutsXmlConfigurationProvider.java:169)
at com.opensymphony.xwork2.config.ConfigurationManager.needReloadContainerProviders(ConfigurationManager.java:215)
at com.opensymphony.xwork2.config.ConfigurationManager.conditionalReload(ConfigurationManager.java:179)
at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:73)
at org.apache.struts2.dispatcher.Dispatcher.getContainer(Dispatcher.java:968)
at org.apache.struts2.dispatcher.ng.PrepareOperations.createActionContext(PrepareOperations.java:77)
at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:86)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at com.google.appengine.api.socket.dev.DevSocketFilter.doFilter(DevSocketFilter.java:74)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at com.google.appengine.tools.development.ResponseRewriterFilter.doFilter(ResponseRewriterFilter.java:123)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at com.google.appengine.tools.development.HeaderVerificationFilter.doFilter(HeaderVerificationFilter.java:34)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at com.google.appengine.api.blobstore.dev.ServeBlobFilter.doFilter(ServeBlobFilter.java:63)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at com.google.apphosting.utils.servlet.TransactionCleanupFilter.doFilter(TransactionCleanupFilter.java:43)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at com.google.appengine.tools.development.StaticFileFilter.doFilter(StaticFileFilter.java:125)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at com.google.appengine.tools.development.DevAppServerModulesFilter.doDirectRequest(DevAppServerModulesFilter.java:368)
at com.google.appengine.tools.development.DevAppServerModulesFilter.doDirectModuleRequest(DevAppServerModulesFilter.java:351)
at com.google.appengine.tools.development.DevAppServerModulesFilter.doFilter(DevAppServerModulesFilter.java:116)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
at com.google.appengine.tools.development.DevAppEngineWebAppContext.handle(DevAppEngineWebAppContext.java:97)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at com.google.appengine.tools.development.JettyContainerService$ApiProxyHandler.handle(JettyContainerService.java:485)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at org.mortbay.jetty.Server.handle(Server.java:326)
at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:923)
at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:547)
at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212)
at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409)
at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
Did you able to resolve this
I also have met this issue! Mkyong, please give me the solutions! Thank so much!
How about if we have existing struts2 app and want to install it on Google App Engine?
Struts2 App build with: SiteMesh, JDBC with SQLite
How to add it to GAE?
Please help me with some propose steps.
Hello,
I’ve followed this tutorial to create an application (next step: tiles).
It worked OK except that, for some reason, eclipse says my jsp files have errors. The project constantly has the red cross on it, and I have an error notification every time I try to deploy the project.
Do you have any idea why this is happening?
Thanks for the article.
Worked for me too. Thanks!
Doesnt work with GAE 1.6.6
I find it very inconvinent that Google and the Java frameworks are constantly changing something in their architecture which causes incompatibility issues.
To get running a Framework on the GAE you have to be really a magician or jedi. Yoda perhaps can do it.
I am new to webapp devoloping. I choosed the java way, but the dark hacky php way seems to me every day more and more tempting.
Very detailed example. Can be used as a template for making Struts 2 apps in Google App Engine.
Thanks for this example.
I like this article…Thank you
Hi mkyoung,
I am regular vistor of your greate site.Thanks for making such site.
A week ago google has revised its policy for many of its API and services ncluding App engine.what will its affect on google appengine application.whether they are going to drop this feature in near future,or they will make it as a paid service.Please reply me soon..