Fiverwoks Homepageへ

EchoとXoneでWebアプリを作る

初版 2004-09-15
沼倉 均
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()メソッドを実装することだけです。

・プログラム2(ソースはこちら)
  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をある程度使っていれば、学習コストはそれほどではないと思われます。

mailto:hnumakura@fiverworks.com

|ページTOP|サンプルTOP|

『Java』は米国および、その他の国における米国Sun Microsystems, Inc.の商標です。
その他本文に記載されている会社名、商品名は、各社の登録商標または商標です。
| 会社案内 | 著作権 | 免責事項 | 通信販売法規に基づく表記 | プライバシーポリシー | お問い合せ | サイトマップ |
COPYRIGHT(C) 2004 Fiverworks Ltd. ALL RIGHTS RESEREVED.