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 :

  1. JDK 1.6
  2. Eclipse 3.7 + Google Plugin for Eclipse
  3. Google App Engine Java SDK 1.6.3.1
  4. Struts 2.3.1.2
Note
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”.

gae struts2 example new project

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.

gae struts2 example sample 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.

gae struts2 example libraries

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.

gae struts2 example java 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.

Note
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.

TextBlock Source Code
Go this URL to download TextBlock source code.

3.5 Review project directory structure.

gae struts2 example directory

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.

gae struts2 example directory

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.

gae struts2 example final directory

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

gae struts2 example run on local
gae struts2 example run on local

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.

gae struts2 example deploy to GAE

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

gae struts2 example run on GAE
Note
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.

Download – Struts2GoogleAppEngine.zip (23 KB)

References

  1. Struts2 hello world example
  2. Google App Engine + Java hello world example, using Eclipse
  3. Apache Struts
  4. Struts 2 on GAE
Tags :

About the Author

mkyong
Founder of Mkyong.com and HostingCompass.com, love Java and open source stuff. Follow him on Twitter, or befriend him on Facebook or Google Plus. If you like my tutorials, consider make a donation to these charities.

Comments

  • Seekend

    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)

    • Knight Rises

      I also have met this issue! Mkyong, please give me the solutions! Thank so much!

  • http://osify.com mtr

    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.

  • Nahoot

    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.

  • Ian Rae

    Worked for me too. Thanks!

  • Bai_Hui

    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.

  • http://jrgalia.com JR Galia

    Very detailed example. Can be used as a template for making Struts 2 apps in Google App Engine.

    Thanks for this example.

  • http://www.javarecord.com zzj

    I like this article…Thank you

  • Deepak

    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..

  • Pingback: Google App Engine + Struts 1 example()