Creating Struts2 application on Google App Engine (GAE)


Why i am writing this blog?

I faced lot of problems while creating my first application using Struts 2 on Google App Engine . So i decided to write a blog detailing how developers can create their application on Struts2 by avoiding the problems that i faced while developing my application.

Note

If you want to do FileUpload using struts 2  on google app engine please refer to this post.

Prerequisites for starting Struts2 Application on Google App Engine

Before you start building your sample application on google app engine using struts 2 you will need the following:-

  1. Google App Engine runs on java 5 and above so if necessary, download and install the Java SE Development Kit (JDK) for your platform and for mac users download and install the latest version.
  2. In this example we will be using Eclipse as our ide. So if necessary, download eclipse and google app engine plugin for eclipse.You will also need to download the google  java app engine SDK. For more information you can refer to installing the java SDK for google app engine.
  3. Download the latest release of Struts2 framework.If you want to learn struts 2 a very good reference is struts 2 in action book.Please buy Struts 2 in Action.

Step by Step procedure to create Struts2 application on Google App Engine.

Step 1: Create a new project by clicking the New Web Application Project button in the toolbarnew_app_button.

1

Step 2 : Give the project name say login  as we are going to create a simple login application. Enter package name as com.login and uncheck “Use Google Web Toolkit,” and ensure “Use Google App Engine” is checked and click the finish button.

2

Step3 : When you click the finish button you will get a sample HelloWorld application, which you can run going in the Run menu, select Run As > Web Application.By default application will run at port 8080, you can view the sample application at http://locahost:8080. For more information on the sample google web application created by the plugin you can refer to Google java app engine documentation .Please keep in mind that intent of this document is not to provide developers the overview of Google App engine for Java.

Step4 : By now you are ready with the google app engine infrastructure and we can move to the next step of creating a login application in Struts 2.

  • for creating a struts 2 application you will need to first add the required dependencies to the login project. The required struts 2 jars are below mentioned and you can find these jars in struts2 package you downloaded inside the lib folder :-
    • commons-fileupload-1.2.1.jar
    • commons-io-1.3.2.jar
    • commons-logging-1.1.jar
    • freemarker-2.3.13.jar
    • ognl-2.6.11.jar
    • struts2-core-2.1.6.jar
    • xwork-2.1.2.jar
  • Add these dependencies in your eclipse java build path.

3

  • Add these dependencies in the war/WEB-INF/lib folder so that these jars gets deployed along with your application.

4

  • As you can see in the above image there are some warnings in the problems view like  “commons-fileupload-1.2.1.jar’ will not be available on the server’s classpath” .To remove these warnings you need to right click on the project and select properties and then go to Google > web application and click the add button and add all the jars and then press ok.

5

  • First step in creating a struts 2 application is configuring the web.xml (deployment descriptor) which is located in WEB-INF folder.You can remove the servlet declaration from web.xml as we will not be needing this.We need to add the FilterDispatcher declaration in web.xml because in struts2 every request goes  pass through FilterDispatcher which will invoke the appropriate action corresponding to the URL mapping.So our web.xml will look like :-
<?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.FilterDispatcher</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>struts2</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
	</welcome-file-list>
</web-app>
  • To start we will be creating a jsp login page using struts 2 tag library and we will call login page from index.html.To call the login page there are two ways first, we can directly call the login.jsp page from link second, we can calling it through struts. We will be taking the second step as this will show you how to configure actions when you dont need to invoke any action.Lets first see how our login page and index.html will look like :-

index.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Struts2 on Google App Engine</title>
</head>

<body>
<h1>Struts2 on Google App Engine!</h1>
<table>
<tr>
<td colspan="2" style="font-weight: bold;">Available Application:</td>
</tr>
<tr>
<td><a href="/member/login" />Login</td>
</tr>
</table>
</body>
</html>

login.jsp

<%@ 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>Please login</title>
</head>
<body>
 <s:actionerror/>
 <s:form action="home" method="post">
 <s:textfield name="username" label="UserName"></s:textfield>
 <s:textfield name="password" label="Password"></s:textfield>
 <s:submit name="login" value="login"></s:submit>
 </s:form>
</body>
</html>
  • After creating the login.jsp we need to configure this as action in the struts.xml file which you be should put inside source folder parallel to log4j.properties file.We can configure action as mentioned below:-
<?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>
	<include file="struts-default.xml"></include>
<package name="member" namespace="/member" extends="struts-default">
		<action name="login">
			<result>/login.jsp</result>
		</action>
	</package>
</struts>
  • Now try running this application by right click on project run as > web application and click http://localhost:8080. You will see index.html and when you click on login you will get this exception :-

SEVERE: Unable to set parameter [location] in result of type    [org.apache.struts2.dispatcher.ServletDispatcherResult]

Caught OgnlException while setting property ‘location’ on type ‘org.apache.struts2.dispatcher.ServletDispatcherResult’. – Class: ognl.OgnlRuntime

File: OgnlRuntime.java

Method: invokeMethod

Line: 508 – ognl/OgnlRuntime.java:508:-1

at com.opensymphony.xwork2.ognl.OgnlUtil.internalSetProperty(OgnlUtil.java:392)

Caused by: java.lang.IllegalAccessException: Method [public void org.apache.struts2.dispatcher.StrutsResultSupport.setLocation(java.lang.String)] cannot be accessed.

at ognl.OgnlRuntime.invokeMethod(OgnlRuntime.java:508)

at ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:812)

at ognl.OgnlRuntime.setMethodValue(OgnlRuntime.java:964)

at ognl.ObjectPropertyAccessor.setPossibleProperty(ObjectPropertyAccessor.java:75)

at ognl.ObjectPropertyAccessor.setProperty(ObjectPropertyAccessor.java:131)

at com.opensymphony.xwork2.ognl.accessor.ObjectAccessor.setProperty(ObjectAccessor.java:28)

at ognl.OgnlRuntime.setProperty(OgnlRuntime.java:1656)

at ognl.ASTProperty.setValueBody(ASTProperty.java:101)

at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:177)

at ognl.SimpleNode.setValue(SimpleNode.java:246)

at ognl.Ognl.setValue(Ognl.java:476)

at com.opensymphony.xwork2.ognl.OgnlUtil.setValue(OgnlUtil.java:192)

at com.opensymphony.xwork2.ognl.OgnlUtil.internalSetProperty(OgnlUtil.java:385)

… 73 more

  • In order to resolve this problem we  need to create an ServletContextListner which will set OGNL security manager to null when the context is initialized.
package com.login;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import ognl.OgnlRuntime;

public class OgnlListener implements ServletContextListener {

	@Override
	public void contextDestroyed(ServletContextEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void contextInitialized(ServletContextEvent arg0) {
		OgnlRuntime.setSecurityManager(null);
	}

}
  • Also we will need to make entry in web.xml file also for OgnlListener
	<listener>
	<listener-class>com.login.OgnlListener</listener-class>
</listener>
  • Now if you run the web application you will see the login page.
  • You need to add this step if you are using Google App Engine 1.2.6 because when you run struts2 application on google app engine 1.2.6 you will get the following error:-

javax.servlet.ServletException: java.lang.NoClassDefFoundError: javax.swing.tree.TreeNode is a restricted class. Please see the Google App Engine developer’s guide for more details.
at org.apache.jasper.runtime.PageContextImpl.doHandlePageException(PageContextImpl.java:825)
at org.apache.jasper.runtime.PageContextImpl.access$1100(PageContextImpl.java:64)
at org.apache.jasper.runtime.PageContextImpl$12.run(PageContextImpl.java:745)
at java.security.AccessController.doPrivileged(Native Method)
at org.apache.jasper.runtime.PageContextImpl.handlePageException(PageContextImpl.java:743)
at org.apache.jsp.login_jsp._jspService(login_jsp.java:86)
at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:94)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:324)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:292)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:236)
at com.google.appengine.tools.development.PrivilegedJspServlet.access$101(PrivilegedJspServlet.java:23)
at com.google.appengine.tools.development.PrivilegedJspServlet$2.run(PrivilegedJspServlet.java:59)
at java.security.AccessController.doPrivileged(Native Method)
at com.google.appengine.tools.development.PrivilegedJspServlet.service(PrivilegedJspServlet.java)

To avoid this error you need to create a new package “freemarker.core” in your source folder and add the following class

</span></span>

/*
 * Copyright (c) 2003 The Visigoth Software Society. All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above
copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowledgement:
 *       "This product includes software developed by the
 *        Visigoth Software Society (http://www.visigoths.org/)."
 *    Alternately, this acknowledgement may appear in the software
itself,
 *    if and wherever such third-party acknowledgements normally
appear.
 *
 * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names
of the
 *    project contributors may be used to endorse or promote products
derived
 *    from this software without prior written permission. For written
 *    permission, please contact visigo...@visigoths.org.
 *
 * 5. Products derived from this software may not be called
"FreeMarker" or "Visigoth"
 *    nor may "FreeMarker" or "Visigoth" appear in their names
 *    without prior written permission of the Visigoth Software
Society.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Visigoth Software Society. For more
 * information on the Visigoth Software Society, please see
 * http://www.visigoths.org/
 */

package freemarker.core;

import java.io.IOException;

/**
 * A TemplateElement representing a block of plain text.
 *
 * @version $Id: TextBlock.java,v 1.17 2004/01/06 17:06:42 szegedia Exp $
 */
public final class TextBlock extends TemplateElement {
 private static final char[] EMPTY_CHAR_ARRAY = new char[0];
 static final TextBlock EMPTY_BLOCK = new TextBlock(EMPTY_CHAR_ARRAY, false);
 // We're using char[] instead of String for storing the text block because
 // Writer.write(String) involves copying the String contents to a char[]
 // using String.getChars(), and then calling Writer.write(char[]).By
 // using Writer.write(char[]) directly, we avoid array copying on each
 // write.
 private char[] text;
 private final boolean unparsed;

 public TextBlock(String text) {
 this(text, false);
 }

 public TextBlock(String text, boolean unparsed) {
 this(text.toCharArray(), unparsed);
 }

 private TextBlock(char[] text, boolean unparsed) {
 this.text = text;
 this.unparsed = unparsed;
 }

 /**
 * Simply outputs the text.
 */
 public void accept(Environment env) throws IOException {
 env.getOut().write(text);
 }

 public String getCanonicalForm() {
 String text = new String(this.text);
 if (unparsed) {
 return "<#noparse>" + text + "</#noparse>";
 }
 return text;
 }

 public String getDescription() {
 String s = new String(text).trim();
 if (s.length() == 0) {
 return "whitespace";
 }
 if (s.length() > 20) {
 s = s.substring(0, 20) + "...";
 s = s.replace('\n', ' ');
 s = s.replace('\r', ' ');
 }
 return "text block (" + s + ")";
 }

 TemplateElement postParseCleanup(boolean stripWhitespace) {
 if (text.length == 0)
 return this;
 int openingCharsToStrip = 0, trailingCharsToStrip = 0;
 boolean deliberateLeftTrim = deliberateLeftTrim();
 boolean deliberateRightTrim = deliberateRightTrim();
 if (!stripWhitespace || text.length == 0) {
 return this;
 }
 if (parent.parent == null && previousSibling() == null)
 return this;
 if (!deliberateLeftTrim) {
 trailingCharsToStrip = trailingCharsToStrip();
 }
 if (!deliberateRightTrim) {
 openingCharsToStrip = openingCharsToStrip();
 }
 if (openingCharsToStrip == 0 && trailingCharsToStrip == 0) {
 return this;
 }
 this.text = substring(text, openingCharsToStrip, text.length
 - trailingCharsToStrip);
 if (openingCharsToStrip > 0) {
 this.beginLine++;
 this.beginColumn = 1;
 }
 if (trailingCharsToStrip > 0) {
 this.endColumn = 0;
 }
 return this;
 }

 /**
 * Scans forward the nodes on the same line to see whether there is a
 * deliberate left trim in effect. Returns true if the left trim was
 * present.
 */
 private boolean deliberateLeftTrim() {
 boolean result = false;
 for (TemplateElement elem = this.nextTerminalNode(); elem != null
 && elem.beginLine == this.endLine; elem = elem
 .nextTerminalNode()) {
 if (elem instanceof TrimInstruction) {
 TrimInstruction ti = (TrimInstruction) elem;
 if (!ti.left && !ti.right) {
 result = true;
 }
 if (ti.left) {
 result = true;
 int lastNewLineIndex = lastNewLineIndex();
 if (lastNewLineIndex >= 0 || beginColumn == 1) {
 char[] firstPart = substring(text, 0,
 lastNewLineIndex + 1);
 char[] lastLine = substring(text, 1 + lastNewLineIndex);
 if (trim(lastLine).length == 0) {
 this.text = firstPart;
 this.endColumn = 0;
 } else {
 int i = 0;
 while (Character.isWhitespace(lastLine[i])) {
 i++;
 }
 char[] printablePart = substring(lastLine, i);
 this.text = concat(firstPart, printablePart);
 }
 }
 }
 }
 }
 if (result) {
 }
 return result;
 }

 /**
 * Checks for the presence of a t or rt directive on the same line. Returns
 * true if the right trim directive was present.
 */
 private boolean deliberateRightTrim() {
 boolean result = false;
 for (TemplateElement elem = this.prevTerminalNode(); elem != null
 && elem.endLine == this.beginLine; elem = elem
 .prevTerminalNode()) {
 if (elem instanceof TrimInstruction) {
 TrimInstruction ti = (TrimInstruction) elem;
 if (!ti.left && !ti.right) {
 result = true;
 }
 if (ti.right) {
 result = true;
 int firstLineIndex = firstNewLineIndex() + 1;
 if (firstLineIndex == 0) {
 return false;
 }
 if (text.length > firstLineIndex
 && text[firstLineIndex - 1] == '\r'
 && text[firstLineIndex] == '\n') {
 firstLineIndex++;
 }
 char[] trailingPart = substring(text, firstLineIndex);
 char[] openingPart = substring(text, 0, firstLineIndex);
 if (trim(openingPart).length == 0) {
 this.text = trailingPart;
 this.beginLine++;
 this.beginColumn = 1;
 } else {
 int lastNonWS = openingPart.length - 1;
 while (Character.isWhitespace(text[lastNonWS])) {
 lastNonWS--;
 }
 char[] printablePart = substring(text, 0, lastNonWS + 1);
 if (trim(trailingPart).length == 0) {
 // THIS BLOCK IS HEINOUS! THERE MUST BE A BETTER
 // WAY! REVISIT (JR)
 boolean trimTrailingPart = true;
 for (TemplateElement te = this.nextTerminalNode(); te != null
 && te.beginLine == this.endLine; te = te
 .nextTerminalNode()) {
 if (te.heedsOpeningWhitespace()) {
 trimTrailingPart = false;
 }
 if (te instanceof TrimInstruction
 && ((TrimInstruction) te).left) {
 trimTrailingPart = true;
 break;
 }
 }
 if (trimTrailingPart)
 trailingPart = EMPTY_CHAR_ARRAY;
 }
 this.text = concat(printablePart, trailingPart);
 }
 }
 }
 }
 return result;
 }

 /*
 * private String leftTrim(String s) { int i =0; while (i<s.length()) { if
 * (!Character.isWhitespace(s.charAt(i))) break; ++i; } return
 * s.substring(i); }
 */
 private int firstNewLineIndex() {
 String content = new String(text);
 int newlineIndex1 = content.indexOf('\n');
 int newlineIndex2 = content.indexOf('\r');
 int result = newlineIndex1 >= 0 ? newlineIndex1 : newlineIndex2;
 if (newlineIndex1 >= 0 && newlineIndex2 >= 0) {
 result = Math.min(newlineIndex1, newlineIndex2);
 }
 return result;
 }

 private int lastNewLineIndex() {
 String content = new String(text);
 return Math.max(content.lastIndexOf('\r'), content.lastIndexOf('\n'));
 }

 /**
 * figures out how many opening whitespace characters to strip in the
 * post-parse cleanup phase.
 */
 private int openingCharsToStrip() {
 int newlineIndex = firstNewLineIndex();
 if (newlineIndex == -1 && beginColumn != 1) {
 return 0;
 }
 ++newlineIndex;
 if (text.length > newlineIndex) {
 if (newlineIndex > 0 && text[newlineIndex - 1] == '\r'
 && text[newlineIndex] == '\n') {
 ++newlineIndex;
 }
 }
 if (new String(text).substring(0, newlineIndex).trim().length() > 0) {
 return 0;
 }
 // We look at the preceding elements on the line to see if we should
 // strip the opening newline and any whitespace preceding it.
 for (TemplateElement elem = this.prevTerminalNode(); elem != null
 && elem.endLine == this.beginLine; elem = elem
 .prevTerminalNode()) {
 if (elem.heedsOpeningWhitespace()) {
 return 0;
 }
 }
 return newlineIndex;
 }

 /**
 * figures out how many trailing whitespace characters to strip in the
 * post-parse cleanup phase.
 */
 private int trailingCharsToStrip() {
 String content = new String(text);
 int lastNewlineIndex = lastNewLineIndex();
 if (lastNewlineIndex == -1 && beginColumn != 1) {
 return 0;
 }
 String substring = content.substring(lastNewlineIndex + 1);
 if (substring.trim().length() > 0) {
 return 0;
 }
 // We look at the elements afterward on the same line to see if we
 // should strip any whitespace after the last newline
 for (TemplateElement elem = this.nextTerminalNode(); elem != null
 && elem.beginLine == this.endLine; elem = elem
 .nextTerminalNode()) {
 if (elem.heedsTrailingWhitespace()) {
 return 0;
 }
 }
 return substring.length();
 }

 boolean heedsTrailingWhitespace() {
 if (isIgnorable()) {
 return false;
 }
 for (int i = 0; i < text.length; i++) {
 char c = text[i];
 if (c == '\n' || c == '\r') {
 return false;
 }
 if (!Character.isWhitespace(c)) {
 return true;
 }
 }
 return true;
 }

 boolean heedsOpeningWhitespace() {
 if (isIgnorable()) {
 return false;
 }
 for (int i = text.length - 1; i >= 0; i--) {
 char c = text[i];
 if (c == '\n' || c == '\r') {
 return false;
 }
 if (!Character.isWhitespace(c)) {
 return true;
 }
 }
 return true;
 }

 boolean isIgnorable() {
 if (text == null || text.length == 0) {
 return true;
 }
 if (!isWhitespace()) {
 return false;
 }
 // trick here
 boolean atTopLevel = true;
 TemplateElement prevSibling = previousSibling();
 TemplateElement nextSibling = nextSibling();
 return ((prevSibling == null && atTopLevel) || nonOutputtingType(prevSibling))
 && ((nextSibling == null && atTopLevel) || nonOutputtingType(nextSibling));
 }

 private boolean nonOutputtingType(TemplateElement element) {
 return (element instanceof Macro || element instanceof Assignment
 || element instanceof AssignmentInstruction
 || element instanceof PropertySetting
 || element instanceof LibraryLoad || element instanceof Comment);
 }

 private static char[] substring(char[] c, int from, int to) {
 char[] c2 = new char[to - from];
 System.arraycopy(c, from, c2, 0, c2.length);
 return c2;
 }

 private static char[] substring(char[] c, int from) {
 return substring(c, from, c.length);
 }

 private static char[] trim(char[] c) {
 if (c.length == 0) {
 return c;
 }
 return new String(c).trim().toCharArray();
 }

 private static char[] concat(char[] c1, char[] c2) {
 char[] c = new char[c1.length + c2.length];
 System.arraycopy(c1, 0, c, 0, c1.length);
 System.arraycopy(c2, 0, c, c1.length, c2.length);
 return c;
 }

 boolean isWhitespace() {
 return text == null || trim(text).length == 0;
 }

}

  • Next step is to create the LoginAction which will be do the business processing of whether usename and password are correct .
package com.login;

import com.opensymphony.xwork2.ActionSupport;

public class LoginAction extends ActionSupport {

	private static final long serialVersionUID = 1L;
	private String username;
	private String password;

	public String login(){
		if(username.equals("whyjava") && password.equals("password")){
			addActionMessage("You are successfully logged in.");
			return SUCCESS;
		}
		addActionError("Username and Password Combination doesnot match.");
		return INPUT;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}

  • Next we need to modify struts.xml so that we LoginAction is configured.
<?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>
	<include file="struts-default.xml"></include>
<package name="member" namespace="/member" extends="struts-default">
		<action name="login">
			<result>/login.jsp</result>
		</action>
		<action name="home" method="login" class="com.login.LoginAction">
			<result>/home.jsp</result>
			<result name="input">/login.jsp</result>
		</action>
	</package>
</struts>
  • finally we need to add one more jsp which is home.jsp which will be called when user is able to successfully login.
<%@ 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>Home Page</title>
</head>
<body>
<s:actionmessage />

</body>
</html>
  • Now try running this application by right click on project run as > web application and click http://localhost:8080.

index

  • Now click on the login link you will be taken to the login page

login

  • Now enter username as whyjava and password as password you will be an struts 2 action will fire and you will be taken to the home page.

home

I have tried to cover all the steps you will need to start of your first struts 2 project. Hope you all find this post useful.

If you need to start learning struts2 you can read Struts 2 in Action. It is a very good book to get a very good understanding of all the struts 2 concepts.

144 thoughts on “Creating Struts2 application on Google App Engine (GAE)”

  1. I also started studying GAE. As I feel happy that now I have got Java managed hosting. But I got stuck with it DB interaction. I have still pending that thing. But I will look into this JPA stuff.

    I am thankful for your post, as I am gona try it. 🙂

  2. Nice work brother! Have you had any experience using the Google UserService in an action? I am trying to use the Google Sign-on code and having trouble getting a valid URL produced.

    public String execute() {
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();
    if (user != null) {
    return SUCCESS;
    } else {
    logonUrl = userService.createLoginURL(req.getRequestURI());
    return INPUT;
    }
    }

    /home.jsp
    ${logonUrl}

    1. Yes i did used Google UserService and was able to implement in my code.I think what you are trying to do is redirect the user to your own custom page “home.jsp”. When you are using Google UserService for sign-on in development machine it will create a dummy login page for you to sign-on but when you deploy this application on Google Application Engine then google will produce the google sign in page you see when you sign in to google.
      If you need to use your custom page you need to design User Management yourself.

      1. The problem is only when running the development server. This code works fine once deployed to GAE. Have you discovered a way to adapt the code S2 action/result mappings to run in development?

        Scott

  3. Great work! I am finding trouble in adding Struts library to the app. When I add the jar. it all goes to the src folder. Detailed steps on the Step 4 will be helpful.

    1. Hi Venkat,
      I didn’t get your problem the basic thing you have to do in step 4 is to add all the above mentioned jars in the WEB-INF/lib folder which got created when you clicked the create a google web application project. Second thing you have to do is to add this to eclipse build path so that you can use these libraries.
      If you are facing any other problem please explain in detail.

  4. Anybody else getting:
    java.lang.NoClassDefFoundError: javax.swing.tree.TreeNode is a restricted class. Please see the Google App Engine developer’s guide for more details.

    It seems that freemarker extends some swing class

  5. When I run this app on my machine (another machine. Previous one worked fine) I receive the following error:
    HTTP ERROR: 500
    java.lang.NoClassDefFoundError: javax.swing.tree.TreeNode is a restricted class. Please see the Google App Engine developer’s guide for more details.
    RequestURI=/member/login

    Caused by:
    javax.servlet.ServletException: java.lang.NoClassDefFoundError: javax.swing.tree.TreeNode is a restricted class. Please see the Google App Engine developer’s guide for more details.
    at org.apache.jasper.runtime.PageContextImpl.doHandlePageException(PageContextImpl.java:825)
    at org.apache.jasper.runtime.PageContextImpl.access$1100(PageContextImpl.java:64)
    at org.apache.jasper.runtime.PageContextImpl$12.run(PageContextImpl.java:745)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.apache.jasper.runtime.PageContextImpl.handlePageException(PageContextImpl.java:743)
    at org.apache.jsp.login_jsp._jspService(login_jsp.java:86)
    at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:94)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
    at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:324)
    at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:292)
    at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:236)
    at com.google.appengine.tools.development.PrivilegedJspServlet.access$101(PrivilegedJspServlet.java:23)
    at com.google.appengine.tools.development.PrivilegedJspServlet$2.run(PrivilegedJspServlet.java:59)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.google.appengine.tools.development.PrivilegedJspServlet.service(PrivilegedJspServlet.java:57)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:362)
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:712)
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
    at com.google.apphosting.utils.jetty.DevAppEngineWebAppContext.handle(DevAppEngineWebAppContext.java:54)
    at org.mortbay.jetty.servlet.Dispatcher.forward(Dispatcher.java:268)

  6. I am getting the following error too..
    HTTP ERROR: 500
    java.lang.NoClassDefFoundError: javax.swing.tree.TreeNode is a restricted class. Please see the Google App Engine developer’s guide for more details.
    RequestURI=/member/login

    Caused by:
    javax.servlet.ServletException: java.lang.NoClassDefFoundError: javax.swing.tree.TreeNode is a restricted class. Please see the Google App Engine developer’s guide for more details.
    at org.apache.jasper.runtime.PageContextImpl.doHandlePageException(PageContextImpl.java:825)
    at org.apache.jasper.runtime.PageContextImpl.access$1100(PageContextImpl.java:64)
    at org.apache.jasper.runtime.PageContextImpl$12.run(PageContextImpl.java:745)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.apache.jasper.runtime.PageContextImpl.handlePageException(PageContextImpl.java:743)
    at org.apache.jsp.login_jsp._jspService(login_jsp.java:86)
    at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:94)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
    at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:324)
    at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:292)
    at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:236)
    at com.google.appengine.tools.development.PrivilegedJspServlet.access$101(PrivilegedJspServlet.java:23)
    at com.google.appengine.tools.development.PrivilegedJspServlet$2.run(PrivilegedJspServlet.java:59)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.google.appengine.tools.development.PrivilegedJspServlet.service(PrivilegedJspServlet.java

    Don’t know how to fix it yet. The above is the message I got on the web page, but on the console I see the following message:-
    “Oct 17, 2009 3:40:25 PM com.opensymphony.xwork2.util.logging.commons.CommonsLogger warn
    WARNING: No configuration found for the specified action: ‘home’ in namespace: ‘/member’. Form action defaulting to ‘action’ attribute’s literal value.”

    Can someone help please?

  7. I am using struts 2.1.6 too.. but I realized I am using google app engine 1.2.6 (in the blog above its actually 1.2.2) does that make a difference?

    1. Today, i had some time, so i looked into this problem. This problem is coming with Google App Engine 1.2.6 till 1.2.5 everything worked fine.I have updated the post with the solution to this problem. You can also see the solution from this link.

  8. I am getting this error. when trying to click on a link for an action. Do you have any idea about the problem. Thanks in advance.

    Srini

    java.security.AccessControlException: access denied (java.io.FilePermission jar:file:\C:\Users\seenu\eclipse-workspaces\finwebapp_ws\finwebapp\war\WEB-INF\lib\struts2-core-2.1.8.jar read)
    at java.security.AccessControlContext.checkPermission(AccessControlContext.java:323)
    at java.security.AccessController.checkPermission(AccessController.java:546)
    at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
    at com.google.appengine.tools.development.DevAppServerFactory$CustomSecurityManager.checkPermission(DevAppServerFactory.java:151)
    at java.lang.SecurityManager.checkRead(SecurityManager.java:871)
    at java.util.zip.ZipFile.(ZipFile.java:109)
    at java.util.jar.JarFile.(JarFile.java:133)
    at java.util.jar.JarFile.(JarFile.java:70)
    at com.opensymphony.xwork2.util.FileManager$JarEntryRevision.needsReloading(FileManager.java:264)
    at com.opensymphony.xwork2.util.FileManager.fileNeedsReloading(FileManager.java:70)
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.needsReload(XmlConfigurationProvider.java:324)
    at org.apache.struts2.config.StrutsXmlConfigurationProvider.needsReload(StrutsXmlConfigurationProvider.java:168)
    at com.opensymphony.xwork2.config.ConfigurationManager.conditionalReload(ConfigurationManager.java:220)

    1. Hello
      I didnt faced this problem. I am using struts 2.1.6 and from your exception stacktrace it seems that you are using struts 2.1.8. So, may be there is some unwanted operation being done in struts 2.1.8. Can you upload the code somewhere and share the link with me so i can take a look at that.

  9. Thank you for youe reply. Yeah, I was using struts 2.1.8. I tried using 2.1.6 and it seems to work fine. Do you want me to upload my app which throws this above exception?

  10. Be carefull, yesterday evening i’ve lost 2 hours trying to understand why I’ve got the following error (I’m working under MacOs X -snow leopard – for few weeks) :

    HTTP ERROR: 404

    result ‘null’ not found

    RequestURI=/member/login

    In fact, this is the browser output for the first problem starting by “SEVERE: Unable to set parameter [location] in result of type [org.apache.struts2.dispatcher.ServletDispatcherResult]”

    I’ve thought it was an policy error or an source file (jsp) encoding problem…

    1. I also struggled with this problem this morning.
      As I expected to see the “SEVERE: Unable to set parameter [location] in result of type [org.apache.struts2.dispatcher.ServletDispatcherResult]” error, I saw this message

      HTTP ERROR: 404
      result ‘null’ not found
      RequestURI=/member/login

      I thought there was something wrong with my application until I tried to go on to next step.
      Add some comment of this issue would be helpful for someone facing the problem. at least can save some time. 🙂

    2. I’ll bet money you didn’t put it in the the jsp file in the right folder. it should be in the same folder as the index.html . If you did it in a hurry, I bet you put it in the /war/WEB-INF/lib folder instead of the /war/WEB-INF folder. It should be in the latter.

  11. Hi,

    can you please tell me issue , when i am trying to above login application i am gettong folloing error

    Jan 1, 2010 7:20:43 AM com.google.apphosting.utils.jetty.JettyLogger warn
    WARNING: Could not instantiate listener com.login.OgnlListener
    java.lang.ClassNotFoundException: com.login.OgnlListener
    at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at com.google.appengine.tools.development.IsolatedAppClassLoader.loadClass(IsolatedAppClassLoader.java:151)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
    at org.mortbay.jetty.handler.ContextHandler.loadClass(ContextHandler.java:1003)
    at org.mortbay.jetty.webapp.WebXmlConfiguration.initListener(WebXmlConfiguration.java:629)
    at org.mortbay.jetty.webapp.WebXmlConfiguration.initWebXmlElement(WebXmlConfiguration.java:367)
    at org.mortbay.jetty.webapp.WebXmlConfiguration.initialize(WebXmlConfiguration.java:289)
    at org.mortbay.jetty.webapp.WebXmlConfiguration.configure(WebXmlConfiguration.java:222)
    at org.mortbay.jetty.webapp.WebXmlConfiguration.configureWebApp(WebXmlConfiguration.java:180)
    at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1215)
    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 org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:117)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:40)
    at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:117)
    at org.mortbay.jetty.Server.doStart(Server.java:217)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:40)
    at com.google.appengine.tools.development.JettyContainerService.startContainer(JettyContainerService.java:181)
    at com.google.appengine.tools.development.AbstractContainerService.startup(AbstractContainerService.java:116)
    at com.google.appengine.tools.development.DevAppServerImpl.start(DevAppServerImpl.java:217)
    at com.google.appengine.tools.development.gwt.AppEngineLauncher.start(AppEngineLauncher.java:86)
    at com.google.gwt.dev.HostedMode.doStartUpServer(HostedMode.java:365)
    at com.google.gwt.dev.HostedModeBase.startUp(HostedModeBase.java:589)
    at com.google.gwt.dev.HostedModeBase.run(HostedModeBase.java:397)
    at com.google.gwt.dev.HostedMode.main(HostedMode.java:232)
    Jan 1, 2010 7:20:44 AM com.opensymphony.xwork2.util.logging.commons.CommonsLogger error
    SEVERE: Dispatcher initialization failed
    Unable to load configuration. – action – file:/C:/work/workspace/teststruds/war/WEB-INF/classes/struts.xml:11:68
    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)
    at org.apache.struts2.dispatcher.FilterDispatcher.init(FilterDispatcher.java:190)
    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 org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:117)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:40)
    at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:117)
    at org.mortbay.jetty.Server.doStart(Server.java:217)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:40)
    at com.google.appengine.tools.development.JettyContainerService.startContainer(JettyContainerService.java:181)
    at com.google.appengine.tools.development.AbstractContainerService.startup(AbstractContainerService.java:116)
    at com.google.appengine.tools.development.DevAppServerImpl.start(DevAppServerImpl.java:217)
    at com.google.appengine.tools.development.gwt.AppEngineLauncher.start(AppEngineLauncher.java:86)
    at com.google.gwt.dev.HostedMode.doStartUpServer(HostedMode.java:365)
    at com.google.gwt.dev.HostedModeBase.startUp(HostedModeBase.java:589)
    at com.google.gwt.dev.HostedModeBase.run(HostedModeBase.java:397)
    at com.google.gwt.dev.HostedMode.main(HostedMode.java:232)
    Caused by: Action class [com.login.LoginAction] not found – action – file:/C:/work/workspace/teststruds/war/WEB-INF/classes/struts.xml:11:68
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.verifyAction(XmlConfigurationProvider.java:409)
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.addAction(XmlConfigurationProvider.java:354)
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.addPackage(XmlConfigurationProvider.java:468)
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.loadPackages(XmlConfigurationProvider.java:264)
    at org.apache.struts2.config.StrutsXmlConfigurationProvider.loadPackages(StrutsXmlConfigurationProvider.java:111)
    at com.opensymphony.xwork2.config.impl.DefaultConfiguration.reloadContainer(DefaultConfiguration.java:193)
    at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:55)
    … 24 more
    Jan 1, 2010 7:20:44 AM com.google.apphosting.utils.jetty.JettyLogger warn
    WARNING: failed struts2
    Unable to load configuration. – action – file:/C:/work/workspace/teststruds/war/WEB-INF/classes/struts.xml:11:68
    at org.apache.struts2.dispatcher.Dispatcher.init(Dispatcher.java:431)
    at org.apache.struts2.dispatcher.FilterDispatcher.init(FilterDispatcher.java:190)
    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 org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:117)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:40)
    at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:117)
    at org.mortbay.jetty.Server.doStart(Server.java:217)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:40)
    at com.google.appengine.tools.development.JettyContainerService.startContainer(JettyContainerService.java:181)
    at com.google.appengine.tools.development.AbstractContainerService.startup(AbstractContainerService.java:116)
    at com.google.appengine.tools.development.DevAppServerImpl.start(DevAppServerImpl.java:217)
    at com.google.appengine.tools.development.gwt.AppEngineLauncher.start(AppEngineLauncher.java:86)
    at com.google.gwt.dev.HostedMode.doStartUpServer(HostedMode.java:365)
    at com.google.gwt.dev.HostedModeBase.startUp(HostedModeBase.java:589)
    at com.google.gwt.dev.HostedModeBase.run(HostedModeBase.java:397)
    at com.google.gwt.dev.HostedMode.main(HostedMode.java:232)
    Caused by: Unable to load configuration. – action – file:/C:/work/workspace/teststruds/war/WEB-INF/classes/struts.xml:11:68
    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)
    … 22 more
    Caused by: Action class [com.login.LoginAction] not found – action – file:/C:/work/workspace/teststruds/war/WEB-INF/classes/struts.xml:11:68
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.verifyAction(XmlConfigurationProvider.java:409)
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.addAction(XmlConfigurationProvider.java:354)
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.addPackage(XmlConfigurationProvider.java:468)
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.loadPackages(XmlConfigurationProvider.java:264)
    at org.apache.struts2.config.StrutsXmlConfigurationProvider.loadPackages(StrutsXmlConfigurationProvider.java:111)
    at com.opensymphony.xwork2.config.impl.DefaultConfiguration.reloadContainer(DefaultConfiguration.java:193)
    at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:55)
    … 24 more
    Jan 1, 2010 7:20:44 AM com.google.apphosting.utils.jetty.JettyLogger warn
    WARNING: Failed startup of context com.google.apphosting.utils.jetty.DevAppEngineWebAppContext@a5586d{/,C:\work\workspace\teststruds\war}
    Unable to load configuration. – action – file:/C:/work/workspace/teststruds/war/WEB-INF/classes/struts.xml:11:68
    at org.apache.struts2.dispatcher.Dispatcher.init(Dispatcher.java:431)
    at org.apache.struts2.dispatcher.FilterDispatcher.init(FilterDispatcher.java:190)
    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 org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:117)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:40)
    at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:117)
    at org.mortbay.jetty.Server.doStart(Server.java:217)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:40)
    at com.google.appengine.tools.development.JettyContainerService.startContainer(JettyContainerService.java:181)
    at com.google.appengine.tools.development.AbstractContainerService.startup(AbstractContainerService.java:116)
    at com.google.appengine.tools.development.DevAppServerImpl.start(DevAppServerImpl.java:217)
    at com.google.appengine.tools.development.gwt.AppEngineLauncher.start(AppEngineLauncher.java:86)
    at com.google.gwt.dev.HostedMode.doStartUpServer(HostedMode.java:365)
    at com.google.gwt.dev.HostedModeBase.startUp(HostedModeBase.java:589)
    at com.google.gwt.dev.HostedModeBase.run(HostedModeBase.java:397)
    at com.google.gwt.dev.HostedMode.main(HostedMode.java:232)
    Caused by: Unable to load configuration. – action – file:/C:/work/workspace/teststruds/war/WEB-INF/classes/struts.xml:11:68
    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)
    … 22 more
    Caused by: Action class [com.login.LoginAction] not found – action – file:/C:/work/workspace/teststruds/war/WEB-INF/classes/struts.xml:11:68
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.verifyAction(XmlConfigurationProvider.java:409)
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.addAction(XmlConfigurationProvider.java:354)
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.addPackage(XmlConfigurationProvider.java:468)
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.loadPackages(XmlConfigurationProvider.java:264)
    at org.apache.struts2.config.StrutsXmlConfigurationProvider.loadPackages(StrutsXmlConfigurationProvider.java:111)
    at com.opensymphony.xwork2.config.impl.DefaultConfiguration.reloadContainer(DefaultConfiguration.java:193)
    at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:55)
    … 24 more

    1. Please look through the logs carefully..its clearly saying the “Caused by: Action class [com.login.LoginAction] not found ” . So, it is not able to find the LoginAction class.

  12. Thanks for this post.
    I’m trying this with struts 2.1.8 and it works when deployed to google, but running on the SDK gives the following error and no pages are found (eg. login.jsp) when trying to open them in a browser.

    22.1.2010 19:34:07 com.google.apphosting.utils.jetty.JettyLogger warn
    WARNING: failed struts2
    javax.xml.transform.TransformerFactoryConfigurationError: Provider org.apache.xalan.processor.TransformerFactoryImpl not found
    at javax.xml.transform.TransformerFactory.newInstance(TransformerFactory.java:109)
    at com.opensymphony.xwork2.util.DomHelper$DOMBuilder.(DomHelper.java:159)
    at com.opensymphony.xwork2.util.DomHelper.parse(DomHelper.java:107)
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.loadConfigurationFiles(XmlConfigurationProvider.java:893)
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.loadDocuments(XmlConfigurationProvider.java:143)
    at com.opensymphony.xwork2.config.providers.XmlConfigurationProvider.init(XmlConfigurationProvider.java:110)
    at com.opensymphony.xwork2.config.impl.DefaultConfiguration.reloadContainer(DefaultConfiguration.java:168)
    at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:55)
    at org.apache.struts2.dispatcher.Dispatcher.init_PreloadConfiguration(Dispatcher.java:374)

  13. Solved #18 myself. The problem was that I had GWT libs in my project. Apparently this doesn’t work out of the box with GWT.
    I don’t know why there is a difference between the SDK and when deploying to google though.

  14. Hi,

    I have an application, Struts2 + GAE 1.3 , Everything is working fine, But I am not able to access admin console, So I can manipulate the data store and indexes etc.

    Any one who are working with struts 2.1.8 + GAE 1.3 please tell me Is admin console working out there..

    Thanks

    1. Add these configurations to your web.xml, in the filter section:

      Probabily your initial settings are like this:

      struts2
      /*

      Change to this:

      struts2
      *.action

      And if you need some ‘extension’ to this url pattern (e.g. in my case I had jquery plugin, which have some static .js files thata to be load from jar files)

      struts2
      /struts/*

      struts2
      /static/*

      struts2
      *.action

      With this configuration, I can see the admin console at the usual address: http://localhost:8888/_ah/admin

      1. Sorry, wordpress delete html/xml entities….

        <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/struts/*</url-pattern>
        </filter-mapping>

      2. Add these configurations to your web.xml, in the filter section:

        Probabily your initial settings are like this:

        <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
        </filter-mapping>

        Change to this:

        <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*.action</url-pattern>
        </filter-mapping>

        And if you need some ‘extension’ to this url pattern (e.g. in my case I had jquery plugin, which have some static .js files thata to be load from jar files)

        <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/struts/*</url-pattern>
        </filter-mapping>

        <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/static/*</url-pattern>
        </filter-mapping>

        <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>*.action</url-pattern>
        </filter-mapping>

        With this configuration, I can see the admin console at the usual address: http://localhost:8888/_ah/admin

  15. Hi,
    I followed your tutorial using GAE 1.3.1.
    When I click in login link, I get the following error.
    I tried to solve it, but could not find any hint.
    Do you know what is the cause of this error.

    Thanks,
    Ravi

    SEVERE:

    Expression parameters.templateDir is undefined on line 23, column 15 in template/xhtml/form.ftl.
    The problematic instruction:
    ———-
    ==> ${parameters.templateDir} [on line 23, column 13 in template/xhtml/form.ftl]
    in include “/${parameters.templateDir}/xhtml/form-validate.ftl” [on line 23, column 1 in template/xhtml/form.ftl]
    ———-

    Java backtrace for programmers:
    ———-
    freemarker.core.InvalidReferenceException: Expression parameters.templateDir is undefined on line 23, column 15 in template/xhtml/form.ftl.
    at freemarker.core.TemplateObject.assertNonNull(TemplateObject.java:124)
    at freemarker.core.Expression.getStringValue(Expression.java:118)
    at freemarker.core.Expression.getStringValue(Expression.java:93)
    at freemarker.core.DollarVariable.accept(DollarVariable.java:76)

  16. Hi,
    Thanks for giving this tutorial to us.In this tutorial after adding the login.jsp file i run as a web application.After clicking the login link on the index page it showing “HTTP ERROR 404
    Problem accessing /member/login. Reason:
    result ‘null’ not found”

    I placed member folder in war folder.Right now i am not able to send screen shots here.

    Thanking you,

    prateep g

  17. I’m having some problems getting this to work with app engine 1.3.4. When I try and run the app, it’s not processing the jsp, but sending back the actually JSP code, eg:

    Anyone else seen this? There are no errors in the console so I’m not sure how to debug it either unfortunately.

    1. Just to reply to my own question, I have now got this working. I think the problem may have been that I had the struts.xml in the WEB-INF/ dir, and not the src/ dir.

  18. Hi , do you test IOC with struts2 and spring on GAE ? I want to let spring create my actions but I don,t know it,s work with OgnlListener ;

  19. I have a problem at run the project and directly call at Login link:

    “HTTP ERROR 404

    Problem accessing /pages/login.jsp. Reason:

    /pages/login.jsp

    Powered by Jetty://”

    1. If you did it in a hurry, I bet you put it in the /war/WEB-INF/lib folder instead of the /war/WEB-INF folder. It should be in the latter.

      1. I kept my files in /war/WEB-INF folder ( login.jsp) still getting.

        HTTP ERROR 404

        Problem accessing /login.jsp. Reason:

        /login.jsp

        Powered by Jetty://

  20. i am from peru. Your post very good

    I wanted to know if you can put (struts2 + tiles + JPA) in a google app engine project

    Also, if you upload your code to google code and if I can not sync with my eclipse Mercurial

  21. Hi. I had an error at my application:
    HTTP ERROR: 404
    result ‘null’ not found

    So I created OgnlListener.
    But now when I call to actions, I have empty page, without any text. Why?

  22. Excellent work.

    Can you send us the sample code? I´m trying to do the same code but it gives me an error.

    java.lang.NoClassDefFoundError: ognl/OgnlRuntime

    I´m desesperated. Please help me.

    Thanks

  23. Hi , I use your idea to make struts 2.1.8 play on GAE, work fine and I start to write a article about this, i need make reference to the author of this blog , can you send me your name for references on my work?

  24. Hi all,

    I’m also facing the Error 404 NOT_FOUND error, doesn’t anybody have a workaround for that ?

    I’m with MAC, Struts-2.1.6 and GAE 1.4.3

    Thanks a lot for the help.

  25. Just an fyi, the new version of Struts is missing javassist.jar. Looks like this is also a dependency now with you will need to add to lib directory

    1. Sorry, one note:
      For struts 2.2.3 it needed to add the common-lang-2.5.jar and javassist-3.11.0.jar dependencies to the class-path of the proyect as well the dependencies explained above

  26. it took me about 6 hours, in two days to make this work because of my version of GAE , now i updated it to 1.5.2 and works great….
    i had the same problem as angela, you could add to your post that in case you’re using struts 2.2.3 you’ll need to add common-lang-2.5.jar and javassist-3.11.0.jar dependencies to the class-path.

    well , i can get back to work now that all is set up.
    thanks for the tutorial

    greetings from argentina!
    pablo

  27. hi!,I like your writing very much! proportion we keep in touch more about your article on AOL? I require an expert in this house to resolve my problem. Maybe that is you! Taking a look forward to peer you.

  28. Hi Am getting the below error please give me some solution for this

    There is no Action mapped for action name login. – [unknown location]
    at com.opensymphony.xwork2.DefaultActionProxy.prepare(DefaultActionProxy.java:177)
    at org.apache.struts2.impl.StrutsActionProxy.prepare(StrutsActionProxy.java:61)
    at org.apache.struts2.impl.StrutsActionProxyFactory.createActionProxy(StrutsActionProxyFactory.java:39)
    at com.opensymphony.xwork2.DefaultActionProxyFactory.createActionProxy(DefaultActionProxyFactory.java:47)
    at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:458)
    at org.apache.struts2.dispatcher.FilterDispatcher.doFilter(FilterDispatcher.java:395)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.google.appengine.tools.development.HeaderVerificationFilter.doFilter(HeaderVerificationFilter.java:35)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.google.appengine.api.blobstore.dev.ServeBlobFilter.doFilter(ServeBlobFilter.java:60)
    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:122)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.google.appengine.tools.development.BackendServersFilter.doFilter(BackendServersFilter.java:97)
    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:78)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at com.google.appengine.tools.development.JettyContainerService$ApiProxyHandler.handle(JettyContainerService.java:362)
    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)
    Jan 22, 2012 5:43:43 PM com.opensymphony.xwork2.util.logging.commons.CommonsLogger warn
    WARNING: Could not find action or result

  29. Pingback: JavaPins
  30. We stumbled over here by a different web page
    and thought I may as well check things out. I like what I see so now i’m following you. Look forward to looking at your web page yet again.

  31. Its like you read my mind! You appear to know so much about this,
    like you wrote the book in it or something. I think that you can do with some pics
    to drive the message home a bit, but other than that,
    this is great blog. An excellent read. I will certainly be back.

  32. I’m extremely impressed with your writing skills and also with the structure on your blog. Is this a paid topic or did you modify it yourself? Either way stay up the excellent high quality writing, it’s rare
    to see a nice blog like this one nowadays..

  33. I have to voice my admiration for your generosity for those individuals
    that have the need for assistance with this one field.

    Your very own dedication to getting the message across has been wonderfully productive and
    has continually enabled individuals like me to achieve their dreams.

    Your own useful facts can mean this much to me and even more to my colleagues.
    With thanks; from everyone of us.

  34. Attractive section of content. I just stumbled upon your weblog and in accession capital to assert that
    I acquire in fact enjoyed account your blog posts. Any way I’ll be subscribing to your feeds and even I achievement you access consistently rapidly.

  35. I loved as much as you will receive carried out right here.
    The sketch is tasteful, your authored subject matter stylish.
    nonetheless, you command get got an nervousness over that you wish be delivering the
    following. unwell unquestionably come further formerly again as exactly the same nearly a lot often
    inside case you shield this hike.

  36. You are so cool! I do not think I have read a single thing
    like this before. So great to find somebody
    with some original thoughts on this topic. Really.
    . many thanks for starting this up. This site is something that is needed on the web, someone with a little originality!

  37. I have to thank you for the efforts you’ve put in writing this site. I’m
    hoping to view the same high-grade content by you
    in the future as well. In truth, your creative writing abilities has inspired me
    to get my own site now 😉

  38. Its like you read my mind! You appear to know a lot about this,
    like you wrote the book in it or something. I think that you can
    do with a few pics to drive the message home a bit, but instead
    of that, this is fantastic blog. An excellent read.

    I will definitely be back.

  39. Hi everybody, here every one is sharing such know-how, therefore it’s nice to read this web site, and I used to pay a quick visit this website everyday.

  40. I have read several just right stuff here. Definitely price bookmarking for
    revisiting. I surprise how much effort you place to create one of these great informative website.

  41. I’m not sure exactly why but this weblog is loading very slow for me.
    Is anyone else having this issue or is it a problem on
    my end? I’ll check back later on and see if the
    problem still exists.

  42. I love your blog.. very nice colors & theme. Did you create this website yourself oor
    did you hire someone to do it for you? Plz answer back as I’m looking to design
    my owwn blog and would like to find out whre u got this from.
    many thanks

  43. My spouse and I stumbled over here coming from
    a different page and thought I might as well check things out.
    I like what I see so i am just following you. Look forward to going over your web page yet
    again.

  44. Ѕome people find teir party entertainment for children by party hire, services,
    DJs, party planners, planning services aand amusement organizations.

    But remember, althoսɡh competition is always fun,
    make surе the ƙids knoա thesе are only gɑmes.
    ” How backwards you go depends upon your imagination.

  45. Excellent beat ! I would like to apprentice at the same time as you amend your website, how can i subscribe for a blog site?

    The account aided me a applicable deal. I had been tiny bit familiar of this
    your broadcast offered brilliant clear concept

  46. Such comments coming from a man appointed by Mubarak himself left
    many unsure of what to think. The hotels stand near to the famous tourist destinations that assure easy visit for
    the tourists from the hotels. I’m glad that they are so thorough and sensitive about such a delicate situation.
    Once the military asserted its allegiance to the people rather than
    to him, Mubarak had no choice, and stepped down.

Leave a reply to poczta głosowa Cancel reply