EchoとXoneでWebアプリを作る
沼倉 均
Numakura Hitoshi
■ はじめに
Webアプリ(ここではクライアント側でブラウザを利用するアプリをWebアプリと呼ぶことにします)を作る場合、ServletやJSPを'生'で使うのではなく、何らかのフレームワークが広く利用されるようになりました。フレームワークといえば、その知名度からいってもStrutsを利用することが多いのではと思われます。
StrutsはMVCでいうところのcontrol回りのフレームワークですから、主にModel回りのフレームワークであるXoneと組み合わせると効果的です。ただ、ここではStrutsではなく、Echoというフレームワークを組み合わせてサンプルを作ってみます。Echoを利用すると、Swingでアプリを作るような感覚で開発できます。結論からいえば、このフレームワークはかなり効率が良く、今後は実際の開発にも取り入れていこうかと考えています。
■ 準備
Echoは本体に加えて、追加のコンポーネント群であるEchoPoint、GUIデザインをグラフィカルに行えるEchoStudioなどがあります。今回はEcho本体だけを利用します。Echoは
http://www.nextapp.com/products/echo/
から、入手してください(ここではVersion1.1.2を使用)。また、Servletコンテナも必要ですが、これにはNetBeans3.6に附属のTomcat(5.0.19)を利用しました。
開発時にも実行時にも、Echoのクラスライブラリ(Echo.jar、EchoServer.jar)Xone関連のクラスライブラリ、Servlet関連のクラスライブラリが必要です。
また、「Java Web StartとXoneでリッチクライアントアプリを作る」で、利用したXoneのクラスとインスタンスを利用するので、適当なインスタンスを作っておいてください。
今回のシステム構成では、(Servletコンテナ)を同一のホストとしたので、xone.propertiesでのrouterの設定は「Direct」で行いました。
そうそう、重要なことを忘れていました。EchoはJavaScriptを利用するので、ブラウザでこれが無効に設定されていると動作しないのでご注意ください。
■ プログラム
プログラムは2つのファイルで構成されています。プログラム1はブラウザで表示されるメインのプログラムで、プログラム2はXoneにアクセスするためのファサード(入り口)のクラスです。
・プログラム1(ソースはこちら)
1 /*
2 * EchoSampleServlet.java
3 *
4 */
5
6 package xone_sample.echo;
7
8 import com.fiverworks.xone.model.XoneObject;
9
10 import java.sql.Timestamp;
11
12 import nextapp.echo.Button;
13 import nextapp.echo.Color;
14 import nextapp.echo.Component;
15 import nextapp.echo.ContentPane;
16 import nextapp.echo.EchoInstance;
17 import nextapp.echo.Filler;
18 import nextapp.echo.Label;
19 import nextapp.echo.Panel;
20 import nextapp.echo.Table;
21 import nextapp.echo.TextArea;
22 import nextapp.echo.Window;
23
24 import nextapp.echo.event.ActionEvent;
25 import nextapp.echo.event.ActionListener;
26
27 import nextapp.echo.table.DefaultTableModel;
28 import nextapp.echo.table.TableCellRenderer;
29 import nextapp.echo.table.TableModel;
30
31 import nextapp.echoservlet.EchoServer;
32
33 public class EchoSampleServlet extends EchoServer {
34
35 public void init() {
36 try {
37 super.init();
38 XoneFacade.init();
39 } catch (Exception ex) {
40 ex.printStackTrace();
41 }
42 }
43
44 public EchoInstance newInstance() {
45 return new EchoSample();
46 }
47
48 public void destroy() {
49 XoneFacade.shutdown();
50 super.destroy();
51 }
52 }
53
54 class EchoSample extends EchoInstance {
55
56 private XoneObject[] xos;
57 private TextArea textArea;
58 private int selectedRow;
59 private Button saveButton;
60 private Table table;
61
62 private TableModel createTableModel() {
63 this.xos = XoneFacade.getList();
64 Object[][] data = new Object[xos.length][5];
65 for (int i = 0; i < xos.length; i++) {
66 data[i][0] = xos[i].getName(); // オブジェクト名;
67 data[i][1] = xos[i].getElementValue("parent"); // 親フォルダ
68 data[i][2] = xos[i].getElementValue("name"); // ファイル名
69 data[i][3] = xos[i].getElementValue("lastModified"); // 最終更新日時
70 data[i][4] = xos[i].getElementValue("length"); // 長さ
71 }
72 Object[] header =
(Object[])(new String[]{"オブジェクト名", "親フォルダ",
"ファイル名", "最終更新日時", "長さ"});
73 DefaultTableModel model = new DefaultTableModel(data, header);
74 return model;
75 }
76
77 private Table createTable() {
78 table = new Table(createTableModel());
79 table.setCellMargin(3);
80 table.setBorderSize(2);
81 return table;
82 }
83
84 public Window init() {
85 Window window = new Window();
86 ContentPane content = new ContentPane();
87
88 window.setContent(content);
89
90 Label label = new Label("オブジェクト一覧");
91 label.setBackground(Color.GREEN);
92 content.add(label);
93 content.add(Filler.createVerticalStrut(5));
94
95 table = this.createTable();
96 table.setDefaultRenderer(Object.class, new MyRenderer());
97 content.add(table);
98 content.add(Filler.createVerticalStrut(10));
99
100 Label label1 = new Label("内容");
101 label1.setBackground(Color.GREEN);
102 content.add(label1);
103 content.add(Filler.createVerticalStrut(5));
104 textArea = new TextArea(50, 20);
105 content.add(textArea);
106 content.add(Filler.createVerticalStrut(5));
107
108 saveButton = new Button("上書き保存ボタン");
109 saveButton.setBackground(Color.MAGENTA);
110 selectedRow = -1; // 選択されていない状態(-1)にセット
111 // クリックされたら選択されているオブジェクトを上書き保存する
112 saveButton.addActionListener(new ActionListener() {
113 public void actionPerformed(ActionEvent actionEvent) {
114 if (selectedRow == -1) return;
115 XoneObject xo = xos[selectedRow];
116 // 内容を置き換えて保存
117 String body = textArea.getText();
118 xo.setElementValue("body", body);
119 xo.setElementValue("lastModified",
new Timestamp(System.currentTimeMillis()).toString());
120 xo.setElementValue("length", Integer.toString(body.length()));
121 XoneFacade.save(xo);
122 // tableを再初期化
123 table.setModel(createTableModel());
124 table.validate();
125 }
126 });
127
128 content.add(saveButton);
129 return window;
130 }
131
132 // cellのレンダラ用
133 private class MyRenderer implements TableCellRenderer, ActionListener {
134 public Component getTableCellRendererComponent(Table table, Object value,
int col, int row) {
135 if (col == 0) {
136 Button button = new Button((String)value);
137 button.setForeground(Color.WHITE);
138 button.setBackground(Color.BLUE);
139 button.addActionListener(this);
140 return button;
141 } else {
142 Panel cell = new Panel();
143 if (value != null) {
144 Label label = new Label(value.toString());
145 cell.add(label);
146 }
147 return cell;
148 }
149 }
150
151 public void actionPerformed(ActionEvent actionEvent) {
152 Button button = (Button)actionEvent.getSource();
153 String name = button.getText();
154 for (int i = 0; i < xos.length; i++) {
155 if (xos[i].getName().equals(name)) {
156 textArea.setText(xos[i].getElementValue("body"));
157 selectedRow = i;
158 break;
159 }
160 }
161 }
162 }
163 }
プログラム1のEchoSampleServletクラスは、EchoServerを継承していますが、これはjavax.servlet.http.HttpServletを継承していますからServletです。そこで、initメソッド(35行)でXoneFacadeのinitを行い、48行のdestroyメソッドでshutdownを行っています。これらはServletでは、それぞれサービス開始時と終了時に1度だけ実行されるものです。XoneFacadeでは、それぞれXoneへのログインとログアウトを行っています。
54行目からはEchoSampleクラスですが、Echoが始めての方は、まるでSwingのプログラムのようだと驚くのではないでしょうか。このようにHTMLのことなどまったく気にせず、イベントドリブンなプログラムを書くことができます。このクラスでの決まりは、EchoInstanceを継承することと84行目のようにinit()メソッドを実装することだけです。
1 /*
2 * XoneFacade.java
3 *
4 */
5
6 package xone_sample.echo;
7
8 import com.fiverworks.xone.XoneException;
9 import com.fiverworks.xone.model.XoneObject;
10 import com.fiverworks.xone.mw.Commands;
11 import com.fiverworks.xone.mw.MwMain;
12
13 import java.util.Arrays;
14
15 public final class XoneFacade {
16
17 private static MwMain mwMain;
18
19 private static final String TEXT_FILE_CLASS = "root/classes/TextFile";
20 private static final String INSTANCES_FOLDER = "root/instances";
21 // private static XoneClass textFileClass;
22
23 // インスタンス生成を禁止
24 private XoneFacade() {}
25
26 /** Creates a new instance of XoneMwFacade */
27 public static void init() throws XoneException {
28 mwMain = new MwMain();
29 mwMain.login("xone", "eagle".toCharArray());
30 // 今回のサンプルではクラスは必要ない
31 // XoneObject[] xos = mwMain.load(new String[]{TEXT_FILE_CLASS});
32 // if (xos == null || xos.length != 1)
throw new XoneException("クラスが見つかりません");
33 // textFileClass = (XoneClass)xos[0];*/
34 }
35
36 public static XoneObject[] getList() {
37 // root/instancesフォルダからクラス名がroot/classes/TextFileであるオブジェクトをロード
38 XoneObject[] xos = mwMain.load(INSTANCES_FOLDER,
MwMain.CLASSNAME_FIELD + "='" + TEXT_FILE_CLASS + "'");
39 // オブジェクト名でソート
40 Arrays.sort(xos);
41 return xos;
42 }
43
44 // オブジェクトを上書き保存する
45 public static void save(XoneObject xo) {
46 mwMain.save(INSTANCES_FOLDER, new XoneObject[]{xo}, Commands.OVERWRITE);
47 }
48
49 public static void shutdown() {
50 if (mwMain.isLogin()) mwMain.logout();
51 }
52
53 }
プログラム2は、プログラム1からXoneを利用するためのクラスです。initメソッドでXoneにログインしています。また、コメントにもあるようにこのサンプルでは、クラスは必要ありません。クラスは、新たにインスタンスを生成したり、評価(Validation)するときには必要です。
36行目のgetListメソッドでは、クラス名が”root/classes/TextFile”のインスタンスだけを読み込んでいます。ここでの条件文は、SQLのWHERE句に渡されます。なお、ここでは条件に合うすべてのインスタンスを読み込んでいますが、状況によってはメモリオーバーになることがあるのでご注意ください。オブジェクトの数が不明の場合は、MwMainのcountメソッドを使って数を調べ、limitやoffset付きでloadするとよいでしょう。
45行目のsaveメソッドでは単に保存でなく、上書き保存にしています。
今回のアプリでは、Xoneのアカウントを1つだけ使っています。この場合、フォルダのアクセス制御やフォルダ/オブジェクトのロックは意味がありません。こうしたことを実現したいときは、セッション毎にXoneに個々のアカウントでログインする必要があります。
web.xmlは次のようになります。これはIDE(NetBeans3.6)のウィザードで作ったもので、Tomcat5.x用になっています。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>EchoSample</servlet-name>
<servlet-class>xone_sample.echo.EchoSampleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>EchoSample</servlet-name>
<url-pattern>/EchoSample</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>
index.jsp
</welcome-file>
<welcome-file>
index.html
</welcome-file>
<welcome-file>
index.htm
</welcome-file>
</welcome-file-list>
</web-app>
■ 実行
実行すると、次のような画面になります。オブジェクト一覧のオブジェクト名が青く表示されている部分をクリックすると、内容が表示されます。内容は上書き保存ボタンをクリックすると上書きされます。

■ 最後に
一般的なWebアプリだと、どうしてもJSPで記述する部分が出てきますが、個人的にはどうもこのJSPが好きになれません。その点、Echoはオブジェクト指向、イベントドリブンでプログラミングできます。HTMLもタグライブラリも関係なく、かなり開発効率が良いように思います。
Echoに関しては、日本語の情報がほとんどないというのが現状です。ただ、Swingをある程度使っていれば、学習コストはそれほどではないと思われます。
その他本文に記載されている会社名、商品名は、各社の登録商標または商標です。
| 会社案内 | 著作権 | 免責事項 | 通信販売法規に基づく表記 | プライバシーポリシー | お問い合せ | サイトマップ |
COPYRIGHT(C) 2004 Fiverworks Ltd. ALL RIGHTS RESEREVED.
![J2EEフレームワーク[Xone]](../../image/xone_small_1.gif)
