Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / Apache

Struts 2 Integration with Google AppEngine

0.00/5 (No votes)
19 Dec 2012MPL7 min read 22.8K   148  
This article introduces developers to use Apache Struts 2 with Google AppEngine. Specifically, the jars needed, and the project setup to allow your application to allow Struts execute under the AppEngine environment.

Sample Application that is deployed to AppSopt that demonstrate the integration of Struts 2 and Google AppEngine

Sample application that is deployed to AppSopt that demonstrates the integration of Struts 2 and Google AppEngine

Introduction

Two years ago, when I first started experimenting with Google AppEngine, I tried to integrate Google AppEngine project with Apache Struts 2. This allows me to design and implement my app with ease.

The problem is that for security reasons, a lot of advanced features of J2EE are disabled when the app runs in AppEngine container. As a result, a lot of supporting jars cannot be used with AppEngine. After extensive search and experiment, I was able to integrate AppEngine with Apache Struts 2. This article will summarize my work to get this to working.

Assumptions

I will assume that if you read this article and try to replicate my work for your own, you have proficient knowledge on Eclipse, J2EE, and Google AppEngine technologies. You should be able to manipulate Eclipse generated project files, and capable of perform change to the code files to get a compilable project running in Eclipse. I will not include any specific details regarding how a task can be performed.

Show Case

As I have mentioned before I used Struts in my AppEngine application. I have deployed my application to appspot.com, the Google AppEngine hosting platform. You can check it out at this URL:

Setting Up AppEngine Project with Struts

I have uploaded a working version of the project setup. If you want to experiment with this project, you can download the zip file at the beginning of this article, unzip it, and do some changes to the projects, compile it, and run it in Eclipse. It is a lot of work. In order to upload this article and enough of source files to keep the package small, I had to remove all the jars in the lib directory and WEB-INF lib directory. Be warned, if you are a novice developer, you might want to skip this article.

First thing first, install the latest Google AppEngine API and plug-in for Eclipse. Once Eclipse is properly configured, you can create a simple AppEngine application and run locally to make sure it works.

Download the sample project, unzip the sample project into a local folder. Import the project into Eclipse. Note that the project will show multiple errors -- missing jars. In order to run the sample app, these errors of missing jars must be fixed. The following are all the jars I am using:

  • commons-fileupload-1.2.2.jar
  • commons-io-2.0.1.jar
  • commons-lang-2.5.jar
  • commons-logging-1.1.1.jar
  • javassist-3.11.0.GA.jar
  • ognl-3.0.1.jar
  • struts2-core-2.2.3.jar
  • struts2-gae-0.1.jar
  • xwork-core-2.2.3.jar
  • appengine-api-labs-1.6.1.jar
  • appengine-api-1.0-sdk-1.6.1.jar
  • appengine-jsr107cache-1.6.1.jar
  • freemarker-gae-2.3.18.jar
  • appengine-local-runtime.jar
  • appengine-testing.jar
  • testng-6.0.1.jar

You can find all these jars in the Eclipse project's .classpath file:

XML
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
    <classpathentry kind="src" path="src"/>
    <classpathentry kind="con" path="com.google.appengine.eclipse.core.GAE_CONTAINER"/>
    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
    <classpathentry kind="lib" path="./war/WEB-INF/lib/commons-fileupload-1.2.2.jar"/>
    <classpathentry kind="lib" path="./war/WEB-INF/lib/commons-io-2.0.1.jar"/>
    <classpathentry kind="lib" path="./war/WEB-INF/lib/commons-lang-2.5.jar"/>
    <classpathentry kind="lib" path="./war/WEB-INF/lib/commons-logging-1.1.1.jar"/>
    <classpathentry kind="lib" path="./war/WEB-INF/lib/javassist-3.11.0.GA.jar"/>
    <classpathentry kind="lib" path="./war/WEB-INF/lib/ognl-3.0.1.jar"/>
    <classpathentry kind="lib" path="./war/WEB-INF/lib/struts2-core-2.2.3.jar"/>
    <classpathentry kind="lib" path="./war/WEB-INF/lib/struts2-gae-0.1.jar"/>
    <classpathentry kind="lib" path="./war/WEB-INF/lib/xwork-core-2.2.3.jar"/>
    <classpathentry kind="lib" path="./war/WEB-INF/lib/appengine-api-labs-1.6.1.jar"/>
    <classpathentry kind="lib" path="./war/WEB-INF/lib/appengine-api-1.0-sdk-1.6.1.jar"/>
    <classpathentry kind="lib" path="./war/WEB-INF/lib/appengine-jsr107cache-1.6.1.jar"/>
    <classpathentry kind="lib" path="./war/WEB-INF/lib/freemarker-gae-2.3.18.jar"/>
    <classpathentry kind="lib" path="./lib/appengine-local-runtime.jar"/>
    <classpathentry kind="lib" path="./lib/appengine-testing.jar"/>
    <classpathentry kind="lib" path="./lib/testng-6.0.1.jar"/>
    <classpathentry kind="output" path="war/WEB-INF/classes"/>
</classpath>

What you need to do in order to fix these missing jars, you can first download these jars from the internet. Most of them can be found from Apache Foundation. But there is a few gotchas:

  • struts2-gae-0.1.jar: This is a customized jar that can be found from here. This jar provides an listener (com.struts2.gae.listener.OgnlListener) that can be used in AppEngine container for Struts 2. You can find the configuration in web.xml file for this project.
  • appengine-api-labs-1.6.1.jar, appengine-api-1.0-sdk-1.6.1.jar, and appengine-jsr107cache-1.6.1.jar: They are AppEngine core files, must be part of the war being deployed. You can find them in the AppEngine lib directories.
  • appengine-local-runtime.jar, appengine-testing.jar, and testng-6.0.1.jar: These are the jars that can be used to run unit tests via Eclipse. Again, TestNG can be downloaded from the web. The other 2 jars can be found under the AppEngine libs directories.
  • freemarker-gae-2.3.18.jar: This jar is one missing link of getting Struts 2 working in AppEngine container. There are info on how to solve the exceptions via some alternative, but this jar completely removes the obstacles and makes Struts 2 works. You can find this jar from here.

Simply getting the jars and getting the project compilable is not enough, I have added two classes that bypass some other issues with integration of Struts 2 and AppEngine. You can find these two classes:

  • org.hanbo.sample.GaeInitOperations
  • org.hanbo.sample.GaePrepareAndExecuteFilter

The two classes are used as Struts 2 filter, that is, based on the URL pattern, certain HTTP requests will be redirected to the Struts 2 for handling. This configuration can be found in web.xml.

org.hanbo.sample.GaeInitOperations:

Java
package org.hanbo.sample;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
 
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.ng.HostConfig;
import org.apache.struts2.dispatcher.ng.InitOperations;

import com.struts2.gae.dispatcher.GaeDispatcher;
 
public class GaeInitOperations extends InitOperations
{
    @Override
    public Dispatcher initDispatcher(HostConfig filterConfig)
    {    
        Dispatcher dispatcher = createDispatcher(filterConfig);
        dispatcher.init();
        return dispatcher;
    }
    
    private Dispatcher createDispatcher( HostConfig filterConfig )
    {
        Map<String, String> params = new HashMap<String, String>();
        for (Iterator<String> e = filterConfig.getInitParameterNames(); e.hasNext(); )
        {
            String name = (String) e.next();
            String value = filterConfig.getInitParameter(name);
            params.put(name, value);
        }
        return new GaeDispatcher(filterConfig.getServletContext(), params);
    }
}

org.hanbo.sample.GaePrepareAndExecuteFilter:

Java
package org.hanbo.sample;

import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
 
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.ng.ExecuteOperations;
import org.apache.struts2.dispatcher.ng.InitOperations;
import org.apache.struts2.dispatcher.ng.PrepareOperations;
import org.apache.struts2.dispatcher.ng.filter.FilterHostConfig;
import org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter;
 
public class GaePrepareAndExecuteFilter extends StrutsPrepareAndExecuteFilter
{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        InitOperations init = new GaeInitOperations();
        try
        {
            FilterHostConfig config = new FilterHostConfig(filterConfig);
            init.initLogging(config);
            Dispatcher dispatcher = init.initDispatcher(config);
 
            init.initStaticContentLoader(config, dispatcher);
 
            prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
            execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
            this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
 
            postInit(dispatcher, filterConfig);
        }
        finally
        {
            init.cleanup();
        }
    }
}

And the web.xml looks like this:

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.hanbo.sample.GaePrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <listener>
        <listener-class>com.struts2.gae.listener.OgnlListener</listener-class>
    </listener>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>

</web-app>

In above configuration, the filter section uses the filter I have implemented (actually the code was copied from online sources). And the listener uses the listener found in struts-gae jar. It is necessary to have the filter class configured in web.xml. The filter class can be used to exclude the AppEngine administrator console URL as a Struts 2 action.

At this point, as long as all errors are completely cleared, you should be able to execute the project as a Google Web Application. The project includes a simple action. Next section will explain how the action works.

Testing the Setup

Now, it is possible to create Struts actions and test the project configuration. First, take a look at struts.xml. Struts.xml defines the how the web page URL routes to the action. It is better to see an example:

Assuming that you have define an web URL: http://localhost:8888/HelloWorld/HelloWorld.action. When struts.xml is properly configured, the URL will be routed to an action. An action is a JAVA object that can be used to process user request. Every action has one or more execute methods. Each execute method can return a string indicate the execution status. So with the URL in the example, it will find a route in the struts.xml that mapping the URL to the action. The action will define how the user request to be processed. And the response status will also route to a response page to be displayed.

In this sample project, the struts.xml looks like this:

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>
    <constant name="struts.action.excludePattern" value="/_ah/.*"/>
    <package
      name="default"
      extends="struts-default">
        <default-action-ref name="index"/>
        <action name="index" class="org.hanbo.sample.HelloWorld">
            <result name="success">/jsp/HelloWorld.jsp</result>
        </action>
    </package>
</struts>

Essentially, the project defines an action called index (you can invoke the action as http://localhost:8888/index.action). This will instantiate the object org.hanbo.sample.HelloWorld (the action class). The default execute method (execute()). This execute method simply returns the string "Success" indicating the processing is successful. Once the execute method returns, the success status will redirect to a predefined JSP page called HelloWorld.jsp, which contains Struts specific tags that can be replaced with real data.

org.hanbo.sample.HelloWorld

Java
package org.hanbo.sample;

import com.opensymphony.xwork2.ActionSupport;

public class HelloWorld extends ActionSupport
{
    private static final long serialVersionUID = -4072951440919824406L;

    private String name;
    
    
    public HelloWorld()
    {
        name = "Jason Jones";
    }
    
    public String getName()
    {
        return name;
    }
    
    public String execute()
    {
        return SUCCESS;
    }
}

HelloWorld.jsp

ASP.NET
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
           "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
  <title>Hello World</title>
  <link rel="stylesheet" href="../default.css" />
</head>
<body>
    <div id="main">
      <div id="content">
        <h3>Hello World - <s:property value="name"/></h3>
      </div>
    </div>
</body>
</html>

Execution

In order to execute the sample project:

  • Click on the sample project to select it. Then right click on it to pop up he context menu.
  • Select Run As -> Web Application (the one that has the icon of blue sphere with the character "g" in the middle).
  • If you are using JDK 1.6_29 or earlier, you should be able to get the web app running successfully. If you are using a later version, there might be a problem with AppEngine.
  • To solve the issue with AppEngine, you need to add "-Dappengine.user.timezone=UTC" via Run Configurations.

Once you were able to start the Web App, you should see the following output in the Console:

********************************************************
There is a new version of the SDK available.
-----------
Latest SDK:
Release: 1.7.4
Timestamp: Sun Nov 11 01:09:13 PST 2012
API versions: [1.0]

-----------
Your SDK:
Release: 1.6.3
Timestamp: Fri Feb 24 09:36:23 PST 2012
API versions: [1.0]

-----------
Please visit http://code.google.com/appengine for the latest SDK.
********************************************************
2012-12-16 07:23:23.268:INFO::Logging to STDERR via org.mortbay.log.StdErrLog
Dec 16, 2012 7:23:23 AM com.google.apphosting.utils.config.AppEngineWebXmlReader readAppEngineWebXml
INFO: Successfully processed C:\Users\hsun1\gwt-workspace\AppEngineSampleProject\war\WEB-INF/appengine-web.xml
Dec 16, 2012 7:23:23 AM com.google.apphosting.utils.config.AbstractConfigXmlReader readConfigXml
INFO: Successfully processed C:\Users\hsun1\gwt-workspace\AppEngineSampleProject\war\WEB-INF/web.xml
2012-12-16 07:23:23.879:INFO::jetty-6.1.x
2012-12-16 07:23:26.344:INFO::Started SelectChannelConnector@127.0.0.1:8888
Dec 16, 2012 7:23:26 AM com.google.appengine.tools.development.DevAppServerImpl start
INFO: The server is running at http://localhost:8888/
Dec 16, 2012 7:23:26 AM com.google.appengine.tools.development.DevAppServerImpl start
INFO: The admin console is running at http://localhost:8888/_ah/admin

Once the app starts successfully, you can run the app in the browser by "http://localhost:8888/index.action". You should see the following screenshot:

Image 2

Be Aware

This project only supports the bare minimum of Struts 2 working with AppEngine. I never even tried to get the Sitemesh to work with Struts 2. Sitemesh is nice, but not really needed.

History

  • Initial draft - 12/15/2012.

License

This article, along with any associated source code and files, is licensed under The Mozilla Public License 1.1 (MPL 1.1)