Javaの使い方メモ(Interface versus Abstract Class)

たまたま読んだ記事が、ためになったのでざっくり訳してみることにした。
こちらの記事 → Java: Interface versus Abstract Class

<補足>~~~~~~~~
Interfaceの使い方とか、基本的なところはこちらのサイトがわかりやすかった

(参考) あぷり部屋 - Java入門講座(インターフェイスについて理解しよう!)
(参考) interfaceについて本気出して考えてみた – 都元ダイスケ IT-PRESS
(参考) Java: Are objects of the same type as the interface implemented?
~~~~~~~~~~~~

ここから先、翻訳内容です。


What’s the difference between an interface and an abstract class in Java?
interfaceと抽象クラスの違いは?

まずは、それぞれの定義をざっくり確認してみる。そのうえで違いを確認してみよう!

abstractなメソッドを持つクラスは、abstractと宣言しないといけない。abstractなメソッドというのは、「メソッドを定義するが、具体的な処理内容を記述しない」といった形のメソッド。

When to use abstract methods in Java?
抽象メソッドのつかいどころ。

「なんで抽象メソッドをつかいたいって思うか?」、ってことを以下のExampleで考えてみる。

/* the Figure class must be declared as abstract 
   because it contains an abstract method  */

public abstract class Figure
{
	/* because this is an abstract method the 
	   body will be blank  */
	public abstract float getArea();	
}

public class Circle extends Figure
{
	private float radius;

	public float getArea()
	{
		return (3.14 * (radius * 2)); 	
	}
}

public class Rectangle extends Figure
{
	private float length, width;

	public float getArea(Figure other)
	{
		return length * width;
	}
}

上記では、抽象メソッドgetArea()が定義されています。なので、Figureクラスは抽象メソッドを持っていることになり、abstractと宣言しないといけない。

Figureクラスを継承したCircleクラス、Rectangleクラスが定義されており、それぞれのクラスにはgetAreaメソッドが定義されています。

ここでのポイントは、「なぜ、Figureクラスの中でgetAreaメソッドを抽象メソッドとして定義したか?」ということ。getAreaメソッドでは、図形の面積を計算しています。

Figureクラスの中では、特定の図形を指定していません。そのため、Figureクラスの中ではgetAreaメソッドに具体的な計算内容を定義していません(定義できない)。つまり、これが、Figureクラスの中ではgetAreaメソッドを抽象クラスとして定義している理由ですね。

Figureクラスを継承する場合、以下の2パターンが考えられる。
・Figureクラスを継承したサブクラスでgetAreaメソッドを実装(オーバーライド)する。
・Figureクラスを継承したサブクラスでgetAreaメソッドを実装(オーバーライド)しない場合、そのサブクラスは抽象クラスとして宣言する。

A non abstract class is called a concrete class
抽象クラスでないものは、具象クラスという。

ということをふまえて、次に、「Interfaceって何か、interfaceと抽象クラスの違いは?」を考えてみる。

Java interface versus abstract class
Interface vs 抽象クラス

Interfaceなクラスではありません。ってところが、抽象クラスとの違いです。
Interfaceは、ほかのクラスに継承されて利用されるtype(型?)になります。

Interfaceを継承するクラスは、以下の2つの条件を満たす必要があります。
・クラスを定義するときに、”implements Interface_Name“といった形で、定義する必要があります。
・クラスの中で、Interfaceで定義されているすべてのメソッドを実装しておく必要があります。

以下のような”Dog”というInterfaceがあった場合に、

public interface Dog
{
    public boolean Barks();
    public boolean isGoldenRetriever();
}

このInterfaceを継承したクラスは、以下のような感じになる。

public class SomeClass implements Dog
{
    public boolean Barks{
    // method definition here
    }

    public boolean isGoldenRetriever{
    // method definition here
    }
}

ここまでの内容で、Interfaceと抽象クラスについて、基本的なことを理解できたと思います。というわけで、それぞれの違いについて見ていきたいと思います。

Abstract classes and inheritance
抽象クラスと継承

(1)抽象クラスは、ほかのクラスに継承されるものになります。あるクラスが継承されるということは、互いのクラスに強い関連性(依存関係?)が生じるということになります。

たとえば、抽象クラスとしてCanineクラスが定義されているとします。Canineクラスを継承したクラス(DogとかWolfクラス)は、Canineファミリーに所属すべきです。「すべき」ということは、開発者がメンテナンス・維持管理していく、ってことになる。

それに対しInterfaceの場合、Interface本体と、「InterfaceをImplementしたクラス」との関係は強くない。
たとえば、Houseというクラスがあったとします。Houseクラスは、AirConditioningというInterfaceをImplementしているとします。この場合、HouseにAirConditioning(空調設備)があるかどうかはそれほど強い関係性ではありません。

逆に「Houseクラスを継承したApartmentクラスと、Houseクラス」といった関係性のほうがはるかに強いつながりを持っています。
なぜかというと、ApartmentはHouseの中の1つに属していることから関連が強いわけです。そのため、クラスの継承をつかってきっちり定義するべきです。この点がInterfaceとの違いになってきます。

まとめると、
抽象クラスとそれを継承したクラスの間に、つよい関係性が考えられる、という場合、「抽象クラスをつかうべき」です。抽象クラスは、”継承”により、クラス間が密接につながります。つまり関係性が強くなる。
それに対し、Interfaceの場合、Interfaceと、それをImplementしたクラスの間に、つよい関係性は必要ありません。

Interfaces are a good substitute for multiple inheritance
Interfaceは、多重継承の代わりになる。

(2)Javaは多重継承を許可していません。Javaでは、クラスは1つのクラスしか継承できません。抽象クラスであろうがそうでなかろうが、継承できるのは1つだけです。
ですが、Interfaceは複数Implementできます。そのため、多重継承の代替になりえます。

抽象クラスとInterfaceの主な違いの1つとしては、「Javaのクラスは、抽象クラスを1つだけしか継承できないが、Interfaceは複数Implementできる」、と言えます。

Abstract classes can have some implementation code
抽象クラスは、実装コードを持てる。

(3)抽象クラスは、メソッドに処理手順を書き込めます。抽象クラスは、具体的な処理を記載したメソッドを持つことができます。また、抽象クラスは、コンストラクタ・インスタンス変数も持つことができます。
ですが、Interfaceはメソッドに処理手順を書き込めません。メソッドの名前だけ定義できます。InterfaceをImplementするクラスは、Interfaceで定義されているメソッドについて、すべて実装する必要があります。

When to use abstract class and interface in Java
抽象クラス、Interfaceのつかいどころ。

抽象クラス、Interfaceをつかうときのガイドラインを以下に示します。

1.次のように考えているなら、抽象クラスが適している。
「共通のBaseとなるクラスを用意して、それを継承したクラスを使う」といった形で、”継承”を使っていこう!

2.Publicな使い方をしたくないなら、抽象クラスをつかうのがよい。Interfaceで定義するメソッドはPublicでないといけない。

3.将来的にメソッドの追加が予想される場合、抽象クラスを使うほうがよい。Interfaceにメソッドを追加すると、そのInterfaceをImplementしているクラスすべてに、メソッド追加しないといけなくなる。おおさわぎになるかも!?

4.さしあたり、APIに変更が入らないという場合、Interfaceをつかうのがよい。

5.多重継承のようなことを行いたいと思っている場合、Interfaceを使うのがよい。複数のInterfaceをImplementすることができる。

Advertisements

Javaの使い方メモ(Downcast, Generics)

Javaの勉強中です。
Tutorialとか、Examplesにあるコードを見ていると、

String str = (String) list.get(0);
とか、
List<String> list = new ArrayList<String>();

といった記述を目にすることがあって、「この丸カッコ・三角カッコで囲まれているものは何だろう。」と思ったものの、「型を指定しているんだろうなぁ、きっと。」と考えてスルーしてきました。
が、いまさらながら、きちんと調べてみました。

使っている環境
Java JDK1.6.0_35(64bit版)


(1)DowncastとGenerics
Genericsの機能がJavaになかった頃、Listに文字列を追加して、それを取出す場合、このようにしていた。
(参考) Javaジェネリクス再入門 – プログラマーの脳みそ

List list = new ArrayList();
list.add("hello!");
String str = (String) list.get(0);

ArrayListに追加される要素は、Object型となっている。そのため、list.get(0)として、要素を取出したとき中身がStringであったとしても、要素の型はObject型となっている。

このままの状態だと、String型の変数strに値を格納することができない。そのため、
(String) list.get(0)

として、Downcastしている。(上位の型(Object型)を下位の型(String型)に変換している)
Downcastしたときの問題点として、「コンパイル時にエラーが出ないが、アプリ実行時にエラーが発生することがある」という問題がある。
上記の例の場合、listの要素にString以外のものが入っていた場合、「コンパイルは通るが実行時エラー」となる。ClassCastExceptionが発生するらしい。
このClassCastExceptionを解決するにはは、listにどんなデータが格納されていたかを調査する必要があり、デバッグがとても大変らしい。

そこで、現在ではJavaのGenerics機能をつかって、以下のように書いている。

List<String> list = new ArrayList<String>();
list.add("hello!");
String str = list.get(0);

List<String>と書くことで、listにString型がバインドされる。これにより、listに格納される要素はString型のみとなる。また、list.getするとString型が返るようになる。


(2)Genericsとは?
クラス定義でGenericsを使える。
(参考)ジェネリックスとは?なぜジェネリックスなのか – Java 入門

たとえば以下のようなクラスBoxを定義すると、

public class Box<T> {
    T o;

    public Box(T o){
        this.o = o;
    }
    public T get(){
        return o;
    }
}

クラスを使うときに、Box<Integer>、Box<String>といった形で指定することにより、それぞれの型用の Box クラスを用意できるようになる。以下のような感じでBox<Integer>を使うと、getしたときの返り値もInteger型になる。

Box<Integer> b = new Box<Integer>(new Integer(123));
Integer i = b.get();
System.out.println(i);

Genericsを使うことにより、Integer用・String用といったクラスを別々に定義する必要がなくなり、汎用化できる。僕に使いこなせるかどうかは、わからないけど(笑)


(3)もっとGenerics
メソッドにも使える。
(参考)Java Generics Tutorial

public class GenericMethodTest
{
   // generic method printArray                         
   public static < E > void printArray( E[] inputArray )
   {
      // Display array elements              
         for ( E element : inputArray ){        
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }

    public static void main( String args[] )
    {
        // Create arrays of Integer, Double and Character
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };

        System.out.println( "Array integerArray contains:" );
        printArray( intArray  ); // pass an Integer array

        System.out.println( "\nArray doubleArray contains:" );
        printArray( doubleArray ); // pass a Double array

        System.out.println( "\nArray characterArray contains:" );
        printArray( charArray ); // pass a Character array
    } 
}

printArrayメソッドでは、渡された配列の内容を表示しています。Genericsをつかうことで、int型の配列・double型の配列・String型の配列に対応できるようになっています。
実行すると以下のようになる。

Array integerArray contains:
1 2 3 4 5 6

Array doubleArray contains:
1.1 2.2 3.3 4.4 

Array characterArray contains:
H E L L O

(4)Downcastの使い道
どうしてもDowncastを使う場合、ClassCastExceptionを避けるために、instanceof演算子などを使って型のチェックをしておくべき。
(参考)8章:継承
(参考)強く型付けされているJavaの理解に必修の“型変換” (2/3) – @IT

    if( obj instanceof String )
        str1 = (String)obj;

でも、「そもそもDowncastをどういうときに使うべきか?」っていう素朴な疑問がわいてくる。つかいどころ、ってどこ?
これについては、こちらのサイトに例がありました。
(参考)Downcasting in Java

オブジェクトとオブジェクトの型を比較したいときにDowncastを使うと便利らしい。

public class Person
{
  private String name;
  private int age;

  public boolean equals(Object anObject)
  {
     if (anObject == null)
         return false;

     /* The object being passed in is checked
         to see it's class type which is then compared
         to the class type of the current class.  If they
         are not equal then it returns false
     */

     else if (getClass( ) != anObject.getClass())
         return false;

     else
     {
        /* 
         this is a downcast since the Object class
         is always at the very top of the inheritance tree
         and all classes derive either directly or indirectly 
         from Object:
        */
        Person aPerson = (Person) anObject;
        return ( name.equals(aPerson.name) 
                && (age == aPerson.age));
     }
  }

}

上記の例では、Personクラスのequals()メソッドでDowncastを使っています。異なる2つのオブジェクト(自分自身とanObject)のnameとageを比較しています。

想定される使用イメージはこんな感じ。
・Personクラスを継承したクラスとして、Japanese, American, Chineseといったクラスがあるとします。
・で、それぞれのクラスを比較したい。

Personクラスのequals()メソッドでは、引数として様々なクラスを引き受ける必要があるため、引数としてObject型の変数anObjectを使っています。Personクラスと比較するときに、型を合わせるために、Downcastしています。

今回の例では、ClassCastExceptionを避けるために、instanceof演算子ではなくgetClassを使っています。なのでJapaneseどうし、Americanどうしであった場合に、Downcastして比較することになりますね。

なお、instanceof演算子とgetClassの微妙な違いが気になったので、以下に引用しておきます。

~~~~~~~~~~~~~~~~~~
(参考)4章 継承
instanceof演算子について(以下、引用)
<オブジェクト変数>に代入されているインスタンスのクラスが<クラス名>と一致すればtrueを返します。また、インスタンスが<クラス名>クラスを継承している場合も、trueが返ります。

ObjectクラスのgetClass()を用いてインスタンスobjがクラスAのインスタンスであるか調べる場合、obj.getClass() == A.classと比較することでできます。
もし、インスタンスobjがクラスAを継承したクラスA’のインスタンスである場合、上記の比較構文はfalseを返します。instanceofであればtrueになることに注意してください。
~~~~~~~~~~~~~~~~~~

GAEでSpringMVCを動かしてみる4(続 Spring Security を試してみる)

前回のエントリー(GAEでSpringMVCを動かしてみる3(Spring Security を試してみる) | Walk on apps.)からの続きです。

SpringSecurityを利用して、ログイン・ログアウト機能を試しています。
前回は、「とりあえず、まずは動かしてみる、」ってことをやってみました。
今回は、パスワード暗号化や、ユーザごとのアクセス制御、HTTPS対応、とかを追加して「もうちょっと、きちんと動かしてみる」、つもりです。

前回のエントリーまでは、ソースコードをBlog上にまるごと貼り付けていたんですが、大変なのでGitHubで公開することにしました。
GitHubからDownload

Blogでは、ポイントに絞って書いていきます。


(6)もうちょっと、きちんと動かしてみる
次のようなことを、設定してみようと思います。
・パスワードの暗号化
・アクセス制御
・ログインユーザ名の取得
・オリジナルのLoginページの作成
・Logout時の設定
・通信の暗号化(HTTPS設定)
・日本語の文字化け対応

・パスワードの暗号化
StandardPasswordEncoderを利用して、パスワードを暗号化します。SpringSecurityのチュートリアルを参考にしました。「Using encoded passwords」のところに設定例が載っています。
Spring Security — Tutorial

マニュアルによると、StandardPasswordEncoderは、SHA-256でハッシュ化を行うようです。あと、saltとして、ランダムな8Byteデータを付与してくれる。
25. Spring Security Crypto Module

SpringSecurityのBean定義ファイルには、以下のように書きました。ユーザを2つ(admin, bob)定義しています。パスワードは、ユーザ名と同じにしています。

spring-security-dispatcher.xml への追記内容

    <beans:bean id="encoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder"/>

    <authentication-manager>
        <authentication-provider>
            <password-encoder ref="encoder"/>
            <user-service>
                <user name="admin" password="d4c4f5f190abf98b0d5224b549a1f2645acf975c75ec5d2d51895b0a7ca35949fc33576cc8294769" authorities="supervisor, user" />
                <user name="bob" password="cb60b076bebc371a3e5f6097399a82eb65948513639f4b090e70e2d49b6808fa3b02aa9bcd25668f" authorities="user" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

ハッシュ値の計算方法は、StandardPasswordEncoderのencodeメソッドを使います。チュートリアルでは、scalaのインタプリタを使っていますが、僕は地道にJavaのコードを書きました。以下のようにして、ハッシュ値を計算できます。

File名:PasswordEncode.java

import org.springframework.security.crypto.password.StandardPasswordEncoder;

public class PasswordEncode {
        public static void main(String[] args) throws Exception {
            StandardPasswordEncoder encoder = new StandardPasswordEncoder();
            System.out.println(encoder.encode("admin"));
            System.out.println(encoder.encode("bob"));
        }
}

・アクセス制御
SpringSecurityのBean定義ファイルで、use-expressions=”true”の設定をして、アクセス制御の設定をします。
これも、SpringSecurityのチュートリアルを参考にしました。
Spring Security — Tutorial

以下のような権限を、設定してみることにしました。
・index.htmlと、ログイン・ログアウト画面には、全員に許可。
・コンテキストパス「/velocity/admin」はadminだけ許可。
・コンテキストパス「/velocity/user」は、admin, bobに許可。
・その他のパスは、アクセス禁止。

spring-security-dispatcher.xml への追記内容は、こんな感じ。

<http use-expressions="true">
    <intercept-url pattern="/index.html" access="permitAll" />
    <intercept-url pattern="/velocity/login" access="permitAll"/>
    <intercept-url pattern="/velocity/loginfailed" access="permitAll" />
    <intercept-url pattern="/velocity/logout" access="permitAll" />

    <intercept-url pattern="/velocity/admin/**" access="hasRole('supervisor')" />
    <intercept-url pattern="/velocity/user/**" access="isAuthenticated()" />
    <intercept-url pattern="/**" access="denyAll" />

   :    :
   (中略)

</http>

access=”permitAll”といった、権限の書き方一覧は、以下のマニュアルの「16.1.1 Common Built-In Expressions」「16.2 Web Security Expressions」に載っていました。
16. Expression-Based Access Control
上記マニュアルによると、permitAllといった書き方は、WebSecurityExpressionRootクラスで定義されているとのこと。一応JavaDocも見てみた。
WebSecurityExpressionRoot (Spring Security 3.2.0.M1 API)

・ログインユーザ名の取得
現在ログインしているユーザ名の取得を行い、ブラウザ画面に表示できるようにします。
Controllerのコードを修正します。こちらのサイトを参考にしました。
Get current logged in username in Spring Security

ControllerのメソッドにPrincipalインターフェースを追加して、それを利用してユーザ名を取得しています。
修正ファイル:VelocityController.java

    (中略)
   :    :

@Controller
@RequestMapping("/velocity")
public class VelocityController {

    //DI via Spring
    String message;

    public void setMessage(String message) {
        this.message = message;
    }

    @RequestMapping(value="/{path}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String path, ModelMap model, Principal principal) {

        model.addAttribute("path", path);
        model.addAttribute("message", this.message);

        String name = principal.getName(); //get logged in username
        model.addAttribute("username", name);

        //return to .vm page, configured in mvc-dispatcher-servlet.xml, view resolver
        return "sample";
    }

   :    :
   (中略)

参考にしたサイトによると、ほかにも2つやり方があるらしく、ちょっと書き方変えるだけなので、参考に載せておきます。

まず、「Authentication.getName()」を使うパターン。ControllerのメソッドにPrincipalインターフェースを追加しなくても動きます。

   :  (中略)  :
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
   :  (中略)  :

    @RequestMapping(value="/{path}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String path, ModelMap model) {

        model.addAttribute("path", path);
        model.addAttribute("message", this.message);

        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String name = auth.getName(); //get logged in username
        model.addAttribute("username", name);

        //return to .vm page, configured in mvc-dispatcher-servlet.xml, view resolver
        return "sample";
    }
   :    :
   (中略)

次に「User.getUsername()」を使うパターン。こちらも、ControllerのメソッドにPrincipalインターフェースを追加しなくても動きます。

   :  (中略)  :

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
   :  (中略)  :

    @RequestMapping(value="/{path}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String path, ModelMap model) {

        model.addAttribute("path", path);
        model.addAttribute("message", this.message);

        User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String name = user.getUsername(); //get logged in username
        model.addAttribute("username", name);

        //return to .vm page, configured in mvc-dispatcher-servlet.xml, view resolver
        return "sample";
    }
   :    :
   (中略)

・オリジナルのLoginページの作成
こちらのサイトを参考にしました。
Spring Security form login using database
このサイトからダウンロードしたサンプルに、ログイン画面のサンプルがあります。

まず、SpringSecurityのBean定義ファイルに、form-login設定をします。

login-pageでは、ログイン画面を指定します。authentication-failure-urlでは、認証エラー時に表示する画面を指定します。
default-target-urlは、リダイレクト先が指定されていないときに、ログイン後に表示する画面を指定します。

認証が必要な画面/velocity/userにアクセスするとログイン画面/velocity/loginが表示されます。ログイン完了すると /velocity/userにリダイレクトされます。ですが、ログイン画面/velocity/loginに直接アクセスする場合もあります。このとき は、ログイン完了後にリダイレクトする宛先がありません。こういったときに、default-target-urlで指定された画面が表示されます。

spring-security-dispatcher.xml への追記内容

<http use-expressions="true">
   :  (中略)  :
    <form-login login-page="/velocity/login" default-target-url="/index.html"
        authentication-failure-url="/velocity/loginfailed" />
   :  (中略)  :
</http>

次に、Controllerのメソッドにログイン画面への遷移を追加します。
ログイン画面/velocity/loginと、認証エラー画面/velocity/loginfailedは、同じHTMLテンプレートを用いています。認証エラー時は、error変数にtrueを設定し、テンプレートに返しています。

修正ファイル:VelocityController.java

:  (中略)  :
@Controller
@RequestMapping("/velocity")
public class VelocityController {

:  (中略)  :
    @RequestMapping(value="/login", method = RequestMethod.GET)
    public String login(ModelMap model) {

        return "login";
    }

    @RequestMapping(value="/loginfailed", method = RequestMethod.GET)
    public String loginerror(ModelMap model) {

        model.addAttribute("error", "true");
        return "login";
    }
:  (中略)  :

}

次にログイン画面のテンプレートを作成します。
認証エラー時に、SPRING_SECURITY_LAST_EXCEPTION.messageにエラー内容が入るみたいなので、認証エラー時はそれを表示するようにしています。

作成したFile:login.vm

<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>Login Page</title>

<style>
.errorblock {
    color: #ff0000;
    background-color: #ffEEEE;
    border: 3px solid #ff0000;
    padding: 8px;
    margin: 16px;
}
</style>

</head>

<body onload='document.f.j_username.focus();'>
    <h3>Login with Username and Password ログインテスト</h3>

    #if(${error}=="true")
        <div>
            Your login attempt was not successful, try again.<br /> Caused :
            $SPRING_SECURITY_LAST_EXCEPTION.message
        </div>
    #end

    <form name='f' action="j_spring_security_check"
        method='POST'>

        <table>
            <tr>
                <td>User:</td>
                <td><input type='text' name='j_username' value=''>
                </td>
            </tr>
            <tr>
                <td>Password:</td>
                <td><input type='password' name='j_password' />
                </td>
            </tr>
            <tr>
                <td colspan='2'><input name="submit" type="submit"
                    value="submit" />
                </td>
            </tr>
            <tr>
                <td colspan='2'><input name="reset" type="reset" />
                </td>
            </tr>
        </table>

    </form>
</body>
</html>

「認証エラー時に、SPRING_SECURITY_LAST_EXCEPTION.messageにエラー内容が入る」と書きましたが、その値がApacheVelocity側に引き渡されるように、VelocityViewResolverクラスへの設定追加が必要です。

Bean定義ファイルspring-security-dispatcher.xml の設定修正し、exposeSessionAttributesをtrueに設定します。

   :  (中略)  :
    <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
        <property name="contentType" value="text/html;charset=UTF-8" />
        <property name="cache" value="true"/>
        <property name="prefix" value=""/>
        <property name="suffix" value=".vm"/>
        <property name="exposeSessionAttributes" value="true"/>
    </bean>
   :  (中略)  :

・Logout時の設定

まず、SpringSecurityのBean定義ファイルに、logout設定をします。

logout-success-urlで、ログアウト画面を指定します。delete-cookiesでログアウト時にJSESSIONIDをcookieから削除するようにしています。

spring-security-dispatcher.xml への追記内容

<http use-expressions="true">
:  (中略)  :
    <logout logout-success-url="/velocity/logout" delete-cookies="JSESSIONID"/>

</http>

次に、Controllerのメソッドにログアウト画面への遷移を追加します。

修正ファイル:VelocityController.java

:  (中略)  :
@Controller
@RequestMapping("/velocity")
public class VelocityController {

    //DI via Spring
    String message;

    public void setMessage(String message) {
        this.message = message;
    }

:  (中略)  :

    @RequestMapping(value="/logout", method = RequestMethod.GET)
    public String logout(ModelMap model) {

        model.addAttribute("message", this.message);
        return "logout";
    }

}

次にログアウト画面のテンプレートを作成します。

作成したFile:logout.vm

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>Hello App Engine</title>
  </head>

  <body>

    message: ${message}<br/>
    <p>Logoutしました。</p>
  </body>
</html>

・通信の暗号化(HTTPS設定)
SpringSecurityで特定のURLに対してHTTPSを要求する設定ができます。ただし、HTTPS自体をSpringSecurityで制御するわけでないので、HTTPSの入り口は、APサーバ等で用意しておく必要があります。

そのため、まず、GoogleAppEngine側でHTTPSの入り口を用意する必要があります。GAEでHTTPSを使う場合、web.xmlにsecurity-constraint設定を追加すると利用できます。
配備記述子: web.xml – Google App Engine — Google Developers

注意点としては、Google App Engine SDKでは、開発環境ではHTTP接続、GAE上ではHTTPS接続になります。
SpringSecurityでHTTPSを要求する設定をした場合、開発環境でもHTTPSが使えるようにしておかないと、アクセスができません。そのため、僕の場合、HTTPS関連設定はGAE側にすべて任せることにします。

web.xmlに、security-constraint設定を追加します。

  :  (中略)  :
    <!-- GAE Setting -->
    <security-constraint>
        <web-resource-collection>
            <url-pattern>/velocity/login*</url-pattern>
            <url-pattern>/velocity/loginfailed*</url-pattern>
            <url-pattern>/velocity/logout*</url-pattern>
        </web-resource-collection>
        <user-data-constraint>
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
    </security-constraint>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
  :  (中略)  :

参考までに、SpringSecurityで設定する場合の内容を書いておきます。
マニュアルはこちら。3. Security Namespace Configuration

requires-channelでプロトコルを指定すると設定できます。
port-mappingは必要に応じて設定します。開発環境などで、通常のポートが使えないときに指定します。

  <http>
    <intercept-url pattern="/secure/**" access="ROLE_USER" requires-channel="https"/>
    <intercept-url pattern="/**" access="ROLE_USER" requires-channel="any"/>
    ...

    <port-mappings>
      <port-mapping http="9080" https="9443"/>
    </port-mappings>
    ...
  </http>

・日本語の文字化け対応
HTMLテンプレートに日本語があると、ブラウザで表示させたときに、文字化けがおきます。
そのため、Bean定義ファイルと、web.xmlに設定追加しておく必要があります。
こちらのサイトを参考にしました Spring-MVCのビューにVelocityを使いたい – PukiWiki

今回はUTF-8に統一することにします。
まず、Bean定義ファイルspring-security-dispatcher.xmlに設定追加します。

VelocityConfigurerクラスのinput.encoding、output.encodingにUTF-8を設定。
→ たぶん、テンプレートファイル読込と、HTML出力の設定だと思う

VelocityViewResolverクラスの、contentTypeにUTF-8を設定。
→ ブラウザに返却するContent-Typeを設定してる。HTTPヘッダですかね。

 
   :  (中略)  :
    <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
        <property name="resourceLoaderPath" value="/WEB-INF/view/"/>

        <property name="velocityProperties">
            <props>
            <prop key="input.encoding">UTF-8</prop>
            <prop key="output.encoding">UTF-8</prop>
            </props>
        </property>
    </bean>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
        <property name="contentType" value="text/html;charset=UTF-8" />
        <property name="cache" value="true"/>
        <property name="prefix" value=""/>
        <property name="suffix" value=".vm"/>
        <property name="exposeSessionAttributes" value="true"/>
    </bean>
   :  (中略)  :

次にweb.xmlに設定追加します。

CharacterEncodingFilterクラスで、encodingにUTF-8を設定します。
→HTMLフォームからの入力された文字を、UTF-8にしてサーバ側で処理することになります。

   :  (中略)  :
    <!-- UTF-8 Setting -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
   :  (中略)  :

上記設定には、forceEncodingというパラメータもあるみたいだけど、今回は設定しませんでした。

   :  (中略)  :
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
   :  (中略)  :

(7)アプリ起動
GAE上で動かしてみました。
コンテキストパス/velocity/user にhttpでアクセスすると、ログイン画面にリダイレクトされます。httpsになってます。
spring4_01

bobでログインすると、画面が返ってきました。httpにもどっています。
ユーザ名bobが、画面に表示されています。
spring4_02

bobユーザのままで、/velocity/admin にアクセスすると、アクセス拒否されます。
spring4_03

/velocity/user 画面でログアウトをクリックすると、ログアウトします。httpsになっています。
spring4_04

ログイン画面でパスワードを間違えると、エラーメッセージが表示されます。
spring4_05


GAEでSpringMVCを動かしてみる3(Spring Security を試してみる)

GoogleAppEngineの勉強中です。
前回のエントリーで、SpringMVCにテンプレートエンジンのApache Velocityを組み合わせて動かしてみました。
今回は、SpringSecurityを利用して、ログイン・ログアウト機能を試してみます。

とりあえず、ユーザの管理は設定ファイルに書き込んでおくシンプルな形を試します。(ユーザデータをDBに保存したり、外部の認証サービス(GoogleAccountとかTwitterとか)を利用する、みたいなことはやらない。)

————————————————————–
使っている環境
Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1

Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)

Spring Framework 3.2.2.RELEASE
Commons Collections 3.2.1
Commons Lang 2.6
Commons Logging 1.1.2
Apache Velocity 1.7
Spring Security 3.2.0.M1


(1)はじめに
今回はSpring Securityの機能を使って、ユーザ認証とアクセス制御を試してみます。
「SpringMVC + Apache Velocity」については、過去のエントリーを参照してください。
GAEでSpringMVCを動かしてみる | Walk on apps.
GAEでSpringMVCを動かしてみる2(テンプレートエンジンVelocityを組込む) | Walk on apps.

進め方としては、「とりあえず、まずは動かしてみる、」ってことをやってみます。
そのあと、パスワード暗号化や、ユーザごとのアクセス制御、HTTPS対応、とかを追加して「もうちょっと、きちんと動かしてみる」、つもりです。


(2)SpringFramework、Commons Loggingのダウンロード
こちらについては、前回のエントリーに書いてあるのでそちらを参照してください。
GAEでSpringMVCを動かしてみる | Walk on apps.


(3)velocity関連ライブラリのダウンロード
以下の3種類必要になります。
Apache Velocity
Commons Collections
Commons Lang

こちらについては、前回のエントリーに書いてあるのでそちらを参照してください。
GAEでSpringMVCを動かしてみる2(テンプレートエンジンVelocityを組込む) | Walk on apps.


(4)ライブラリ追加
ライブラリに追加するjarファイルは、以下の15個

commons-collections-3.2.1.jar
commons-lang-2.6.jar
commons-logging-1.1.2.jar
spring-aop-3.2.2.RELEASE.jar
spring-beans-3.2.2.RELEASE.jar
spring-context-3.2.2.RELEASE.jar
spring-context-support-3.2.2.RELEASE.jar
spring-core-3.2.2.RELEASE.jar
spring-expression-3.2.2.RELEASE.jar
spring-security-config-3.2.0.M1.jar
spring-security-core-3.2.0.M1.jar
spring-security-web-3.2.0.M1.jar
spring-web-3.2.2.RELEASE.jar
spring-webmvc-3.2.2.RELEASE.jar
velocity-1.7.jar

これらのjarファイルを、Eclipseの“war/WEB-INF/lib”フォルダに、配置します。
また、Eclipseの、「Java Build Path」に追加します。


(5)まずは動かしてみる。
Springのマニュアルと、
3. Security Namespace Configuration

こちらのサイトを参考にしました。
Spring Security hello world example
Spring Security logout example

まずは、Controllerを作成します。
リクエスト「/velocity/***」に対して、テンプレートsample.vmを返す想定にしています。
テンプレートの置き場所や、ファイル名の拡張子については、SpringのBean定義ファイルの中で定義しています。

File名:src/sample/controller/VelocityController.java

package sample.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/velocity")
public class VelocityController {

    //DI via Spring
    String message;

    @RequestMapping(value="/{path}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String path, ModelMap model) {

        model.addAttribute("path", path);
        model.addAttribute("message", this.message);

        //return to .vm page, configured in mvc-dispatcher-servlet.xml, view resolver
        return "sample";
    }

    public void setMessage(String message) {
        this.message = message;
    }

}

次に、テンプレート(.vmファイル)を作成します。

リクエスト「/velocity/***」を受け取ったControllerが、***の部分を抜き出して、path変数に入れて、テンプレートに引き渡しています。

message変数は、SpringのBean定義ファイルの中でControllerに対して初期設定しています。Controllerがその値をテンプレートに引き渡しています。

File名:war/WEB-INF/view/sample.vm

<html>
<body>
path: ${path}<br/>
message: ${message}<br/>
<p><a href="j_spring_security_logout">Logout</a></p>
</body>
</html>

次に、SpringのBean定義ファイルを作成します。
Beanの定義と、テンプレートの場所を指定しています。

File名:war/WEB-INF/spring-mvc-dispatcher-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.2.xsd
                        http://www.springframework.org/schema/mvc
                        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
                        ">

    <mvc:annotation-driven />

    <bean class="sample.controller.VelocityController">
        <property name="message">
            <value>Velocity Sample</value>
        </property>
    </bean>

    <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
        <property name="resourceLoaderPath" value="/WEB-INF/view/"/>
    </bean>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
        <property name="cache" value="true"/>
        <property name="prefix" value=""/>
        <property name="suffix" value=".vm"/>
    </bean>

</beans>

次に、Spring Securityの定義ファイルを作成します。
とりあえず、admin, bobというユーザを準備しておきました。パスワードは、ひとまず平文で書いておきます。

File名:war/WEB-INF/spring-security-dispatcher.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                        http://www.springframework.org/schema/security
                        http://www.springframework.org/schema/security/spring-security-3.1.xsd
                        ">

    <!-- Spring Security -->

    <http auto-config='true'>
        <intercept-url pattern="/**" access="ROLE_USER" />
    </http>

    <authentication-manager>
        <authentication-provider>
            <user-service>
              <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />
              <user name="bob" password="bob" authorities="ROLE_USER" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

</beans:beans>

次に、web.xmlを修正します。

すべてのコンテキストパスに対して、DelegatingFilterProxyを適用する(フィルタする?)ように設定しています。(赤字にしています)

contextConfigLocationのところで、Spring Securityの定義ファイルを指定しています。
ここでは、Bwan定義ファイル(spring-mvc-dispatcher-servlet.xml)については指定していません。これは、servlet-nameで指定している名称spring-mvc-dispatcherに紐づいた名前のファイルを、自動的に読み込みに行ってくれるから。(というか、名前を紐づけないやり方がわからない。。)

File名:war/WEB-INF/web.xml

<?xml version="1.0" encoding="utf-8" standalone="no"?><web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <!-- Spring MVC -->
    <servlet>
        <servlet-name>spring-mvc-dispatcher</servlet-name>
        <servlet-class>
                    org.springframework.web.servlet.DispatcherServlet
                </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring-mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/spring-security-dispatcher.xml
        </param-value>
    </context-param>

    <!-- Spring Security -->
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

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

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

</web-app>

appengine-web.xmlへ設定追加
GoogleAppEngineでは、デフォルトではセッションが使用できないようです。なので、appengine-web.xmlに以下の設定を追加します

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

作成したソースコードの配置場所をまとめると、こんな感じになっています。
spring_security01

自PC上で、ためしにアプリ起動
http://localhost:8888/velocity/test  にアクセスしてみます。ログイン画面が出てきました。
spring_security02

ユーザ「bob」でログインすると、画面が出ました!!
spring_security03

GAE上にDeployしても、きちんと動きました。よかった。


(6)もうちょっと、きちんと動かしてみる
:  :
:  :
記事が長くなってきたので、ここから先は次回のエントリーに書くことにします。
あと、ソースを張付けるのが大変になってきたので、GitHubを導入してみたいと思う!
こちら→ GAEでSpringMVCを動かしてみる4(続 Spring Security を試してみる) | Walk on apps.

GAEでSpringMVCを動かしてみる2(テンプレートエンジンVelocityを組込む)

GoogleAppEngineの勉強中です。
前回のエントリーで、SpringMVCを動かしてみました。
その時は、ひとまずjspで画面表示していたけど、今回は、テンプレートエンジンのApache Velocity を使って、「SpringMVC + Velocity」を試してみます。
とりあえず動かすことを目標にしているので、テンプレートの分割(ヘッダ、フッタ、コンテンツ、サイドバー領域とか)まではやりません。

————————————————————–
使っている環境
Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1

Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)

Spring Framework 3.2.2.RELEASE
Commons Collections 3.2.1
Commons Lang 2.6
Commons Logging 1.1.2
Apache Velocity 1.7


(1)はじめに

テンプレートエンジンって、そもそも、どれ使えばいいのか謎。
このページに一覧が載ってるけど、すげーたくさんある。
Template engine (web) – Wikipedia, the free encyclopedia

今回は、Springと連携させる、ってことで、以下のサイトを参考にしました。
(a)SpringMVCで使えるテンプレートエンジンを比較してみた #jsug – from world import goodies
(b)Template Engineの比較(Java) – No Bugs, No Life

上記サイトを見た感じだと、
・Velocity
・Freemarker
・Thymeleaf
・Mayaa

といったものが候補になりそう。(JSPはテンプレートの分割ができないと思うので最初から除外(タグを使えばできる?))

また、SpringFrameworkのドキュメントの「18. View technologies」にテンプレートのことが書かれてあるのを見つけました。
その中の、「18.4. Velocity & FreeMarker」で、Velocity 、FreeMarkerの説明があるので、この2つのうちどちらかがよさそう。
18. View technologies

で、先述したサイト(b)に記載があるんですが、FreeMarkerについては、現時点の最新版FreeMarker 2.3.19に、セキュリティの問題があるらしい。
FreeMarker Manual – 2.3.19

Attention! This release contains two important security workarounds that unavoidably make it obvious how some applications can be exploited. FreeMarker can’t solve these issues on all configurations, so please read the details instead of just updating FreeMarker!

というわけで、Velocityを使うのが無難そう。


(2)SpringFramework、Commons Loggingのダウンロード
こちらについては、前回のエントリーに書いてあるのでそちらを参照してください。
GAEでSpringMVCを動かしてみる | Walk on apps.


(3)velocityのダウンロード
こちらのサイトのDownloadsから入手できます。
Apache Velocity Site – The Apache Velocity Project

僕の場合、velocity-1.7.tar.gz をダウンロードしました。


(4)Commons Collectionsのダウンロード
こちらのサイトのDownloadsから入手できます。
Collections – Home

僕の場合、commons-collections-3.2.1-bin.tar.gz をダウンロードしました。


(5)Commons Langのダウンロード
こちらのサイトのDownloadsから入手できます。
Lang – Home

Commons Langについては、ちょっと注意です。
Commons Langがない状態でSpringMVCを起動すると、Class Not Foundエラーが出ます。原因は以下のClassでした。
org.apache.commons.lang.StringUtils

が、現時点での最新版、commons-lang3-3.1を使うとこのエラーが解消しません。JavaDocを見ると
version 3.*ではクラス名が微妙に違っているみたいでした。
extended by org.apache.commons.lang3.StringUtils

なので、version2.*系の最新版、commons-lang-2.6-bin.tar.gz をダウンロードしました。


(6)ライブラリ追加
ライブラリに追加するjarファイルは、以下の11個
commons-collections-3.2.1.jar
commons-lang-2.6.jar
commons-logging-1.1.2.jar
spring-beans-3.2.2.RELEASE.jar
spring-context-3.2.2.RELEASE.jar
spring-context-support-3.2.2.RELEASE.jar
spring-core-3.2.2.RELEASE.jar
spring-expression-3.2.2.RELEASE.jar
spring-web-3.2.2.RELEASE.jar
spring-webmvc-3.2.2.RELEASE.jar
velocity-1.7.jar

これらのjarファイルを、Eclipseの“war/WEB-INF/lib”フォルダに、配置します。
spring_velocity01

また、Eclipseの、「Java Build Path」に追加します。
spring_velocity02


(7)コーディング

まずは、Controllerを作成します。
Controllerの書き方は、こちらのサイトを参考にしました。
Spring-MVCとVelocityを使った Twitterライクに REST風な URLハンドリングをするサイトのテンプレート

書き方としては、Velocityを使わなかった時と変わらない書き方で大丈夫そう。(今回は、新規作成したので、前回のエントリーで作成したものと若干ちがっています。)
おそらく、SpringMVCでは、テンプレートエンジンに依存しない書き方ができるように設計されているんだと思った。

今回は、リクエスト「/velocity/***」に対して、テンプレートsample.vmを返す想定にしています。
model.addAttributeメソッドでテンプレートに引き渡す値をセットし、return “sample”;でテンプレートを指定しています。
テンプレートの置き場所や、ファイル名の拡張子については、SpringのBean定義ファイルの中で定義しています。

File名:src/sample/controller/VelocityController.java

package sample.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/velocity")
public class VelocityController {

    //DI via Spring
    String message;

    @RequestMapping(value="/{name}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String name, ModelMap model) {

        model.addAttribute("name", name);
        model.addAttribute("message", this.message);

        //return to .vm page, configured in mvc-dispatcher-servlet.xml, view resolver
        return "sample";
    }

    public void setMessage(String message) {
        this.message = message;
    }

}

次に、テンプレート(.vmファイル)を作成します。

リクエスト「/velocity/***」を受け取ったControllerが、***の部分を抜き出して、name変数に入れて、テンプレートに引き渡しています。

message変数は、SpringのBean定義ファイルの中でControllerに対して初期設定しています。Controllerがその値をテンプレートに引き渡しています。

File名:war/WEB-INF/view/sample.vm

<html>
<body>
name: ${name}<br/>
message: ${message}<br/>
</body>
</html>

次に、SpringのBean定義ファイルを作成します。
Bean定義ファイルについては、前回のエントリーで作成したものに追記し、不要な部分をコメントアウトしています。

<bean class=”sample.controller.MovieController”>の部分は、今回関係ないので読み飛ばしてください。

<bean class=”sample.controller.VelocityController“>のところで、コントローラの初期設定をしています。

「bean id=”velocityConfig“」と、「bean id=”viewResolver“」のところで、テンプレートファイルの置き場所・拡張子を指定しています。(複数のディレクトリを使い分けることは、できるんだろうか?)

File名:war/WEB-INF/mvc-dispatcher-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.2.xsd
                        http://www.springframework.org/schema/mvc
                        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
                        ">

<!--
    <context:component-scan base-package="sample.controller">
        <context:exclude-filter type="regex"
            expression="sample.controller.Movie.*" />
    </context:component-scan>
-->

    <mvc:annotation-driven />

    <!-- Bean to show you Di in GAE, via Spring, also init the MovieController -->
    <bean class="sample.controller.MovieController">
        <property name="message">
            <value>Hello World</value>
        </property>
    </bean>

<!--
    <bean
       class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/pages/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>
-->

    <bean class="sample.controller.VelocityController">
        <property name="message">
            <value>Velocity Sample</value>
        </property>
    </bean>

    <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
        <property name="resourceLoaderPath" value="/WEB-INF/view/"/>
    </bean>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
        <property name="cache" value="true"/>
        <property name="prefix" value=""/>
        <property name="suffix" value=".vm"/>
    </bean>

</beans>

次に、web.xmlを修正します。
web.xmlは、前回のエントリーで作成したときと同じ状態です。

リクエストをすべて、DispatcherServletに渡す、って感じ。と、Bean定義ファイルの場所を指定
ContextLoaderListenerは・・・なんだろ。。。。

File名:war/WEB-INF/web.xml

<?xml version="1.0" encoding="utf-8" standalone="no"?><web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>
                    org.springframework.web.servlet.DispatcherServlet
                </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
    </context-param>

    <listener>
        <listener-class>
                    org.springframework.web.context.ContextLoaderListener
                </listener-class>
    </listener>

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

</web-app>

作成したソースコードの配置場所をまとめると、こんな感じになっています。

spring_velocity03


(8)アプリ起動
ブラウザから、アクセスするとこんな感じ。
spring_velocity04

リクエストで、/velocity/*** としたときの、***の部分が、
ブラウザ画面上で「name: ***」と表示されます。

上記の画面キャプチャは、自分のPC上(開発環境)のものですが、GAE上に実際にDeployしてもきちんと動きました。

GAEでSpringMVCを動かしてみる

GoogleAppEngineの勉強中です。
GAEでJavaを使って開発する場合、JSP,Servletが使えます。が、MVCモデルでアプリ作成しようとすると、何らかのFrameworkを使わないとダメっぽい。

というわけで、SpringMVCを試してみました。

————————————————————–
使っている環境
Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1

Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)

Spring Framework 3.2.2.RELEASE
commons-logging-1.1.2


(1)はじめに
Javaのフレームワークというと、Struts、Springくらいしか聞いたことがなくって、たまたま目にしたPlayFrameworkというものが結構よさそうだと思ってた。ただPlayFrameworkは、まだ仕様がちょっと落ち着いてないみたい、う~ん。

で調べてみると、比較資料がありました。
Comparing JVM Web Frameworks – Devoxx France 2013

この資料は、すごい参考になったんだけど、スライド90枚に及ぶ長編スペクタクルなので、簡単にポイントを紹介します。

資料の40~44ページに、比較項目にポイント付けして、ランキングしたものがあります。
Grailsが1位になってるんですけど、どうなんでしょうか。Springは2位ですね。

57ページに、各種情報サイト?で紹介されているTop JVM Frameworkがのっています。選定基準は謎ですけど、有名どころ?と思われるFrameworkはこのあたりのものなんでしょうか。

69ページから、GoogleTrendでどんだけ話題になっているか、
73ページから、JobTrend(仕事で需要あるか)、
なんてことも紹介されています。

そして、最終的に筆者は、「俺に聞くな、自分で考えろ」と言っています(笑)。

Prioritize a list of goals that are important to your application.
・目的を一覧にして、優先順位をつける
Pick 3-4 frameworks and do a spike with each, developing the same application.
・3~4つのフレームワークをつかって、同じ機能のアプリ作成してみる。
Document and rank each framework against your list of goals.
・目的を一覧化し、それぞれの項目について各フレームワークに点数をつける
Calculate and choose!
・ランキングして、どれにするか選ぶ
Don’t be a Picker
・適当にチョイスするのは避ける


(2)SpringFrameworkのダウンロード
こちらのサイトの、一番下のところにProjectsという一覧があって、そこのSpring Frameworkのページに行きます。
SpringSource.org

spring_framework

そうすると、右のほうに、ダウンロード・ドキュメントへのリンクがあります。
僕の場合、spring-framework-3.2.2.RELEASE-dist.zip をダウンロードしました。


(3)Commons Loggingのダウンロード
Springを動かしてみるとわかるんですが、起動時にCommons LoggingがないとClass NotFoundエラーが出ます。
古いバージョンのSpringだと、SpringFrameworkのダウンロードすると、中にCommons Loggingのjarファイルも入ってたみたいですが、今は別になっているようです。

こちらのサイトの、Downloadから入手できます。
Commons Logging – Overview

僕の場合、commons-logging-1.1.2-bin.tar.gz をダウンロードしました。


(4)ライブラリ追加
とりあえず、超シンプルなアプリで試してみたいだけなので、必要最小限のjarファイルをライブラリに追加します。
追加するファイルはこちら7つ。

commons-logging-1.1.2.jar
spring-beans-3.2.2.RELEASE.jar
spring-context-3.2.2.RELEASE.jar
spring-core-3.2.2.RELEASE.jar
spring-expression-3.2.2.RELEASE.jar
spring-web-3.2.2.RELEASE.jar
spring-webmvc-3.2.2.RELEASE.jar

これらのjarファイルを、Eclipseの“war/WEB-INF/lib”フォルダに、配置します。
spring_library1

また、Eclipseの、「Java Build Path」に追加します。
spring_library2


(5)コーディング
こちらのサイトを参考にして、サンプル作成しました。
Google App Engine + Spring 3 MVC REST example
Getting Started With Spring MVC and Google App Engine | [Be el o ge]

まずは、Controllerを作成します。

Eclipseのsrcフォルダ配下に、パッケージsample.controllerを作成し、その中にMovieController.javaを作成します。
File名:src/sample/controller/MovieController.java

package sample.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/movie")
public class MovieController {

    //DI via Spring
    String message;

    @RequestMapping(value="/{name}", method = RequestMethod.GET)
    public String getMovie(@PathVariable String name, ModelMap model) {

        model.addAttribute("movie", name);
        model.addAttribute("message", this.message);

        //return to jsp page, configured in mvc-dispatcher-servlet.xml, view resolver
        return "list";

    }

    public void setMessage(String message) {
        this.message = message;
    }

}

次に、JSPファイルを作成します。
File名:war/pages/list.jsp

<html>
<body>
    <h1>GAE + Spring 3 MVC REST example</h1>

    <h3>Movie : ${movie} , DI message : ${message}</h3>    
</body>
</html>

次に、SpringのBean定義ファイルを作成します。
File名:war/WEB-INF/mvc-dispatcher-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.2.xsd
                        http://www.springframework.org/schema/mvc
                        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
                        ">

<!--
    <context:component-scan base-package="sample.controller">
        <context:exclude-filter type="regex"
            expression="sample.controller.Movie.*" />
    </context:component-scan>
-->

    <mvc:annotation-driven />

    <!-- Bean to show you Di in GAE, via Spring, also init the MovieController -->
    <bean class="sample.controller.MovieController">
        <property name="message">
            <value>Hello World</value>
        </property>
    </bean>

    <bean
       class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/pages/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

</beans>

GoogleAppEngineでは、アプリ起動時にかかる時間を気にする必要があります。(今回はサンプルアプリなので気にしなくていいですが)

こちらの情報によると、
Optimizing Spring Framework for App Engine Applications – Google App Engine — Google

Springの「Component Scanning」は使わないほうがいいとのことです。
なので、context:component-scan タグを使わないようにしました。

ファイルの頭で、xmlの定義を読込ませているところ(以下の項目)
xmlns:context
xmlns:mvc
xsi:schemaLocation

は、漏れの無いように注意です。僕は「xmlns:mvc」を書き忘れていて、アプリ起動時にmvc-dispatcher-servlet.xmlの読込エラーが発生して悩まされました。

次に、web.xmlを修正します。
File名:war/WEB-INF/web.xml

<?xml version="1.0" encoding="utf-8" standalone="no"?><web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>
                    org.springframework.web.servlet.DispatcherServlet
                </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
    </context-param>

    <listener>
        <listener-class>
                    org.springframework.web.context.ContextLoaderListener
                </listener-class>
    </listener>

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

</web-app>

作成したソースコードの配置場所をまとめると、こんな感じになります。

spring_files


(6)アプリ起動
ブラウザから、アクセスするとこんな感じ。
spring_sample_view

リクエストで、/movie/*** としたときの、***の部分が、
ブラウザ画面上で「Movie : ***」と表示されます。

上記の画面キャプチャは、自分のPC上(開発環境)のものですが、GAE上に実際にDeployしてもきちんと動きました。よかった。


4/21追記
テンプレートエンジンにApache Velocityを使ってみました。
GAEでSpringMVCを動かしてみる2(テンプレートエンジンVelocityを組込む) | Walk on apps.

(GAEで日本語の形態素解析を試してみる)第3回 Kuromojiを使ってみる

GoogleAppEngineの勉強中です。
3回に分けて、GAEで日本語の形態素解析を試してみたことを書いています。
第1回 lucene-gosenを使ってみる
第2回 lucene-gosenのTokenFilterを試す
第3回 Kuromojiを使ってみる

今回は、第3回です。

使っている環境
Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1

Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)

kuromoji-0.7.7


(1)はじめに
もともと、kuromoji に関しては、GoogleAppEngineで動かせないと思っていました。
なぜかというと、GoogleAppEngineにアップロードするファイルサイズには、10MBまでという制限があるから。
kuromojiの場合、Jarファイルのサイズが、11MBなので、これだと制限にひっかかるな~、と思っていました。

だけど、Eclipse上でアプリのwarフォルダの中を見ていたら、libフォルダの中に入っているライブラリ(jarファイル)の中に、サイズが20MB超えてるものがあった。
あれ、もしかしてjarファイルの場合、10MB超えていても大丈夫なのかな、って思って、kuromojiを試してみたら、動いた!


(2)kuromojiダウンロード
こちらのサイトに行くと、
kuromoji – japanese morphological analyzer

「Downloading」というところに、GitHubへのリンクがあるので、それをクリック。
kuromoji-0.7.7.tar.gzをダウンロードし、展開します。中にlibフォルダがあり、その中にkuromoji-0.7.7.jarがあります。

このjarファイルをクラスパスに通しておけば、準備完了。


(3)コーディングの流れ
第2回にもかいたんだけど、まずはじめに、注意点です。
僕はGoogleAppEngine上で動かしています。
で、今回テスト用に作成するものは、HTTPのGETリクエストに「q=解析したい文章」といったパラメータを付けることで、パラメータqに設定した文章を解析して、ブラウザに結果を返すように作っています。

そのため、以下のようにして、getリクエストのパラメータqを取得して、変数contentに格納するようにしています。

String content = req.getParameter("q");

また、結果をブラウザに返却するため、以下のメソッドで出力しています。

resp.getWriter().println("出力内容をここに書く");

このあたりはGAEならではの作法になります。

それを踏まえて、kuromojiの使い方をみていきます。

まず、一番シンプルなコーディングのサンプルは、kuromojiのサイトに載っています。
kuromoji – japanese morphological analyzer

また、その他もろもろの情報は以下のサイトを参考にしました。
(参考)Java製形態素解析器「Kuromoji」を試してみる
(参考)sample/kuromoji-sample/src/main/java/jp/mwsoft/sample/kuromoji/SimpleTokenizerSample.java at master · mwsoft/sample · GitHub
(参考)sample/kuromoji-sample/src/main/java/jp/mwsoft/sample/kuromoji/TokenizerModeSample.java at master · mwsoft/sample · GitHub

以下のような形で、Tokenizerクラスのtokenizeメソッド実行時に、形態素解析されます。(ここでは、contentに解析したい文章を入れています)

        Builder builder = Tokenizer.builder();
        Tokenizer tokenizer = builder.build();
        List<Token> tokensNormal = tokenizer.tokenize(content);

上記では、解析結果がtokensNormalの中に入っていて、取り出すときは以下のような感じ。

        String words="";

        for (Token token : tokensNormal){
            words = words +
                    token.getSurfaceForm() + " | "; 
        }

        words = words + "\n";

        resp.getWriter().println(words);

TokenクラスのgetSurfaceFormメソッドで、解析された単語を取出せます。

上記の場合、たとえば「日本経済新聞でモバゲーの記事を読んだ。」という文章を解析した結果が、
「日本経済新聞 | で | モバゲー | の | 記事 | を | 読ん | だ | 。 |」

といった感じで表示される。

Tokenクラスには、ほかにも情報取得用のメソッドが用意されていて、以下のようにすると、いろいろ取得できる。

String words="";

for (Token token : tokens){
    words = words +
            "--------------------------------------------------" + "\n" + 
            "allFeatures : " + token.getAllFeatures() + "\n" + 
            "partOfSpeech : " + token.getPartOfSpeech() + "\n" + 
            "position : " + token.getPosition() + "\n" + 
            "reading : " + token.getReading() + "\n" + 
            "SurfaceForm : " + token.getSurfaceForm() + "\n" + 
            "allFeaturesArray : " + Arrays.asList(token.getAllFeaturesArray()) + "\n" + 
            "辞書にある言葉? : " + token.isKnown() + "\n" + 
            "未知語? : " + token.isUnknown() + "\n" + 
            "ユーザ定義? : " + token.isUser() + "\n"; 
}
resp.getWriter().println(words);

この場合、取得結果はこんな感じで表示されます。

--------------------------------------------------
allFeatures : 名詞,固有名詞,組織,*,*,*,日本経済新聞,ニホンケイザイシンブン,ニホンケイザイシンブン
partOfSpeech : 名詞,固有名詞,組織,*
position : 0
reading : ニホンケイザイシンブン
SurfaceForm : 日本経済新聞
allFeaturesArray : [名詞, 固有名詞, 組織, *, *, *, 日本経済新聞, ニホンケイザイシンブン, ニホンケイザイシンブン]
辞書にある言葉? : true
未知語? : false
ユーザ定義? : false
--------------------------------------------------
allFeatures : 助詞,格助詞,一般,*,*,*,で,デ,デ
partOfSpeech : 助詞,格助詞,一般,*
position : 6
reading : デ
SurfaceForm : で
allFeaturesArray : [助詞, 格助詞, 一般, *, *, *, で, デ, デ]
辞書にある言葉? : true
未知語? : false
ユーザ定義? : false
--------------------------------------------------
    :   :   :
    ( 中略 )

getAllFeaturesメソッドで取得できる情報ですが、
kuromojiでは、MeCab-IPADIC辞書を使っているようで、MeCabの出力フォーマットになっているようです。

品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音

MeCabのフォーマットは、以下のサイトの「使い方」-「とりあえず解析してみる」のところに載っています。
MeCab: Yet Another Part-of-Speech and Morphological Analyzer

さて、kuromojiには3つのモードが用意されています。
kuromojiのホームページによると、

・Normal – regular segmentation
・Search – use a heuristic to do additional segmentation useful for search
・Extended – similar to search mode, but also unigram unknown words (experimental)

Searchモードでは、「日本経済新聞」といった単語が、「日本, 経済, 新聞」といった感じに細かく分割されます。
Lucene/Solrで全文検索するときに、「 ”日本”という検索ワードで検索したときに、”日本経済新聞”も引っかかる」 ようになるみたいです。

Extendedモードでは、「辞書に登録されていない単語については、unigramで解析されて、1文字ずつ分割されます。
たとえば、「Lucene」という単語は、「L, u, c, e, n, e」と分割されます。

で、モードの切り替えは、builder.build()する前に、builder.mode()でモードをセットします。(以下では、tokenizerを使いまわしています)

        Builder builder = Tokenizer.builder();

        // Normalモードで解析
        builder.mode(Mode.NORMAL);
        Tokenizer tokenizer = builder.build();
        List<Token> tokensNormal = tokenizer.tokenize(content);

        // Searchモードで解析
        builder.mode(Mode.SEARCH);
        tokenizer = builder.build();
        List<Token> tokensSearch = tokenizer.tokenize(content);

        // Extendsモードで解析
        builder.mode(Mode.EXTENDED);
        tokenizer = builder.build();
        List<Token> tokensExtended = tokenizer.tokenize(content);

ここまでの内容を踏まえて、次に、作成したサンプルのソースを書いておきます。


(4)ソースコード
実際に作成したサンプルアプリのソースコードと、動かした出力結果を書いておきます。
まずは、ソースコードから。

package sample;

import java.io.*;
import java.util.Arrays;
import java.util.List;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.atilika.kuromoji.Token;
import org.atilika.kuromoji.Tokenizer;
import org.atilika.kuromoji.Tokenizer.Builder;
import org.atilika.kuromoji.Tokenizer.Mode;

@SuppressWarnings("serial")
public class TestKuromoji_Servlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {

        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/plain; charset=UTF-8");
        resp.getWriter().println("TestKuromoji テスト");

        // パラメータ取得
        String content = req.getParameter("q");
        if (content == null || content.length() == 0){
            content = "";
        }

        resp.getWriter().printf("content= %s%n", content);

        Builder builder = Tokenizer.builder();

        // Normalモードで解析
        builder.mode(Mode.NORMAL);
        Tokenizer tokenizer = builder.build();
        List<Token> tokensNormal = tokenizer.tokenize(content);

        // Searchモードで解析
        builder.mode(Mode.SEARCH);
        tokenizer = builder.build();
        List<Token> tokensSearch = tokenizer.tokenize(content);

        // Extendsモードで解析
        builder.mode(Mode.EXTENDED);
        tokenizer = builder.build();
        List<Token> tokensExtended = tokenizer.tokenize(content);

        // 解析結果の表示(SurfaceFormのみ)
        resp.getWriter().println("NORMALモード(要約表示)=====================================");
        resp.getWriter().println(disp_SurfaceForm(tokensNormal));
        resp.getWriter().println("SEARCHモード(要約表示)=====================================");
        resp.getWriter().println(disp_SurfaceForm(tokensSearch));
        resp.getWriter().println("Extendsモード(要約表示)====================================");
        resp.getWriter().println(disp_SurfaceForm(tokensExtended));

        // 解析結果の表示(全パラメータ)
        resp.getWriter().println("NORMALモード(全パラメータ表示)================================");
        resp.getWriter().println(disp_all(tokensNormal));

    }

    public static String disp_SurfaceForm(List<Token> tokens) {
        String words="";

        for (Token token : tokens){
            words = words +
                    token.getSurfaceForm() + " | "; 
        }

        words = words + "\n";
        return words;
    }

    public static String disp_all(List<Token> tokens) {
        String words="";

        for (Token token : tokens){
            words = words +
                    "--------------------------------------------------" + "\n" + 
                    "allFeatures : " + token.getAllFeatures() + "\n" + 
                    "partOfSpeech : " + token.getPartOfSpeech() + "\n" + 
                    "position : " + token.getPosition() + "\n" + 
                    "reading : " + token.getReading() + "\n" + 
                    "SurfaceForm : " + token.getSurfaceForm() + "\n" + 
                    "allFeaturesArray : " + Arrays.asList(token.getAllFeaturesArray()) + "\n" + 
                    "辞書にある言葉? : " + token.isKnown() + "\n" + 
                    "未知語? : " + token.isUnknown() + "\n" + 
                    "ユーザ定義? : " + token.isUser() + "\n"; 
        }

        return words;
    }

}

次に、動かしてみた結果を載せておきます。

「日本経済新聞でモバゲーの記事を読んだ。2013年のLucene/Solrは検索エンジンです。」という文章を、形態素解析させてみました。以下のような感じでgetリクエストを送った後に帰ってきた結果になります。

http://URL/?q=日本経済新聞でモバゲーの記事を読んだ。2013年のLucene/Solrは検索エンジンです。

量が多いので、途中を省略してあります。

TestKuromoji テスト
content= 日本経済新聞でモバゲーの記事を読んだ。2013年のLucene/Solrは検索エンジンです。
NORMALモード(要約表示)======================================
日本経済新聞 | で | モバゲー | の | 記事 | を | 読ん | だ | 。 | 2013 | 年 | の | Lucene | / | Solr | は | 検索 | エンジン | です | 。 | 

SEARCHモード(要約表示)======================================
日本 | 経済 | 新聞 | で | モバゲー | の | 記事 | を | 読ん | だ | 。 | 2013 | 年 | の | Lucene | / | Solr | は | 検索 | エンジン | です | 。 | 

Extendsモード(要約表示)======================================
日本 | 経済 | 新聞 | で |  |  |  | ー | の | 記事 | を | 読ん | だ | 。 | 2 | 0 | 1 | 3 | 年 | の | L | u | c | e | n | e | / | S | o | l | r | は | 検索 | エ | ン | シ | ゙ | ン | です | 。 | 

NORMALモード(全パラメータ表示)======================================
--------------------------------------------------
allFeatures : 名詞,固有名詞,組織,*,*,*,日本経済新聞,ニホンケイザイシンブン,ニホンケイザイシンブン
partOfSpeech : 名詞,固有名詞,組織,*
position : 0
reading : ニホンケイザイシンブン
SurfaceForm : 日本経済新聞
allFeaturesArray : [名詞, 固有名詞, 組織, *, *, *, 日本経済新聞, ニホンケイザイシンブン, ニホンケイザイシンブン]
辞書にある言葉? : true
未知語? : false
ユーザ定義? : false
--------------------------------------------------
allFeatures : 助詞,格助詞,一般,*,*,*,で,デ,デ
partOfSpeech : 助詞,格助詞,一般,*
position : 6
reading : デ
SurfaceForm : で
allFeaturesArray : [助詞, 格助詞, 一般, *, *, *, で, デ, デ]
辞書にある言葉? : true
未知語? : false
ユーザ定義? : false
--------------------------------------------------
 :  :
 (省略)