bluemixでgwtアプリケーションを動かす

103
Bluemix で GWT

Upload: shisei-hanai

Post on 12-Aug-2015

513 views

Category:

Engineering


0 download

TRANSCRIPT

Page 1: BluemixでGWTアプリケーションを動かす

Bluemixで GWT

Page 2: BluemixでGWTアプリケーションを動かす

概要• GWTは、 Googleが提供しているクライアント・サイド用のツールキット

• Javaから JavaScriptに変換して実行するため、クライアントサイドもJavaで書くことができる

• 今回は、これを使って簡単なアプリケーションを作成して、 Bluemixにデプロイしてみます

Page 3: BluemixでGWTアプリケーションを動かす

おことわり

• 本資料の記載内容は、私が個人的に調べた内容であり、正式な日本 IBMのテストやレビューを受けておりません。内容について、できる限り正確を期すよう努めてはおりますが、いかなる明示または暗黙の保証も責任も負いかねます。本資料の情報は、使用先の責任において使用されるべきものであることを、あらかじめご了承ください。

Page 4: BluemixでGWTアプリケーションを動かす

前提• Java SE 7残念ながら、 Bluemixの runtimeは、まだ IBM JDK 7ベースなので、 Java8でそのままコンパイルすると動きません。 Java8を開発に使う場合用は、 -target 1.7、 -source 1.7を指定し、 Java8で追加されたライブラリを使わないように注意する必要があります。

• Eclipse LUNAEclipse IDE for Java EE Developersをインストールしておいてください。

• BluemixBluemixへのアカウント登録と、 Cloud Foundry CLIのインストールは終わっていることを前提とします。

• GWT紙面の都合上、 GWTの詳細については解説しませんので、適宜 GWTの解説書を参照してください。

Page 5: BluemixでGWTアプリケーションを動かす

GWTプラグインのインストール• Eclipseを起動して、プラグインをインストールする

• Help => Install New Software...

• ダイアログ右上の Add...をクリック

• 以下のように入力

Page 6: BluemixでGWTアプリケーションを動かす

GWTプラグインのインストール• 少し待つとインストール項目の選択になるので、以下のように選択する

あとはウィザードに従ってインストールを進めてください最後の Eclipseを再起動するか聞かれるので、再起動してください

Page 7: BluemixでGWTアプリケーションを動かす

Hello World

Page 8: BluemixでGWTアプリケーションを動かす

プロジェクト作成• まずは Hello Worldを確認します。

• Eclipseで、 File => New => otherを選びます

• 下のダイアログが表示されるので、 Google => Web Application Projectを選びます

Page 9: BluemixでGWTアプリケーションを動かす

プロジェクト作成• Nextを押して、以下のようにします (Use Google App Engineのチェックを外してください )

Page 10: BluemixでGWTアプリケーションを動かす

プロジェクト設定• Finishを押すとプロジェクトが出来ます。

• 既にサンプルが作成されているので、 Project Explorerで helloを右クリックして、 Run As => Web Application(GWT Super Dev Mode)を選びます。

• しばらくすると、 Development Modeビューに以下のように URLが表示されるので、ダブルクリックするとデフォルトのブラウザが開きます。

Page 11: BluemixでGWTアプリケーションを動かす

サンプルの起動• GWT Userのところに名前を入れて、 Enterを押すと、ダイアログが表示されます

Page 12: BluemixでGWTアプリケーションを動かす

Bluemixにデプロイ• 次に Bluemixにデプロイしてみます

• 前提で述べた通り、 2015/7現在、 Bluemix側の Javaは Java7なので、もしも手元の環境が Java8の場合は、 Project Explorerで、 helloを右クリックして Propertiesを選び、以下のように設定を変更します

Page 13: BluemixでGWTアプリケーションを動かす

Javaの設定• 同様に Facetを設定します

Page 14: BluemixでGWTアプリケーションを動かす

GWT compile

• GWT compileを実行しますこれによって、クライアント側の Javaのコードが JavaScriptに変換されます。 Project Explorerから helloを右クリックし Google => GWT Compileを選びます

Page 15: BluemixでGWTアプリケーションを動かす

GWT Compile

• Consoleにエラーが出ていないことを確認し、生成された場所を確認します

Page 16: BluemixでGWTアプリケーションを動かす

Bluemixにアップロード• cf loginを実行してログインしたら、 GWT Compileされた場所の 1つ上のディレクトリで cf pushします。

$ cd /Users/shanai/workspace/gwt/hello/war/hello$ cd ..$ lsHello.css Hello.html WEB-INF favicon.icohello$ cf push ruimohelloUpdating app ruimohello in org [email protected] / space dev as [email protected]... state since cpu memory disk details#0 running 2015-07-18 09:12:46 AM 0.0% 179.1M of 1G 228.2M of 1G

ここの名前は、 Bluemix内で他とぶつからない名前を指定します

Page 17: BluemixでGWTアプリケーションを動かす

Bluemixで実行• ダッシュボードで確認しますこ

こをクリック

実行中になっているのを確認

Page 18: BluemixでGWTアプリケーションを動かす

Bluemixで実行

Page 19: BluemixでGWTアプリケーションを動かす

地図アプリケーションの作成

Page 20: BluemixでGWTアプリケーションを動かす

地図アプリケーション• ある程度インタラクティブで、かつクライアント側の JavaScriptとの連携が必要なアプリケーションが例としてふさわしいので、簡単な地図アプリケーションを作成します現在地と周辺地

図が表示される地図をクリックすると、周辺施設が表示される

選んだ施設が右に追加される

施設をクリックするとピンが表示さ

れる

Page 21: BluemixでGWTアプリケーションを動かす

全画面アプリケーション• 最初に作成した Hello, Worldアプリケーションは、画面の一部に GWTの動的コンテンツを埋め込んでいました。

<table align="center"> <tr> <td colspan="2" style="font-weight:bold;">Please enter your name:</td> </tr> <tr> <td id="nameFieldContainer"></td> <td id="sendButtonContainer"></td> </tr> <tr> <td colspan="2" style="color:red;" id="errorLabelContainer"></td> </tr></table>

Hello.html

public void onModuleLoad() { final Button sendButton = new Button("Send"); final TextBox nameField = new TextBox(); nameField.setText("GWT User"); final Label errorLabel = new Label();

sendButton.addStyleName("sendButton");

RootPanel.get("nameFieldContainer").add(nameField); RootPanel.get("sendButtonContainer").add(sendButton); RootPanel.get("errorLabelContainer").add(errorLabel);

Hello.java

Page 22: BluemixでGWTアプリケーションを動かす

全画面アプリケーション• 今回は画面全体を GWTで描画します。 GWTのレイアウト・パネルを使うことで画面サイズの変更に柔軟に対応することができます。

RootPanel

DockLayoutPanel

HTML(text)

SplitLayoutPanel

GoogleMap

DockLayoutPanel

更新

DataGrid

施設リスト

Page 23: BluemixでGWTアプリケーションを動かす

プロジェクト作成• それでは、 Hello, World作成時同様にプロジェクトを作成します。今回はmymapという名前にしました。

Page 24: BluemixでGWTアプリケーションを動かす

画面の雛形• 画面の雛形を作成します。

• src/mymap/ client/Mymap.javaのonModuleLoad()を右のように変更します。

RootPanelDockLayoutPanel

SplitLayoutPanel

public void onModuleLoad() { DockLayoutPanel dlp = new DockLayoutPanel(Style.Unit.EM); dlp.addStyleName("rootLayout"); dlp.addNorth(new HTML("My application"), 2);

SplitLayoutPanel slp = new SplitLayoutPanel(); slp.addEast(new HTML("施設リスト "), 120); slp.add(new HTML("<div id='map-canvas'></div>"));

dlp.add(slp); RootPanel.get().add(dlp);}

施設リスト

My application

<div id='map-canvas'></div>

Page 25: BluemixでGWTアプリケーションを動かす

画面の雛形• 一方、war/Mymap.htmlの方は、<body>内の h1や tableタグを削除します。

<body> <!-- OPTIONAL: include this if you want history support --> <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe> <!-- RECOMMENDED if your web app will not function without JavaScript enabled --> <noscript> <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif"> Your web browser must have JavaScript enabled in order for this application to display correctly. </div> </noscript> </body>

Page 26: BluemixでGWTアプリケーションを動かす

画面の雛形• Mymap.cssの内容は、元あったものを全て削除して以下のようにします。

これで全画面アプリケーションになりますhtml, body { height: 100%; margin: 0px !important;}

.rootLayout { height: 100%; width: 100%;}

GWT標準の cssが、 bodyにマージンを設定してしまうので、これで削除しないと、余計なスクロールバーが出てしまう

Page 27: BluemixでGWTアプリケーションを動かす

画面の確認• ここまでできたら、 Hello, Worldの時と同様に、 Run As => Web

Application (Gwt Super Dev Mode)で起動して画面を確認します。 GWTのレイアウト・パネルのおかげでリサイズしても画面の構成が維持されることが確認できます。

マウスで境界をドラッグ可能

Page 28: BluemixでGWTアプリケーションを動かす

コード変更時の注意• htmlや cssを修正した場合ブラウザをリロードしてください。それでも表示されない場合は、 1つ下の「クライアント側の Javaコードを修正した時」の方法を実行

• クライアント側の Javaコードを修正した時ブラウザ右下のアイコンをクリック

• サーバ側の Javaコードを修正した時

サーバをリロードします

Page 29: BluemixでGWTアプリケーションを動かす

地図を表示する• それでは地図を表示してみましょう。

• Google Map APIの使用には設定が必要ですhttps://console.developers.google.com/ にアクセスして自分のGoogleアカウントでログインしてください。

• 以下のようにして、 Google Mapを有効にします。

Page 30: BluemixでGWTアプリケーションを動かす

API Keyを取得する• 以下の手順で API Keyを取得します。

今回は開発用なのでリファラ制限を指定していませんが、公開する際にはリファラの設定をし

てください。無料で使用できるのは、現在

1000リクエスト / 日までなので、キーが漏れて他の人に使われてしまうと APIが使えなくなって

しまいます。(Bluemix上にデプロイした際の

URLを指定 )

Page 31: BluemixでGWTアプリケーションを動かす

API Keyを保存

メモしておきます

Page 32: BluemixでGWTアプリケーションを動かす

Google Mapの組込み<script type="text/javascript" language="javascript" src="mymap/mymap.nocache.js"></script><script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=xxxxxxxxxxxxxxxx"></script><script type="text/javascript"> function initialize() { currentLoc(function(latitude, longitude) { showMap(latitude, longitude); }); }

続く ...

war/Mymap.htmlにscriptタグを追加します。ここに API Keyを指定し

ます初期化メソッド。 currentLoc()

で現在地を取得して、 showMap()で地図を表示

( どちらも次頁で解説 )

Page 33: BluemixでGWTアプリケーションを動かす

Google Mapの組込み前頁から続き

function currentLoc(f) { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( function(pos) { f(pos.coords.latitude, pos.coords.longitude); }, function(e) { alert(' 現在地の取得が許可されていません。 '); } ); } else { alert(' 現在地が取得できません。 '); } }

function showMap(latitude, longitude) { map = new google.maps.Map(document.getElementById('map-canvas'), { center: new google.maps.LatLng(latitude, longitude), zoom: 15 }); }</script>

navigatorを使って現在地を取得するメ

ソッド

指定された場所の地図を表示

これは、地図を表示する divタグの id 属性 (Mymap.javaと

対応 )

Page 34: BluemixでGWTアプリケーションを動かす

GWT側から JavaScriptを呼び出し• あとは、この initialize() メソッドを呼び出せば okです。通常であれば

ページの onloadなどをトリガーにすれば良いのですが、今回は、 GWT側の初期化が終わってから呼ばれるようにします。

• GWTは、 Javaを JavaScriptに変換する仕組みですが、生のJavaScriptとやりとりするための仕組みが用意されています。

• Mymap.javaを以下のように変更します。public class Mymap implements EntryPoint { native void initialize() /*-{ $wnd.initialize(); }-*/;

public void onModuleLoad() { initialize(); DockLayoutPanel dlp = new DockLayoutPanel(Style.Unit.EM); dlp.addStyleName("rootLayout"); dlp.addNorth(new HTML("My application"), 2);

このように特殊なタグで囲むと、 JavaScriptを直接書くことができる。$wndを指定すると、Window オブジェ

クトを取得できるので、これでMymap.html側の initialize()を呼び出せ

Page 35: BluemixでGWTアプリケーションを動かす

地図の表示• 最後に、Mymap.cssを変更して地図がパネル内にフィットするようにします。#map-canvas, .rootLayout {

height: 100%; width: 100%;}

アプリケーションを実行すると、以下のように現在地の地図が表示されます

この状態のアプリケーションは、mymap00.zipで公開されてい

ます

Page 36: BluemixでGWTアプリケーションを動かす

施設情報の表示

Page 37: BluemixでGWTアプリケーションを動かす

緯度、経度から施設情報を取得• 地図がクリックされたら、クリックされた場所をもとに緯度、経度を取得して、その周辺の施設情報を取得します。

• これは、 Googleの Places APIで取得できます。Mapの時と同じように以下のようにして APIを有効化します。

Page 38: BluemixでGWTアプリケーションを動かす

施設情報の取得• Places APIは以下のような GETリクエストを送ることで、 JSONで情報を取得できます。https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=35.587981,139.663820&radius=500&key=xxxxxxxxxxxxxxxx

• locationパラメータが緯度、経度

• radiusが半径

• keyには、Mapの時と同じキーを指定

• GETなので普通にブラウザでアクセスすれば、 JSONが表示されます。

Page 39: BluemixでGWTアプリケーションを動かす

施設情報の取得{... "results" : [ { "geometry" : { "location" : { "lat" : 35.5887497, "lng" : 139.674198 },... }, "icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/geocode-71.png", "id" : "dc3f145c15f68b3fa5f0b3a41d59f819f3975dd1", "name" : " 田園調布1丁目 ", "place_id" : "ChIJpUK62EH1GGARS_Z2KSq8N88",... }, { "geometry" : { "location" : { "lat" : 35.587554, "lng" : 139.668713 } },... "icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/generic_business-71.png", "id" : "10fbe9518e5a11444c7b7c358eb0dd270e0ab6ee", "name" : " 多摩川浅間神社 ",

位置と施設のセットが複数返ってくる

Page 40: BluemixでGWTアプリケーションを動かす

SOP(Same Origin Policy)

• GWTで他のサーバに httpリクエストを上げようとすると、 SOPに引っかかります。

• GWTは Javaで書いたコードを JavaScriptに変換して、ブラウザで動かす仕組みなので、ホストページ (今回なら、Mymap.html)を取得したサーバとは別のサーバに httpリクエストすることは直接にはできません。

• これを回避する方法は幾つかあるのですが、ここではもっとも無難と思われる、自分のサーバを経由する方法を使います。

Page 41: BluemixでGWTアプリケーションを動かす

SOP

ブラウザ自分のサーバ

Mymap.html

JavaScript

Google Places

ブラウザ自分のサーバ

Mymap.html

JavaScript

Google Places

直接の相手が、Mymap.htmlを取得したサーバと同一なら ok

Page 42: BluemixでGWTアプリケーションを動かす

サーバキーを取得する• Googleのデベロッパコンソールで、今度はサーバキーを取得します

Page 43: BluemixでGWTアプリケーションを動かす

サーバキーを取得する

今回はテスト用なので、 IPアドレス制限はしません。本稼動する際は IPアド

レスを指定してください。

Page 44: BluemixでGWTアプリケーションを動かす

サーバキーを取得する

メモする

Page 45: BluemixでGWTアプリケーションを動かす

キーを管理するクラスを作る• 今のようにキーを htmlファイルなどに埋め込んでいると、キーを変更したい時に面倒ですし、うっかりキーが漏れてしまう危険性もあります。

• 今回は Bluemix(Cloud Foundry)を使うこともあるので、キーは環境変数でアプリケーションに渡すようにします。

• そのためにキーを管理するクラスを作成しておきます。

Page 46: BluemixでGWTアプリケーションを動かす

キーを管理するクラスpackage mymap.client;

import java.io.Serializable;import static java.util.Objects.requireNonNull;

public class Settings implements Serializable { private static final long serialVersionUID = -6808664382199552658L;

String apiKey; String serverApiKey;

Settings() { apiKey = ""; serverApiKey = ""; } public Settings(String apiKey, String serverApiKey) { this.apiKey = requireNonNull(apiKey, "apiKey"); this.serverApiKey = requireNonNull(serverApiKey, "serverApiKey"); }

public String getApiKey() { return this.apiKey; } public String getServerApiKey() { return this.serverApiKey; }}

サーバ、クライアント間でやりとりできるように直列化可能と

する

GWTの直列化の制限により、デフォルトコンストラクタを用意 (アプリケーションからは使

えないようにパッケージプライベートに )。フィールドも残念ながら GWTの制限で final

宣言できないので注意

nullでないことが保証されているイ

ミュータブルクラス

Page 47: BluemixでGWTアプリケーションを動かす

Settingsのファクトリpackage mymap.server;

import static java.util.Objects.requireNonNull;import mymap.client.Settings;

public class SettingsFactory { static final SettingsFactory THE_INSTANCE = new SettingsFactory(); final Settings settings;

public SettingsFactory() { settings = new Settings (requireNonNull(System.getenv("apiKey"), "Set Google api key by setting environment variable 'apiKey'"), requireNonNull(System.getenv("serverApiKey"), "Set Gooogle api server key by setting environment variable 'serverApiKey'")); } public Settings getSettings() { return settings; }

public static SettingsFactory getInstance() { return THE_INSTANCE; }}

Settingsのファクトリクラス

環境変数 apiKey/serverApiKeyから取得

Page 48: BluemixでGWTアプリケーションを動かす

GWT RPCを作成• 今回は開発の手間を減らすため、クライアント・サーバ間の通信に、 GWT RPCを使います。

• JSONのパースがどこかで必要ですが、残念ながら Javaは標準ではJSONのパーサが無く、ライブラリの追加など面倒なので、今回はサーバからは JSONを文字列でそのまま返して、クライアント側でパースすることにします。

• まずサーバとのインターフェイスを作成します。package mymap.client;

import com.google.gwt.user.client.rpc.RemoteService;import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("places")public interface PlacesService extends RemoteService { String places(double latitude, double longitude);}

緯度、経度を受け取って、結果を JSON 文字列で返す

Page 49: BluemixでGWTアプリケーションを動かす

Asyncインターフェイスの生成すると、 Eclipseで Asyncインターフェイスを作るか聞かれるので、電球をクリックして作成しま

Page 50: BluemixでGWTアプリケーションを動かす

Asyncインターフェイスの作成

そのまま Finishで作成

package mymap.client;

import com.google.gwt.user.client.rpc.AsyncCallback;

public interface PlacesServiceAsync { void places(double latitude, double longitude, AsyncCallback<String> callback);}

クライアント側は、このインターフェイスで呼び出すことになりま

Page 51: BluemixでGWTアプリケーションを動かす

サーバ間通信• サーバ間通信は Java標準の HttpURLConnectionを使いますが、資源

管理を楽にするためヘルパクラスを作っておきます。

package mymap.server;

import java.net.HttpURLConnection;import java.net.URI;

public abstract class WithUrlConnection<T> { public T process(String url) throws Exception { URI uri = new URI(url); HttpURLConnection conn = (HttpURLConnection)uri.toURL().openConnection(); try { return perform(conn); } finally { conn.disconnect(); } }

abstract protected T perform(HttpURLConnection conn) throws Exception;}

典型的なテンプレート・メソッドパターンで資源管理を行うだ

Page 52: BluemixでGWTアプリケーションを動かす

Places APIの呼び出しpackage mymap.server;

import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.HttpURLConnection;

import mymap.client.PlacesService;import mymap.client.Settings;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

@SuppressWarnings("serial")public class PlacesServiceImpl extends RemoteServiceServlet implements PlacesService { static final Settings settings = SettingsFactory.getInstance().getSettings(); @Override public String places(double latitude, double longitude) { final String url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=" + latitude + "," + longitude + "&radius=200&key=" + settings.getServerApiKey();続く

PlacesServiceを実装

Settingsを取得

キーを埋め込んだ Places APIのURL

Page 53: BluemixでGWTアプリケーションを動かす

Places APIの呼び出し try { return new WithUrlConnection<String>() { @Override protected String perform(HttpURLConnection conn) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); copy(conn.getInputStream(), baos);

return new String(baos.toByteArray(), "utf-8"); } }.process(url); } catch (Exception e) { e.printStackTrace(); return null; } }

void copy(InputStream is, OutputStream os) throws IOException { byte[] buf = new byte[4096]; int readLen; while ((readLen = is.read(buf)) != -1) { os.write(buf, 0, readLen); } }}

さきほどのヘルパクラスでPlaces APIをリクエストし、レスポンスを utf-8 文字列とし

て返す本番ではエラーレスポンスを返すなりしてくださ

Page 54: BluemixでGWTアプリケーションを動かす

Mapのクリックを検知する• サーバ側ができたので、今度はクライアント側を作成します。だいたい以下のような流れになります。• まずMapのクリックを検知できるようにします。• クリックされたら、先ほど作った PlacesServiceを使って施設情報を取得します。

• 取得されたら施設情報をクライアントにポップアップします。

Page 55: BluemixでGWTアプリケーションを動かす

ロギング設定を追加しておく• そろそろプログラムが複雑になってきたので、クライアント側にログを追加しておきます。 src/mymap/Mymap.gwt.xmlを変更します。<?xml version="1.0" encoding="UTF-8"?><!-- When updating your version of GWT, you should also update this DTD reference, so that your app can take advantage of the latest GWT module capabilities.--><!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.6.0//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.6.0/distro-source/core/src/gwt-module.dtd"><module rename-to='mymap'> <!-- Inherit the core Web Toolkit stuff. --> <inherits name='com.google.gwt.user.User'/> <inherits name="com.google.gwt.logging.Logging"/>... <!-- allow Super Dev Mode --> <add-linker name="xsiframe"/> <set-property name='gwt.logging.popupHandler' value='DISABLED'/></module>

追加

これを指定しないと、アプリケーション画面にオーバーレイでロギングが表示されて、わずらわしい

Page 56: BluemixでGWTアプリケーションを動かす

クリックの検知package mymap.client;

import java.util.logging.Level;import java.util.logging.Logger;

...public class Mymap implements EntryPoint { Logger logger = Logger.getLogger("MymapLogger"); private final PlacesServiceAsync placesService = GWT .create(PlacesService.class);

native void initialize() /*-{ var that = this; $wnd.onMapClicked = function(latitude, longitude) { [email protected]::onMapClicked(DD)(latitude, longitude); }; $wnd.initialize(); }-*/;

public void onModuleLoad() { logger.log(Level.INFO, "Application start"); initialize();... }

public void onMapClicked(double latitude, double longitude) { logger.log(Level.INFO, "Map clicked on (" + latitude + ", " + longitude + ")"); }}

ロギングのimport

ロガー生成Placesの RPCは、このようにして生成

する

html側に、 onMapClicked変数を用意して、そこに、下の onMapClicked 関数を代入しておく (詳細は後

述 )。

Page 57: BluemixでGWTアプリケーションを動かす

クリックの検知• html側を変更して、Mapのクリックを検出できるようにする

function showMap(latitude, longitude) { map = new google.maps.Map(document.getElementById('map-canvas'), { center: new google.maps.LatLng(latitude, longitude), zoom: 15 }); google.maps.event.addListener(map, 'click', function(e) { onMapClicked(e.latLng.lat(), e.latLng.lng()); }); }

native void initialize() /*-{ var that = this; $wnd.onMapClicked = function(latitude, longitude) { [email protected]::onMapClicked(DD)(latitude, longitude); }; $wnd.initialize(); }-*/;

addListenerでクリックのリスナを登

onMapClickedは、 GWT側で登録し

たもの

JavaScriptから、 Java側を呼びたい時は、@ 完全修飾クラス名 :: メソッド名 ( 引数のシグニチャ )( 引数 )を指定する。このシグニチャは、バイトコード

での指定方法と同じ。詳細は以下の 4.3.2, 4.3.3を参照 (http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-

4.html)

Page 58: BluemixでGWTアプリケーションを動かす

実行してみる

Chromeの開発者ツールや、 Firefoxの Firebugの

Consoleでアプリケーションのログを確認できる。

地図をクリックすると緯度、経度がログされる

Page 59: BluemixでGWTアプリケーションを動かす

クライアント側のデバッグ• Chromeの開発者ツールではクライアント側を Javaでデバッグ (?)できる

ここでブレークポイントを置いたり、変数をインスペクトしたりで

きる

Page 60: BluemixでGWTアプリケーションを動かす

Map APIのキーも埋め込まないように• Mymap.htmlは、Mymap.jspにして、 key部分は Settingsから取るようにする。Mymap.htmlをMymap.jspにコピーして以下を変更する

<%@ page import="mymap.server.SettingsFactory"%><!doctype html> <script type="text/javascript" language="javascript" src="mymap/mymap.nocache.js"></script> <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=<%= SettingsFactory.getInstance().getSettings().getApiKey() %>"> </script>

• web.xmlのwelcome ページを変更する <welcome-file-list> <welcome-file>Mymap.jsp</welcome-file> </welcome-file-list>

実際は 1行で

Page 61: BluemixでGWTアプリケーションを動かす

実行設定を変更する

Eclipseの Run => Run Configurations...

で、 Argumentsを変更。

Environmentで環境変数apiKey/serverApiKeyにキーを指

Page 62: BluemixでGWTアプリケーションを動かす

実行してみる• サーバ側を変更しているので、一度 Eclipse上で停止してから再実行する。

• 地図クリックで、ブラウザの Consoleにクリック位置がログされれば、ここまでの動作は ok。

この状態のアプリケーションは、mymap01.zipで公開されてい

ます

Page 63: BluemixでGWTアプリケーションを動かす

Places APIの結果を表示する

Page 64: BluemixでGWTアプリケーションを動かす

Places APIを呼び出す• まず施設のモデルクラスを作成します

package mymap.client;

import static java.util.Objects.requireNonNull;

import com.google.gwt.json.client.JSONObject;import java.io.Serializable;

public class Place implements Serializable { private static final long serialVersionUID = 286987229306285007L; private String name; private double latitude; private double longitude;

Place() { name = ""; latitude = 0; longitude = 0; }

このあたりの GWT 特有の注意は、Settingsの時と同じ

Page 65: BluemixでGWTアプリケーションを動かす

Places APIを呼び出す

続き

public Place(String name, double latitude, double longitude) { this.name = requireNonNull(name); this.latitude = latitude; this.longitude = longitude; } public String getName() {return name;} public double getLatitude() {return latitude;} public double getLongitude() {return longitude;}

public static Place parse(JSONObject placeJson) { JSONObject geo = placeJson.get("geometry").isObject(); JSONObject loc = geo.get("location").isObject();

return new Place (placeJson.get("name").isString().stringValue(), loc.get("lat").isNumber().doubleValue(), loc.get("lng").isNumber().doubleValue()); }}

イミュータブルクラス

GWTが提供しているJSONパーサで施設名と

位置を取り出す

Page 66: BluemixでGWTアプリケーションを動かす

Places APIの呼び出し public void onMapClicked(double latitude, double longitude) { logger.log(Level.INFO, "Map clicked on (" + latitude + ", " + longitude + ")");

placesService.places (latitude, longitude, new AsyncCallback<String>() { public void onFailure(Throwable t) { logger.log(Level.SEVERE, "Cannot get places.", t); } public void onSuccess(String json) { List<Place> places = getPlaces(json); logger.log(Level.INFO, "Got places. count = " + places.size()); } }); } List<Place> getPlaces(String placesApiJsonResponse) { JSONObject jo = JSONParser.parseStrict(placesApiJsonResponse).isObject(); JSONArray results = jo.get("results").isArray(); List<Place> places = new ArrayList<>(); for (int i = 0; i < results.size(); ++i) { places.add(Place.parse(results.get(i).isObject())); }

return places; }

正常時と異常時の処理を実装する

JavaScriptからの RPC 呼び出しは非同期に行うので、このようにコールバックを渡す

Places APIが返したJSONの中から

resultsの項目をパース

Page 67: BluemixでGWTアプリケーションを動かす

web.xmlにサービスを登録 <servlet> <servlet-name>placesServlet</servlet-name> <servlet-class>mymap.server.PlacesServiceImpl</servlet-class> </servlet> <servlet-mapping> <servlet-name>placesServlet</servlet-name> <url-pattern>/mymap/places</url-pattern> </servlet-mapping>

既存の greetServletは不要なので削除して上のように書き換える。あるいは余力のある人は、Servlet 3.0のアノテーションにしても良いで

しょう。

Page 68: BluemixでGWTアプリケーションを動かす

実行してみる

このように Places APIから返ってきた件数がログされれば ok

Page 69: BluemixでGWTアプリケーションを動かす

施設をポップアップする

Page 70: BluemixでGWTアプリケーションを動かす

施設のポップアップ• これまでは、こういうケースはダイアログで施設の一覧を表示するのが一般的だったように思いますが、今はポップアップで表示するのがはやりのようなので、今回もポップアップします。ポップアップは、ポップアップ外の領域のクリックで消すことができます。

• GWTには PopupPanelというパーツが用意されているので、ポップアップの生成自体は簡単です。ただ Java 7なので GUIの構築コードが少々長いです ...

Page 71: BluemixでGWTアプリケーションを動かす

ポップアップの構造PopupPanel

DockLayoutPanel

閉じる

DataGrid

施設一覧

Page 72: BluemixでGWTアプリケーションを動かす

ポップアップの生成• さきほどの、 Places API 呼び出しの onSuccess()側にポップアップ生成処理を実装します。public void onSuccess(String json) { List<Place> places = getPlaces(json); final PopupPanel pp = new PopupPanel(true); final Button closeButton = new Button(" 閉じる "); closeButton.addStyleName("closeButton");

DataGrid<Place> ct = new DataGrid<Place>(); TextColumn<Place> nameColumn = new TextColumn<Place>() { @Override public String getValue(Place p) { return p.getName(); } }; ct.addStyleName("placesDataGrid"); ct.addColumn(nameColumn, " 名称 "); ct.setRowData(0, places);続く

DataGridは、各行の表示するモデルクラスを型パラメータで指定す

DataGridの列の定義。モデルクラスの何を表示するかを実装 (この場合は

名称 )

列のヘッダ

DataGridにデータを設定。第一引数は、設定を開始する行位置 (0なの

で先頭 )

Page 73: BluemixでGWTアプリケーションを動かす

ポップアップの生成續き final SingleSelectionModel<Place> selectionModel = new SingleSelectionModel<Place>(); ct.setSelectionModel(selectionModel); selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { public void onSelectionChange(SelectionChangeEvent event) { Place selected = selectionModel.getSelectedObject(); if (selected != null) { addMyPlace(selected); pp.hide(); } } }); DockLayoutPanel dl = new DockLayoutPanel(Unit.EM); dl.addNorth(closeButton, 2); dl.add(ct); pp.setWidget(dl);

closeButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { pp.hide(); } }); pp.setPopupPosition(30, 30); pp.show();

logger.log(Level.INFO, "onMapClicked ended.");}

void addMyPlace(Place newPlace) { // 施設の登録 }

DataGridの行をクリックした時の処理を実装 (addMyPlace()で施設を登録する

(後述 ))

ポップアップは、ポップアップ外のクリックでも消せるが、ボタンも配置して

おく

このあと実装する

Page 74: BluemixでGWTアプリケーションを動かす

ポップアップの cssを調整• ポップアップが正しく表示されるように war/Mymap.cssを調整する

.gwt-PopupPanel { height: 80%; width: 90%;}

.popupContent, .placesDataGrid { height: 100%; width: 100%;}

.popupContent > div { height: 100%; width: 100%;}

Page 75: BluemixでGWTアプリケーションを動かす

実行してみる

クリックした場所周辺の施設が一覧されるようになった。閉じるボタンか、ポップアップ外のクリックで消すことができる

この状態のアプリケーションは、mymap02.zipで公開されてい

ます

Page 76: BluemixでGWTアプリケーションを動かす

選んだ施設をサーバに登録する

Page 77: BluemixでGWTアプリケーションを動かす

施設登録用の RPCを作成• PlacesServiceに APIを追加する。

public interface PlacesService extends RemoteService { String places(double latitude, double longitude); void addMyPlace(Place newPlace);}

すると、 Async側と不整合があるというエラーになるので、電球をクリックして Async

側を生成する

Page 78: BluemixでGWTアプリケーションを動かす

施設登録用の RPCを作成

同様に、 PlacesSerivceImplもエラーになるので電球をクリックして、 Add

unimplemented methodsを選ぶ

Page 79: BluemixでGWTアプリケーションを動かす

施設登録用の RPCを作成• サーバ側は、とりあえずメモリに保持するだけの実装にします ( 将来はデータベースに保管 )

public class PlacesServiceImpl extends RemoteServiceServlet implements PlacesService { static final Settings settings = SettingsFactory.getInstance().getSettings(); static final ArrayList<Place> places = new ArrayList<>();

... @Override public void addMyPlace(Place newPlace) { synchronized (places) { places.add(newPlace); } }}

とりあえずメモリに保持する仮実装

Page 80: BluemixでGWTアプリケーションを動かす

RPCを呼び出すpublic class Mymap implements EntryPoint {... void addMyPlace(Place newPlace) { placesService.addMyPlace (newPlace, new AsyncCallback<Void>() { public void onFailure(Throwable t) { logger.log(Level.SEVERE, "Cannot add my place.", t); }

public void onSuccess(Void v) { updateEastContent(); } }); }

void updateEastContent() { // サーバから登録施設を取り出して表示する。 logger.log(Level.INFO, "Update place list."); }}

addMyPlaceで RPCを実行

ここで登録施設を表示する

Page 81: BluemixでGWTアプリケーションを動かす

実行してみる

施設リストの更新処理の開始まで実行されれば ok

Page 82: BluemixでGWTアプリケーションを動かす

登録施設の表示

Page 83: BluemixでGWTアプリケーションを動かす

登録施設の表示• 登録施設の表示は、以下のようになります。

• 現在地図に表示されている緯度、経度の情報から、その中に含まれる施設のみを選んで取り出します。

• これを画面右側の領域に一覧として表示します。• 現在の GWTの DataGridは、 0 件の場合に更新中のアニメーションが表示されたままになってしまうバグがあるようなので、 0 件の場合は DataGridのかわりに、「結果が無い」というテキストを表示するようにします。

Page 84: BluemixでGWTアプリケーションを動かす

地図領域の取得• 地図領域の取得のために、Mymap.jspの中で、mapに

bounds_changedイベントのリスナを登録します

<script type="text/javascript"> var currentMapBounds = [0, 0, 0, 0];

function initialize() {... google.maps.event.addListener(map, 'click', function(e) { onMapClicked(e.latLng.lat(), e.latLng.lng()); }); google.maps.event.addListener(map, 'bounds_changed', function() { currentMapBounds[0] = map.getBounds().getNorthEast().lat(); currentMapBounds[1] = map.getBounds().getNorthEast().lng(); currentMapBounds[2] = map.getBounds().getSouthWest().lat(); currentMapBounds[3] = map.getBounds().getSouthWest().lng(); }); } </script> これで、Map 領域が変更された時に自動的に、

currentMapBoundsが更新されます

Page 85: BluemixでGWTアプリケーションを動かす

領域から登録施設取得 RPCの作成• これまで同様に、 RPCに APIを追加します。

public interface PlacesService extends RemoteService { String places(double latitude, double longitude); List<Place> myPlaces(double latitude0, double longitude0, double latitude1, double longitude1); void addMyPlace(Place newPlace);}

public class PlacesServiceImpl extends RemoteServiceServlet implements PlacesService {... @Override public List<Place> myPlaces(double latitude0, double longitude0, double latitude1, double longitude1) { synchronized (places) { return new ArrayList<>(places); } }} 今回もとりあえず現在メモリに保持し

ている登録施設を全て返す簡易実装

Page 86: BluemixでGWTアプリケーションを動かす

施設一覧領域を作成する• Mymap.javaに施設一覧領域を作成します。

public class Mymap implements EntryPoint {... DockLayoutPanel eastContentPanel = new DockLayoutPanel(Unit.EM) { Widget primaryChild; @Override public void add(Widget widget) { if (primaryChild != null) { remove(primaryChild); primaryChild = null; } super.add(widget); this.primaryChild = widget; } }; Button refreshEastContentButton = new Button(" 更新 ");... public void onModuleLoad() { logger.log(Level.INFO, "Application start"); refreshEastContentButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { updateEastContent(); } }); eastContentPanel.addNorth(refreshEastContentButton, 2);

initialize();... }}

0 件の時に DataGridとテキスト表示を切り替えたいので DockLayoutPanelの子ウィジェットを置き換えたいが、そういうメソッドが無いので継承して拡張す

登録施設一覧の更新処理をボタンに登録

Page 87: BluemixでGWTアプリケーションを動かす

施設一覧領域を作成する

public void onModuleLoad() {... initialize(); DockLayoutPanel dlp = new DockLayoutPanel(Style.Unit.EM); dlp.addStyleName("rootLayout"); dlp.addNorth(new HTML("My application"), 2);

SplitLayoutPanel slp = new SplitLayoutPanel(); slp.addEast(mainEastContent(), 120); slp.add(new HTML("<div id='map-canvas'></div>"));... }... Widget mainEastContent() { updateEastContent(); return eastContentPanel; }

native double[] getCurrentMapBounds() /*-{ return $wnd.currentMapBounds; }-*/;}

これまで固定文字列を入れていた施設一覧を、ここで作成

更新処理をしてから、施設一覧のパネルを返す

Mymap.jsp側のcurrentMapBoundsを取得

Page 88: BluemixでGWTアプリケーションを動かす

施設一覧領域を作成する• 施設一覧の更新処理です。

void updateEastContent() { logger.log(Level.INFO, "Update place list."); double[] cmb = getCurrentMapBounds(); if (cmb == null) return; placesService.myPlaces (cmb[0], cmb[1], cmb[2], cmb[3], new AsyncCallback<List<Place>>() { public void onFailure(Throwable t) { logger.log(Level.SEVERE, "Cannot get my places.", t); }続く

現在の地図領域で登録施設を検索

Page 89: BluemixでGWTアプリケーションを動かす

施設一覧領域を作成する public void onSuccess(List<Place> places) { if (places.isEmpty()) { eastContentPanel.add(new HTML(" レコード無し ")); } else { DataGrid<Place> myPlaces = new DataGrid<Place>(); TextColumn<Place> nameColumn = new TextColumn<Place>() { @Override public String getValue(Place p) { return p.getName(); } }; myPlaces.addColumn(nameColumn, " 名称 "); myPlaces.setRowData(0, places); final SingleSelectionModel<Place> selectionModel = new SingleSelectionModel<Place>(); myPlaces.setSelectionModel(selectionModel); selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { public void onSelectionChange(SelectionChangeEvent event) { Place selected = selectionModel.getSelectedObject(); // TODO: 施設がクリックされた時の処理 } }); eastContentPanel.add(myPlaces); } } });}

結果 0 件なら「レコード無し」表示

このあたりの DataGridの扱いはポップアップの時と

同様

登録施設がクリックされた時の処理は今後ここに実装

Page 90: BluemixでGWTアプリケーションを動かす

実行してみる

地図クリックでポップアップから施設を選ぶと、ここに登録されていく

この状態のアプリケーションは、mymap03.zipで公開されてい

ます

Page 91: BluemixでGWTアプリケーションを動かす

登録施設がクリックされたら、ピンを立てる

Page 92: BluemixでGWTアプリケーションを動かす

ピンを立てる• 登録施設一覧から施設がクリックされたら、地図上にピンを立ててみましょう。まずMymap.jspにピンを立てるメソッドを用意します。

var map; var currentPin = null;...

function pinPlace(name, latitude, longitude) { if (currentPin !== null) { currentPin.setMap(null); currentPin = null; }

var latlng = new google.maps.LatLng(latitude, longitude); currentPin = new google.maps.Marker({ position: latlng, map: map, title: name }); map.setCenter(latlng); }

function initialize() {

後でピンを消す時にはピンのインスタンスが必要になるので保存してお

既にピンが立っていれば消す

指定位置にピンを立てる

Page 93: BluemixでGWTアプリケーションを動かす

ピンを立てる• 登録施設一覧クリック処理を追加します。

void updateEastContent() {... selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { public void onSelectionChange(SelectionChangeEvent event) { Place selected = selectionModel.getSelectedObject(); if (selected != null) { pinPlace(selected); } } });... }

native void pinPlace(Place place) /*-{ $wnd.pinPlace([email protected]::getName()(), [email protected]::getLatitude()(), [email protected]::getLongitude()()); }-*/;}

ピン立て処理を呼び出す

Mymap.jsp側の処理を呼び出す

Page 94: BluemixでGWTアプリケーションを動かす

実行してみる

登録施設一覧をクリックすると

ピンが立つようになった

Page 95: BluemixでGWTアプリケーションを動かす

現在地にもピンを立てる

Page 96: BluemixでGWTアプリケーションを動かす

現在地にもピンを立てる• 現在地にもピンが無いと分かりにくですね。現在地には別の色のピンを

立てましょう。これはMymap.jsp側の変更だけで実現できます。 var currentLocPin = null;... function pinCurrentLoc() { if (currentLocPin != null) { currentLocPin.setMap(null); currentLocPin = null; }

currentLoc(pinCurrentLocAt); } function pinCurrentLocAt(latitude, longitude) { var latlng = new google.maps.LatLng(latitude, longitude); currentLocPin = new google.maps.Marker({ position: latlng, map: map, title: '現 ', icon: 'http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=現 |00FFFF|000000' }); }

function showMap(latitude, longitude) {... pinCurrentLocAt(latitude, longitude); setInterval(pinCurrentLoc, 5000); }

5 秒に 1回、現在地のピンを更新

少し違うピンを立てます (後述 )

現在地ピンも、後で消せるように保存

Page 97: BluemixでGWTアプリケーションを動かす

違う色のピン• ピンには引数でビットマップを渡せるのですが、ビットマップを手で作るのは面倒です。 Googleの Dynamic Iconというサービスを使うと、引数を渡せば作ってくれます。 https://developers.google.com/chart/image/docs/gallery/dynamic_icons

• 今回は次のように指定していますhttp://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=現 |00FFFF|000000「現」がピン内の文字、その後が背景色 (シアン )、その後が文字色( 黒 )です

Page 98: BluemixでGWTアプリケーションを動かす

実行してみる

現在地も分かるようになりました

この状態のアプリケーションは、mymap04.zipで公開されてい

ます

Page 99: BluemixでGWTアプリケーションを動かす

Bluemixで確認• 最後に Bluemixで確認します。

• 方法は Hello Worldの時と全く同じです。 Eclipseで GWT Compileをしてから、 cf pushで uploadしてください。

• ただし既に述べた通り API keyを環境変数で設定する必要があります。

ダッシュボードで、アイコン部分をクリックします (今回は

ruimomapという名前で cf pushしました )

Page 100: BluemixでGWTアプリケーションを動かす

環境変数の設定

Page 101: BluemixでGWTアプリケーションを動かす

環境変数の設定

保存を押すと、設定されてアプリケーションも再起動されます

これまでと同じ画面が表示されれば成功です。お疲れ様でした !DBアクセスも入れたかったのですが紙面が尽きました。次の資料に入れたいと思

います

Page 102: BluemixでGWTアプリケーションを動かす

サンプル

Page 103: BluemixでGWTアプリケーションを動かす

サンプル• サンプルは以下の場所で取得できます http://www.ruimo.com/

bluemix/samples/gwt/

• mymap00.zip地図を表示する (war/Mymap.html 内の TODO の場所に、自分の API Keyを指定してください )

• mymap01.zip地図のクリックで、ブラウザのコンソールに位置がログされる。自分の API Key を、 Eclipse の実行構成に設定する必要があります。詳細は本文を参照。

• mymap02.zip施設一覧のポップアップを実装。

• mymap03.zip施設を登録できるように。

• mymap04.zipピンを立てるように。