Kana Character Conversion in Javascript (Part 1)

When dealing with enterprise customers in Japan building web applications, a lot of the times we deal with legacy systems that have specific character requirements

  1. Legacy systems that require half-width kana, but most browsers by default enter full-width kana. 
  2. Legacy systems that do not support 第二水準 SJIS characters, however most browsers support these kanji characters. 
The best way to address these character issues is typically upon first entry into the browser. Restricting and converting characters in the browser typically results in less work for integration systems downstream.

Part 1 of this article is going to focus on the first issue of half-width kana characters.

Half-width Kana Character Conversion Code Sample:
In order to setup our example we will first need to add the requisite libraries. To keep things simple we will only use JQuery.
Next we will need to create the html for kana entry:
The following code will be used for replacement of full-width kana characters with half-width kana characters:
Finally, we will add the conversion characters. Note that any replacement characters can be added or removed from this list.
Clicking on the "convert Full to Half" button, all characters which were entered full-width kana are converted to half-width kana.

The following code can also be added into KnockoutJS or into AngularJS so that the character conversion happens without having to click a button.  However, that will be the topic of the next blog post.

A working example can be seen on jsFiddle.
http://jsfiddle.net/mkgo69gk/3/

About the Author
Kent Horng worked in the Silicon Valley for 6 years at various startup companies, prior to moving to Japan and later joining Enrapt.  He has spent the last 8 years working on various web/cloud/tablet applications and is currently obsessed with iOS, AngularJS, and HTML5.

JSON Patch(RFC6902) with Spring MVC

イントロダクション

ウェブアプリケーションは多くのケースで以下のようにサーバと同期します。
  • ボタンクリック時に画面で更新された値をサーバに送り、サーバから必要な情報を取得する
  • スクロール時に表示に必要な情報を取得する
  • ページ遷移時に次のページに必要な情報を取得する
このように特定のイベント実行時だけサーバと同期するとき問題になることがあるのが、データサイズが大きいと通信に時間がかかるということです。
更新されたフィールドの数が多く何層にもネストしているようなModelをやりとりするケースでは1回の通信に対する待ち時間が長いと実用には耐えられません。
特にモバイル端末のように制限された環境ではなんらかの対策は必須です。
これを解決するには、
  • 最小単位のデータを常にサーバに送る
    1項目単位で変更をサーバに送り、ユーザのセッション単位にサーバ側にクライアントと同期されたデータを一時的に保持しておく。そして特定のボタンクリック時にサーバ側にある画面の情報をコミット(永続化, or etc...)する。
  • 前回サーバ通信時との差分だけをサーバに送る
    直近にサーバ通信した際のデータをクライアント側に保持しておき、特定のイベント実行時にその前回のデータと変更後のデータとの差分だけを送信する。
この記事では後者(前回サーバ通信時との差分だけをサーバに送る)に対する1つの回答として JSON Patch を使います。

JSON Patch(RFC6902) とは

JSON形式を使ってPatchを表現した JSON PatchというものがRFC(RFC6902)にあります。
これは、差分適用のオペレーション(addやreplaceなど)と、差分をHTTP PATCHメソッドを使って送信するものです。
クライアントからは、下記のようなJSONを送ります (下記は属性の追加例で、path, valueが追加データ)。
{ "op": "add", "path": "/baz/bat", "value": "qux" }

Javaでは現状だとJersey(JAX-RSのリファレンス実装)には実装されています。
ですが、Javaで大規模なWeb applicationを開発する場合、Spring MVCが使われることが多く、そのSpring MVCでJSONを扱う場合には、Springがデフォルトで提供しているJacksonを使用したJSON Deserializer/Serializerを使うケースがほとんどでしょう。
そこで今回は Spring MVC(+ Jackson) にJSON Patchを組込みたいと思います。

JSON PatchをSpring MVC frameworkで使えるようにする

JSON Patchが扱えるようにSpring MVC + Jacksonの拡張します。

I/Fを決める

まずは使う側がどのように使用(実装)したいかを決めましょう。
SpringではControllerは以下のように定義します
@RequestMapping(value = "/memo/{id}", method = RequestMethod.PATCH, produces = "application/json-patch+json")
@ResponseBody
public ? patchMemo(@PathVariable final long id, @RequestBody ? memo) {
  ...
  return memo;
}
引数と戻り値にはなにを指定するのが良いでしょうか。
まず引数には、
部分的に値を保持しているものが渡ってくるのが良さそうです。
以下の様なI/Fを持つPartialModel型を作りましょう。
PartialModel.java
package diff;

@Partial
public interface PartialModel<T> {

  public T apply(T original) throws IllegalPatchException;
}
Partial.java
(あとで使うJacksonの拡張のためにアノテーションを用意します)
package diff;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Partial {

}
これは以下のような使われ方を想定しています。
public ? patchMemo(@PathVariable final long id, @RequestBody PartialModel<Memo> partialMemo) {

  // 永続化されたメモをどこからか取ってくる
  Memo original = repository.getMemo(id);

  // 引数の部分メモに永続化されていたメモを適用する
  Memo current = partialMemo.apply(original);

  // 更新されたメモを永続化する
  repository.updateMemo(current);

  ....
そして戻り値は、
クライアント側に差分だけを返せるるように更新前と更新後の値を戻します。
以下のように更新前と更新後の値を保持できるDifference型を作ります。
  ...
  return Difference.of(original, current);
}
Difference.java
package diff;

@Subtractable
public class Difference<T> {

  public final T original;
  public final T updated;

  public Difference(T original, T updated) {
    this.original = original;
    this.updated = updated;
  }

  public static <T> Difference<T> of(T original, T updated) {
    return new Difference<T>(original, updated);
  }
}
Subtractable.java
(あとで使うJacksonの拡張のためにアノテーションを用意します)
package diff;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subtractable {

}
最終的にControllerのメソッドはこうなりました。
@RequestMapping(value = "/memo/{id}", method = RequestMethod.PATCH, produces = "application/json-patch+json")
@ResponseBody
public Difference<Memo> patchMemo(@PathVariable final long id, @RequestBody PartialModel<Memo> partialMemo) {

  // 永続化されたメモをどこからか取ってくる
  Memo original = repository.getMemo(id);

  // 引数の部分メモに永続化されていたメモを適用する
  Memo current = partialMemo.apply(original);

  // 最新のMemoに対する処理
  repository.updateMemo(current);
  ...

  // クライアントには差分だけ返す
  return Difference.of(original, current);
}
I/Fが決まったので、
これに合わせてSpring frameworkの拡張をしましょう。

Spring WEB MVC Frameworkを拡張する

まずは、
HttpMessageConverterへJSONをやりとりするためのMessageConverterを設定するために、以下のようにSpringへJSONを扱うための設定を追加します。
@Configuration
public class SampleJsonConfig extends WebMvcConfigurerAdapter {

  @Override
  public List extends HttpMessageConverter> createHttpMessageConverter() {

    // JSON(application/json)用Message converter
    // ('application/json', 'application/*+json'のときにこれが使用される)
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    ...
    return Arrays.asList(converter);
  }
}
これだけだとRFC6902には対応できていないので、MappingJackson2HttpMessageConverterがデフォルトで使用するObjectMapper(*)を拡張します。
(*)Jacksonはcom.fasterxml.jackson.databind.ObjectMapperがJavaのObjectのSerialize/Deserialize処理を提供しています。
以下のようにObjectMapperを継承しJson patch用の処理を追加します。
public class SampleObjectMapper extends ObjectMapper {

  public SampleObjectMapper() {

    // DEFAULT_ANNOTATION_INTROSPECTORはJacksonがデフォルトで提供するannotation処理機能
    AnnotationIntrospector introspector = AnnotationIntrospector.pair(new PartialJsonAnnotationIntrospector(this), DEFAULT_ANNOTATION_INTROSPECTOR);

    setAnnotationIntrospector(introspector);
  }
}
Jacksonのデフォルト機能を簡単に拡張するには、annotation introspectorを追加します。これはannotationベースでダイナミックに処理を追加するための機構です。新しく作る PartialJsonAnnotationIntrospector を上のように追加することで簡単にJacksonを拡張することができます。
次にこのPartialJsonAnnotationIntrospectorの実装をしましょう。
ここには以下のようにDeserializeとSerializeの手段を書きます。
class PartialJsonAnnotationIntrospector extends NopAnnotationIntrospector {

  private final ObjectMapper mapper;

  PartialJsonAnnotationIntrospector(ObjectMapper mapper) {
    this.mapper = mapper;
  }

  @Override
  public boolean isAnnotationBundle(Annotation ann) {
    return false;
  }

  @Override
  public Object findDeserializer(Annotated a) {
    // a = Deserialize対象のClass

    // PartialというannotationがあればJSON patch専用のDeserializerを返す
    Partial ann = a.getAnnotation(Partial.class);
    if (ann != null) {
      return new PartialJsonDeserializer(this.mapper);
    }
    return null;
  }

  @Override
  public Object findSerializer(Annotated a) {
    // a = Serialize対象のClass

    // SubtractableというannotationがあればJSON patch専用のDeserializerを返す
    Subtractable ann = a.getAnnotation(Subtractable.class);
    if (ann != null) {
      return new PartialJsonSerializer(this.mapper);
    }
    return null;
  }
}
あとは、
JSON patch専用のDeserializer, Serializerの実装をしたらおしまいです。
一からJSON patchのDeserializer, Serializerを作るのは本題ではありませんので、簡単のために今回この部分にはありもののライブラリを利用することにします。
以下がDeserializerの実装です。
import java.io.IOException;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

final class PartialJsonDeserializer extends JsonDeserializer<Object> {

  private final ObjectMapper mapper;

  public PartialJsonDeserializer(ObjectMapper mapper) {
    this.mapper = mapper;
  }

  @Override
  public Object deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {
    return new PartialJson<Object>(this.mapper, (JsonNode) jp.readValueAsTree());
  }
}
Deserizeメソッドが戻しているPartialJsonクラスは以下のようにします。
import java.io.IOException;

import diff.IllegalPatchException;
import diff.PartialModel;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.fge.jsonpatch.JsonPatch;
import com.github.fge.jsonpatch.JsonPatchException;

public class PartialJson<T> implements PartialModel<T> {
  private final ObjectMapper mapper;
  private final JsonNode node;

  public PartialJson(ObjectMapper mapper, JsonNode node) {
    this.mapper = mapper;
    this.node = node;
  }

  @Override
  @SuppressWarnings("unchecked")
  public T apply(T original) throws IllegalPatchException {
    try {
      JsonPatch patch = JsonPatch.fromJson((JsonNode) this.node);
      JsonNode patched = patch.apply(mapper.valueToTree(original));
      return mapper.treeToValue(patched, (Class<T>)original.getClass());
    } catch (IllegalArgumentException | JsonPatchException | IOException e) {
      throw new IllegalPatchException(e);
    }
  }
}
そして、Serializerです。
import java.io.IOException;

import diff.Difference;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.github.fge.jsonpatch.diff.JsonDiff;

final class PartialJsonSerializer extends JsonSerializer<Difference> {

  private final ObjectMapper mapper;

  protected PartialJsonSerializer(ObjectMapper mapper) {
    this.mapper = mapper;
  }

  @Override
  public void serialize(Difference value, JsonGenerator jgen,
      SerializerProvider provider) throws IOException,
      JsonProcessingException {
    provider.defaultSerializeValue(JsonDiff.asJson(
        mapper.valueToTree(value.original),
        mapper.valueToTree(value.updated)), jgen);
  }
}
これでSpringの拡張は完了です。
使ったライブラリ
Spring 4.0.x
Jackson 2.2.x
json-patch 1.7 https://github.com/fge/json-patch

使ってみる

最後に、上のコードを組み込んでサーバを起動したのち、HTTPでアクセスしてみましょう。
$ curl http://localhost:8080/memo/1 -X PATCH -H "Content-Type: application/json-patch+json" -H "Accept: application/json-patch+json" -d '[ { "op": "replace", "path": "/id", "value": 2 } ]'
サーバに送った差分が適用され、クライアントに適用するための新しい差分が返ってきました。
[{"op":"replace","path":"/id","value":"2"}]

シングルページアプリケーションとDurandal.js

先月終わったばっかりのエンタープライズ向けのプロジェクトで最近話題になっているシングルページアプリケーションを作ったので、感想をシェアしたいと思います。


シングルページアプリケーション(SPA)とは?

シングルページアプリケーション(以下:SPA)という言葉、このページを読んでる皆さんはおそらく聞いたことがあると思いますが、実際にSPAで大規模プロジェクトをこなした経験を踏まえ、いくつかの観点からSPAの特徴をまとめておきます。

SPAは、画面の挙動と画面間の遷移が完全にクライアントサイドのJavaScriptにコントロールされ、サーバーの役割は主にクライアントが求めるデータの提供/処理に限るアプリケーションのことです。


主なメリット

  • リッチなUI

    UIが豊富で複雑なアプリケーションを作るときに、画面の挙動と画面間の遷移をコントロールしやすく、ネイティブアプリケーションのような感覚をユーザにもたらすことができます。

  • 通信量の削減で、反応速度アップ

    画面を構成するほとんどのリソース初期ロードし、あとはデータ通信のみなので、3GやLTEを使うモバイル端末にとってやさしく、画面表示が速いです。例えば、サーバーからのデータを必要としない画面を即時に表示できます。

    一方、画面を表示するのに複数のAPIからのデータが必要になったときに、平行の複数のHTTPリクエストでそのデータが取得できるので、こちらも画面の表示をはやくすることができます。

SPA用のフレームワーク

SPA用のフレームワークは幾つかありますが、僕らが使ったのは、Durandal.js というものです。

Durandal.js を選んだメインの理由は以下の通りです。

  • Durandal.js 内部では、実績の多い Knockout.js を利用している
  • SPAを作るのに必要な機能を完備している
  • Durandal.js による開発は非常に容易で、学習コストが比較的に低い
  • Durandal.js 内部では、 Require.js を利用しているので、モジュラリティが高く、様々なライブラリが簡単に使える

大規模の業務系アプリケーションの作成に Durandal.js を使った経験を顧みると、フレームワークとしての簡単さとアプリケーションの実装における自由度の高さが魅力と感じました。

上記で平行の複数のHTTPリクエストのメリットの話しをしていたので、Durandal.jsでの実際の実装方法を紹介したいと思います。

ちなみに、使ったバージョンは1.2でしたが、既に2013年8月にバージョン2.0がリリース済みで、このブログを執筆時点で、2.1は開発中というステータスです。

DB2用のHibernateカスタムDialect作成

HibernateのカスタムDialect

 DB2用にHibernateのSQLのカスタムDialectを作成したので、作成方法をご紹介します。HibernateなどのORマッパーを使用してDB操作する場合、開発者は接続用のDBに合わせて、使用するSQLのDialectを指定するだけ、というケースがほとんどだと思います。
 ただ今回は、下記の事情からDialectをカスタマイズすることになりました。

事情1.今回は、EntityクラスからDDLを生成していた
事情2.文字列データ型としてDB2のVARGRAPHICを使うことにした
 (HibernateのデフォルトのDialectだとJavaのStringをVARCHARに変換します。)
事情3.開発環境ではH2を使用していたため、Entityクラスのアノテーションで直接データ型指定ができなかった

 事情3について補足すると、文字列データ型のVARGRAPHICを指定する方法としては、Entityクラスに直接指定する方法もあるのですが、そうすると開発環境で使用しているH2にはないデータ型になってしまうので、この方法は使えません。

 HibernateがEntityクラス(Javaのソースコード)からDDLを生成するタイミングで各DB用のDialectが言語の型を、DB専用のデータ型に変換します。ここにカスタムDialectを作成することで、開発環境ではH2を使いつつ、本番環境ではDB2を利用することも可能となります。今回は、DB2Dialectのデフォルトの動作を変更し、JavaのStringをDB2のVARCHARに変換するところを、VARGRAPHICに変換するようにカスタムDialectを作成しました。

Dialectの作成方法/設定方法

・作成するクラス:
 下記の2つのクラスを継承したクラス
 org.hibernate.dialect.DB2Dialect
 org.hibernate.dialect.unique.DB2UniqueDelegate


・設定箇所:
 Dialectを指定している設定ファイルも変更する必要があります。
 hibernate.dialect:<パッケージ名>.CustomDB2Dialect
 (Hibernateのプロパティです。設定ファイルは、様々な種類があるので割愛しています。)

これで、Hibernateがスキーマを自動生成するときにStringに対応するDBのデータ型がVARGRAPHICになります。

所感

 地味な機能ですが、必要なケースは意外とありそうだなというのが作ってみての感想です。
 今回はデータ型のマッピングのみの修正でしたが、Dialectを修正すると実行時の値の変換などもカスタマイズできます。例えば、DB2のデータ型には、真偽値がありません。Javaのbooleanは、DB2のsmallintになり、trueは数値の1、falseは数値の0に変換されます。これも裏でDialectが密かにやっていることです。

Hibernate(4.2)のDialectに関しては、下記に情報がありますので参考にしてください。
http://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch01.html#configuring-dialects


Popular Posts