GAE/j用struts2環境構築

GAE/j用struts2環境構築ASのおもちゃ箱開発ブログ仕事でGAE/j上でstruts2を使いたかったけど、完全なまとめページがなかったので、自分でまとめました。
主に参考にしたサイト:http://d.hatena.ne.jp/suzune64/20090613/1244872314
同僚にも助言をもらいました。感謝!!

<環境>
eclipse: Pleiades All in one for Ultimate Developers 3.5 Galileo
GAEプラグイン:http://dl.google.com/eclipse/plugin/3.5 からインストール

(1)GAEプロジェクトを作成します。
・Google Webツールキットは使わないので、チェックを外します。

(2)struts2ライブラリをダウンロードします。
・ダウンロード元:http://struts.apache.org/download.cgi
・今回は、struts-2.2.1-lib.zipをダウンロードします。

(3)javassistライブラリをダウンロードします。
何故かわからないのですが、無いと起動時にエラーを吐くのでダウンロードします。
・ダウンロード元:http://ftp.us.xemacs.org/pub/mirrors/maven2/jboss/javassist/
・今回は、javassist-3.7.ga.jar をダウンロードします。

(4)プロジェクトにstruts2ライブラリをコピー
・ (2)でダウンロードしたファイルを解凍し、その中の以下のファイルをwar\WEB-INF\libにコピーします。
– commons-logging-1.0.4.jar
– commons-fileupload-1.2.1.jar
– commons-io-1.3.2.jar
– freemaker-2.3.16.jar
– ognl-3.0.jar
– struts2-core-2.2.1.jar
– xwork-core-2.2.1.jar

(5)プロジェクトにjavassistライブラリをコピー
・ (3)でダウンロードしたファイルの以下のファイルをwar\WEB-INF\libにコピーします。
– javassist-3.7.ga.jar

(6)ライブラリをクラスパスに追加
プロジェクトのプロパティ→Javaのビルド・パス→外部JARの追加を選んで、(4)(5)でコピーしたファイルを追加します。

(7)appengine-web.xmlを編集

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <application></application>
    <version>1</version>

    <!-- Configure java.util.logging -->
    <system-properties>
        <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>

        <property name="file.encoding" value="UTF-8" />
        <property name="DEFAULT_ENCODING" value="UTF-8" />
    </system-properties>

    <sessions-enabled>true</sessions-enabled>

    <ssl-enabled>true</ssl-enabled>

</appengine-web-app>

(8)web.xmlを編集
・filter-nameの”strutstest”は適当に変更。
・param-valueとlistener-classの”strutstest”は、自分が作ったパッケージ名に変更します。
・filter-classを”org.apache.struts2.dispatcher.FilterDispatcher”と書くと、/_ah_admin/ が使えません。

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">

    <filter>
        <filter-name>strutstest</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
        <init-param>
            <param-name>actionPackages</param-name>
            <param-value>strutstest.action</param-value>
        </init-param>
    </filter>

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

    <listener>
        <listener-class>strutstest.InitListener</listener-class>
    </listener>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>

</web-app>

(9)struts.xmlを追加
srcディレクトリの下にstruts.xmlを追加します。
・packageのname属性は、適当に変更
・actionのclass属性は、”strutstest”を自分で作ったパッケージ名に合わせて変更
・/_ah/admin/ を使うために、struts.action.excludePatternが必要です。

<?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.ui.theme" value="simple" />

    <constant name="struts.action.excludePattern" value="/_ah/.*" />

    <package name="strutstest" namespace="/" extends="struts-default">

        <global-results>
            <result name="failed">/WEB-INF/jsp/failed.jsp
            </result>
        </global-results>

        <action name="Index" class="strutstest.Index">
            <result name="success">/WEB-INF/jsp/index.jsp
            </result>
        </action>

    </package>

</struts>

(10)InitListenerクラスを追加します。
src/(パッケージ名)の下に、InitListener.javaを追加します。
場所は、(8)web.xmlで指定しています。
ソース中のパッケージ名は、自分で作った名前に合わせて変更してください。
詳細はわかりませんが、GAEで動かすためにセキュリティを無効にする必要があるようです。

package strutstest;

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 InitListener implements ServletContextListener,
        HttpSessionListener, HttpSessionAttributeListener {

    public void contextDestroyed(ServletContextEvent arg0) {
        // TODO 自動生成されたメソッド・スタブ

    }

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

    public void sessionCreated(HttpSessionEvent arg0) {
        // TODO 自動生成されたメソッド・スタブ

    }

    public void sessionDestroyed(HttpSessionEvent arg0) {
        // TODO 自動生成されたメソッド・スタブ

    }

    public void attributeAdded(HttpSessionBindingEvent arg0) {
        // TODO 自動生成されたメソッド・スタブ

    }

    public void attributeRemoved(HttpSessionBindingEvent arg0) {
        // TODO 自動生成されたメソッド・スタブ

    }

    public void attributeReplaced(HttpSessionBindingEvent arg0) {
        // TODO 自動生成されたメソッド・スタブ

    }

}

(11)テスト用アクションクラスの追加
src/(パッケージ名)の下に、Index.javaを追加します。
場所は、(8)web.xmlと(9)struts.xmlで指定しています。
パッケージ名は、自分が作ったものに合わせて変更。

package strutstest;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.interceptor.RequestAware;
import org.apache.struts2.interceptor.ServletRequestAware;
import org.apache.struts2.interceptor.SessionAware;

public class Index implements RequestAware, SessionAware, ServletRequestAware {

    protected Map<String, Object> requestMap;
    protected Map<String, Object> sessionMap;
    protected HttpServletRequest request;

    @Override
    public void setRequest(Map<String, Object> requestMap) {
        this.requestMap = requestMap;
    }

    @Override
    public void setSession(Map<String, Object> sessionMap) {
        this.sessionMap = sessionMap;
    }

    @Override
    public void setServletRequest(HttpServletRequest request) {
        this.request = request;
    }

    public String execute() throws Exception {

        requestMap.put("MESSAGE", "Hello World!!");

        return "success";
    }

}

(12)テスト用のjspの追加
今回は、war/WEB-INF/jsp/index.jspとして作成しました。
この場所は、(9)struts.xmlで指定しています。

<?xml version="1.0" encoding="UTF-8"?>
<%@ page contentType="text/html; charset=UTF-8" language="java" %>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
    <meta http-equiv="Content-Script-Type" content="text/javascript" />
    <meta http-equiv="Content-Style-Type" content="text/css" />
    <title>Index</title>
  </head>

  <body>
    <p><s:property value="#request.MESSAGE" /></p>
  </body>
</html>

(13)TextBlock.javaのオーバーライド
以下のエラーが出るので、src/freemarker/core/TextBlock.javaを追加します。
java.lang.NoClassDefFoundError: javax.swing.tree.TreeNode is a restricted class. Please see the Google App Engine developer’s guide for more details.

freemarker.core.TextBlock.java

/*
 * 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;
 }

}

(14)実行する
プロジェクトを右クリックして、実行→Webアプリケーションで、起動します。
ブラウザで、http://localhost:8888/Index.action を見ると、表示されるはずです。(ポート番号は環境によって違うかもしれません。)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です


*

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>