Java Web StartとXoneでリッチクライアントアプリを作る 1/3
沼倉 均
Numakura Hitoshi
■ はじめに
最近は企業内の業務アプリもWebアプリとして作ることが多いようです。たしかにアプリの配布の問題やバージョン管理の面倒はないし、クライアント側でもWebブラウザさえあればよいのでインストール作業も必要ありません。しかし、その反面ユーザインターフェイスはさみしいもので、レスポンスも悪く、たくさんのデータを入力するには向きません。開発する側もセッションの管理や「戻る」ボタンの対策など、面倒なことはたくさんあります。
これなら、以前のRDB+VBでクライアントサーバのシステムの方がましだった、ということもしばしばです。Webアプリでもブラウザをクライアントにするなら、入力項目の少ないアプリとか、検索系のアプリなどに限定し、それ以外はリッチクライアントを利用することを考えた方がよいのではと思います。
■ リッチクライアントアプリを作る
リッチクライアントアプリを実現するにはいくつか手段がありますが、サーバからクライアントすべてをJavaだけで開発できるというメリットから、ここではJava Web Start(以降JWS)を使って簡単なGUIアプリを作ってみたいと思います。JWSを使ったアプリは、配布やバージョン管理の面倒がなく、開発する側にとってはほとんど通常のJavaアプリを作るようなものです。ただし、クライアントにはJavaの実行環境をインストールしておいてもらう必要があります。
用意するのは、次のソフトウェアです。
- Java1.4以上
- Xone1.0.3以降(パーソナル版でも可)
- tomcat(ここでは4.1.30を使用)
- Apache-AXIS(ここでは1.1を使用)
- Xoneに対応するRDB(MySQL,PostgreSQL,oracle,SQL Server,DB2)
GUIアプリとしては、クライアント側のテキストファイルをRDBに保存したり、読み込みができ、テキストの編集ができるものにします。手順は次のようになります。
1.Xoneのクラスを定義する
今回は、テキストファイルをXoneのオブジェクトとして格納します。そのために、まずXoneのクラスを定義します(Anyインスタンスでもよいのですが、今回はクラスとインスタンスで作ります)。
その前に、クラスを入れるフォルダと、インスタンスを入れるフォルダを作っておきます。これらはライフサイクルも違うし、アクセス制御をすることもあるので別のフォルダにしておきます。フォルダの作成はXoneに附属するモデルマネージャで、rootの下にclassesとinstancesという名前で作っておきます。
オブジェクトには、ファイルの本体だけでなく、ファイルのメタ情報(ファイル名など)も格納します。メタ情報はjava.io.Fileで取得できるものを使います。クラスを定義するには、次の方法があります。
- モデルマネージャに附属のオブジェクトエディタを使う
- プログラムで定義する
- XMLファイルで定義しておき、それをモデルマネージャでインポートするか、あるいはプログラムで書き込む
今回はモデルマネージャ(オブジェクトエディタ)で定義しましたが、それをXML形式でエクスポートとしたものが次のリストです。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xoneObjectList SYSTEM "http://www.fiverworks.com/xone/dtd/xoneObjectList.dtd">
<xoneObjectList xmlns="http://www.fiverworks.com/xoneObjectList" ver="1.0">
<xoneObject name="TextFile" type="XoneClass">
<property>
<hint />
</property>
<xoneElement name="name" type="string">~null;</xoneElement>
<xoneElement name="parent" type="string">~null;</xoneElement>
<xoneElement name="lastModified" type="timestamp">~null;</xoneElement>
<xoneElement name="length" type="long">~null;</xoneElement>
<xoneElement name="body" type="string">~null;</xoneElement>
<xoneElementList name="_restrictionList" />
<xoneElementList name="_restrictionMessageList" />
</xoneObject>
</xoneObjectList>
上記のリスト中の「~null;」は値がnullであることを示しています。上記をコピーしてXMLファイルにし、そのファイルをモデルマネージャでclassesフォルダにインポートします。これで、TextFileというクラスが定義できます。2.アプリを作る
GUIアプリですから、SwingをビジュアルにデザインできるIDEで作りました(IDEはNetBeansを使用。そのため変数宣言は後ろにあります)。プログラムは次のようになります(ソースはこちら)。
1 /*
2 * TextEditor.java
3 *
4 *
5 */
6
7 package xone_sample.jws;
8
9 import com.fiverworks.xone.model.XoneClass;
10 import com.fiverworks.xone.model.XoneInstance;
11 import com.fiverworks.xone.model.XoneModel;
12 import com.fiverworks.xone.model.XoneNode;
13 import com.fiverworks.xone.model.XoneObject;
14 import com.fiverworks.xone.model.XoneUser;
15
16 import com.fiverworks.xone.mw.MwMain;
17 import com.fiverworks.xone.tools.man.ObjectChooser;
18
19 import java.io.BufferedReader;
20 import java.io.BufferedWriter;
21 import java.io.File;
22 import java.io.FileReader;
23 import java.io.FileWriter;
24
25 import java.sql.Timestamp;
26
27 import javax.swing.JFileChooser;
28 import javax.swing.JFrame;
29
30
31 public class TextEditor extends JFrame {
32
33 /** Creates new form TextEditor */
34 public TextEditor() {
35 initComponents();
36 }
37
38 /** This method is called from within the constructor to
39 * initialize the form.
40 * WARNING: Do NOT modify this code. The content of this method is
41 * always regenerated by the Form Editor.
42 */
43 private void initComponents() {
44 jScrollPane1 = new javax.swing.JScrollPane();
45 textArea = new javax.swing.JTextArea();
46 menuBar = new javax.swing.JMenuBar();
47 fileMenu = new javax.swing.JMenu();
48 openMenuItem = new javax.swing.JMenuItem();
49 saveMenuItem = new javax.swing.JMenuItem();
50 loadDBMenuItem = new javax.swing.JMenuItem();
51 saveDBMenuItem = new javax.swing.JMenuItem();
52 exitMenuItem = new javax.swing.JMenuItem();
53
54 setTitle("TextEditor");
55 addWindowListener(new java.awt.event.WindowAdapter() {
56 public void windowClosing(java.awt.event.WindowEvent evt) {
57 exitForm(evt);
58 }
59 });
60
61 jScrollPane1.setViewportView(textArea);
62
63 getContentPane().add(jScrollPane1, java.awt.BorderLayout.CENTER);
64
65 fileMenu.setText("\u30d5\u30a1\u30a4\u30eb");
66 fileMenu.setFont(new java.awt.Font("Dialog", 0, 12));
67 openMenuItem.setFont(new java.awt.Font("Dialog", 0, 12));
68 openMenuItem.setText("\u958b\u304f");
69 openMenuItem.addActionListener(new java.awt.event.ActionListener() {
70 public void actionPerformed(java.awt.event.ActionEvent evt) {
71 openMenuItemActionPerformed(evt);
72 }
73 });
74
75 fileMenu.add(openMenuItem);
76
77 saveMenuItem.setFont(new java.awt.Font("Dialog", 0, 12));
78 saveMenuItem.setText("\u4fdd\u5b58");
79 saveMenuItem.addActionListener(new java.awt.event.ActionListener() {
80 public void actionPerformed(java.awt.event.ActionEvent evt) {
81 saveMenuItemActionPerformed(evt);
82 }
83 });
84
85 fileMenu.add(saveMenuItem);
86
87 loadDBMenuItem.setFont(new java.awt.Font("Dialog", 0, 12));
88 loadDBMenuItem.setText("\u30b5\u30fc\u30d0\u304b\u3089\u8aad\u307f\u8fbc\u307f");
89 loadDBMenuItem.addActionListener(new java.awt.event.ActionListener() {
90 public void actionPerformed(java.awt.event.ActionEvent evt) {
91 loadDBMenuItemActionPerformed(evt);
92 }
93 });
94
95 fileMenu.add(loadDBMenuItem);
96
97 saveDBMenuItem.setFont(new java.awt.Font("Dialog", 0, 12));
98 saveDBMenuItem.setText("\u30b5\u30fc\u30d0\u306b\u4fdd\u5b58");
99 saveDBMenuItem.addActionListener(new java.awt.event.ActionListener() {
100 public void actionPerformed(java.awt.event.ActionEvent evt) {
101 saveDBMenuItemActionPerformed(evt);
102 }
103 });
104
105 fileMenu.add(saveDBMenuItem);
106
107 exitMenuItem.setFont(new java.awt.Font("Dialog", 0, 12));
108 exitMenuItem.setText("\u7d42\u4e86");
109 exitMenuItem.addActionListener(new java.awt.event.ActionListener() {
110 public void actionPerformed(java.awt.event.ActionEvent evt) {
111 exitMenuItemActionPerformed(evt);
112 }
113 });
114
115 fileMenu.add(exitMenuItem);
116
117 menuBar.add(fileMenu);
118
119 setJMenuBar(menuBar);
120
121 pack();
122 }
123
124 private void saveDBMenuItemActionPerformed(java.awt.event.ActionEvent evt) {
125 // 処理コードをここに追加します:
126 try {
127 int result = this.oc.showSaveDialog();
128 if (result == ObjectChooser.APPROVE) {
129 String objPath = oc.getSelectedObject();
130 String objName = XoneNode.getLastName(objPath);
131 String objParent = XoneNode.getParent(objPath);
132 // Xoneのインスタンスを作る
133 XoneInstance xi = XoneModel.newXoneInstance(this.textFileClass, objName);
134 xi.setElementValue("name", this.currentFileName);
135 xi.setElementValue("parent", this.currentFileParent);
136 Timestamp ts = new Timestamp(System.currentTimeMillis());
137 xi.setElementValue("lastModified", ts.toString());
138 xi.setElementValue("length", Integer.toString(this.textArea.getText().length()));
139 xi.setElementValue("body", this.textArea.getText());
140
141 int res = mwMain.save(objParent, new XoneObject[]{xi}, oc.getWriteMode());
142 }
143 } catch (Exception ex) {
144 ex.printStackTrace();
145 return;
146 }
147 }
148
149 private void saveMenuItemActionPerformed(java.awt.event.ActionEvent evt) {
150 // 処理コードをここに追加します:
151 int result = jfc.showSaveDialog(this);
152 if (result == JFileChooser.APPROVE_OPTION) {
153 File file = jfc.getSelectedFile();
154 BufferedWriter out = null;
155 try {
156 out = new BufferedWriter(new FileWriter(file));
157 out.write(this.textArea.getText());
158 this.currentFileName = file.getName();
159 this.currentFileParent = file.getParent();
160 } catch (Exception ex) {
161 ex.printStackTrace();
162 return;
163 } finally {
164 if (out != null) {
165 try {
166 out.close();
167 } catch (Exception ex) {}
168 }
169 }
170 }
171 }
172
173 private void openMenuItemActionPerformed(java.awt.event.ActionEvent evt) {
174 // 処理コードをここに追加します:
175 int result = jfc.showOpenDialog(this);
176 if (result == JFileChooser.APPROVE_OPTION) {
177 File file = jfc.getSelectedFile();
178 BufferedReader in = null;
179 try {
180 in = new BufferedReader(new FileReader(file));
181 this.textArea.setText("");
182 String str = in.readLine();
183 while (str != null) {
184 this.textArea.append(str + "\n");
185 str = in.readLine();
186 }
187 this.currentFileName = file.getName();
188 this.currentFileParent = file.getParent();
189 } catch (Exception ex) {
190 ex.printStackTrace();
191 return;
192 } finally {
193 if (in != null) {
194 try {
195 in.close();
196 } catch (Exception ex) {}
197 }
198 }
199 }
200 }
201
202 private void loadDBMenuItemActionPerformed(java.awt.event.ActionEvent evt) {
203 // 処理コードをここに追加します:
204 try {
205 int result = this.oc.showOpenDialog(false);
206 if (result == ObjectChooser.APPROVE) {
207 String objName = oc.getSelectedObject();
208 XoneObject[] xos = mwMain.load(new String[]{objName});
209 if (xos == null || xos.length != 1) return;
210 XoneObject xo = xos[0];
211 if (!xo.getClassName().equals(TEXT_FILE_CLASS)) return;
212 this.textArea.setText(xo.getElementValue("body"));
213 this.currentFileName = xo.getElementValue("name");
214 this.currentFileParent = xo.getElementValue("parent");
215 }
216 } catch (Exception ex) {
217 ex.printStackTrace();
218 return;
219 }
220 }
221
222 private void exitMenuItemActionPerformed(java.awt.event.ActionEvent evt) {
223 exit();
224 }
225
226 /** Exit the Application */
227 private void exitForm(java.awt.event.WindowEvent evt) {
228 exit();
229 }
230
231 // logout
232 private void exit() {
233 if (mwMain.isLogin()) mwMain.logout();
234 System.exit(0);
235 }
236
237 // override
238 public void show() {
239 super.setSize(500, 400);
240
241 mwMain = new MwMain();
242 try {
243 XoneUser xu = mwMain.showLoginDialog(this);
244 } catch (Exception ex) {
245 ex.printStackTrace();
246 System.exit(0);
247 }
248 // loginしていなければ終了
249 if (!mwMain.isLogin()) System.exit(0);
250 // TextFileクラスを読み込んでおく
251 try {
252 XoneObject[] xos = mwMain.load(new String[]{TEXT_FILE_CLASS});
253 if (xos == null || xos.length != 1) exit();
254 this.textFileClass = (XoneClass)xos[0];
255 // 上の行は下でもよい
256 // this.textFileClass = XoneModel.getXoneClass(TEXT_FILE_CLASS);
257 } catch (Exception ex) {
258 ex.printStackTrace();
259 this.exit();
260 }
261
262 // ObjectChooserとJFileChooserの準備
263 oc = new ObjectChooser(this, mwMain);
264 oc.setCurrentFolder(INSTANCES_FOLDER);
265 jfc = new JFileChooser();
266 jfc.setMultiSelectionEnabled(false);
267
268 super.show();
269 }
270
271 /**
272 * @param args the command line arguments
273 */
274 public static void main(String args[]) {
275 new TextEditor().show();
276 }
277
278 // 変数宣言 - 編集不可
279 private javax.swing.JMenuItem exitMenuItem;
280 private javax.swing.JMenu fileMenu;
281 private javax.swing.JScrollPane jScrollPane1;
282 private javax.swing.JMenuItem loadDBMenuItem;
283 private javax.swing.JMenuBar menuBar;
284 private javax.swing.JMenuItem openMenuItem;
285 private javax.swing.JMenuItem saveDBMenuItem;
286 private javax.swing.JMenuItem saveMenuItem;
287 private javax.swing.JTextArea textArea;
288 // 変数宣言の終わり
289
290 private MwMain mwMain;
291 private ObjectChooser oc;
292 private XoneClass textFileClass;
293
294 private JFileChooser jfc;
295 private String currentFileName;
296 private String currentFileParent;
297
298 private static final String TEXT_FILE_CLASS = "root/classes/TextFile";
299 private static final String INSTANCES_FOLDER = "root/instances";
300 }
実行すると、最初にログインダイアログがあらわれ、ログインに成功すると使えるようになります(図は実行中のスクリーンショット)。

実行してみればわかりますが、メニューは「ファイル」だけであり、テキストエディタといっても単にJTextAreaにテキストを貼り付けているだけ、というこれ以上はないくらいシンプルなものです(^^;)。
ただ、JDBCを使ってデータを保存するより、ずいぶん簡単になっていることがわかると思います。このプログラムで留意してほしいのは、次の2点です。
・アプリで使うクラスは最初に用意しておく(250〜260行)
・必ずlogoutしてから終了する(232行のexitメソッド)
クラスは必要になったときにロードすると、ユーザに少々待たせることになることもあるので、できれば最初にロードしておくといいでしょう。また、255行のコメントにあるように、クラスはロードするとキャッシュ(com.fiverworks.xone.mw.Cache)に保存されるので、必要なときにキャッシュからクラスを取り出すこともできます。このXoneModel.getXoneClass(TEXT_FILE_CLASS)は、Cache.getInstance().getXoneClass(TEXT_FILE_CLASS)と等価です。
■ 最後に
今回のように、Xoneの1つのオブジェクトに1つのファイルを格納する、ということをすべてのファイルタイプに対して行うと、モデルマネージャはファイルマネージャとほぼ同等となります。そして、それをWebサービスを使って利用した場合、WebDAVと同じようなことができてしまいます。Xoneの場合、フォルダ単位でのアクセス制御やフォルダ/オブジェクトのロックができますから、より高度なデータの共有環境ができると思います(アクセス制御やロックはスタンダード版のみの機能です)。どなたか、作ってみます?
次回は、ここで作成したGUIアプリをWebサービスで実行するための環境を構築します。
その他本文に記載されている会社名、商品名は、各社の登録商標または商標です。
| 会社案内 | 著作権 | 免責事項 | 通信販売法規に基づく表記 | プライバシーポリシー | お問い合せ | サイトマップ |
COPYRIGHT(C) 2004 Fiverworks Ltd. ALL RIGHTS RESEREVED.
![J2EEフレームワーク[Xone]](../../image/xone_small_1.gif)
