fc2ブログ

【Java】【Google App Engine】GAEでインクリメンタルサーチ【javascript】

2011-12-28 02:07:05 Wed

■GAEでインクリメンタルサーチ


インクリメンタルサーチ(候補検索)を行うには前方一致検索をする必要がある。
SQL的に記述すると param% のような記述になるがこれをGAEの制限されたDatastoreで表現すると次のようになる。
Slim3を使っています。

public List search(Map params){
IncrementalSearchMasterMeta meta = IncrementalSearchMasterMeta.get();
String param = (String)params.get("send_data");
String param2 = param + "\uFFFD";
List list = Datastore.query(meta).filter(meta.text.greaterThanOrEqual(param)).filter(meta.text.lessThan(param2)).limit(10).asList();
return list;
}


こんな感じ。
フィルタクエリで param <= text AND text < param2 と指定しています。
param:あ <= text AND text < param2: あ + \uFFFDという感じの範囲検索で実現できます。
上記であれば「あ」から始まるものをすべて取得できます。

http://code.google.com/p/objectify-appengine/wiki/FrequentlyAskedQuestions
http://blog.virtual-tech.net/2009/10/google-app-engine-key.html


■ちょっと厄介なクライアントコーディング


インクリメンタルサーチをすればGoogleみたいに入力ボックスの下にびろっと出して選択できるようにしたくなりました。
で適当に作りました。
スクロールバーのこととか、下キー上キーのキー制御のロジックとか入っていませんがメモ。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset=utf-8 />
<title>Test</title>
<script src="/common/js/jquery.js"></script>
<script type="text/javascript">
$(document).ready(function(){
// tableタグを生成して非表示で追加
var table = $('<table id="incremental" onclick="onClick(event, true)" onmouseout="onMouseOut(event,true)" onmouseover="highlight(event,true)" style="position: absolute;overflow: visible;top: 73px;left: 8px;width: 155px;border: 1px #E3E3E3 solid;border-collapse: collapse;border-spacing: 0;text-alighn: left;display: none;cursor: pointer; background-color: white;"><tr><td>aaa</td></tr></table>');
$('html').append(table);

// 入力ボックスへのイベント設定
$("[name=keyword]").bind("keyup", onCange);
$("[name=keyword]").bind("blur", onBlur);

// 登録ボタンへのイベント設定
$("#regBtn").bind("click", register);
});

// インクリメンタルサーチの候補一覧が選択されたとき選択されている行のテキストを入力テキストに代入して候補一覧を非表示にする
function onClick(e,b){
    var src;
    // Netscape イベント処理用の分岐
    if (navigator.appName == "Netscape"){
        src = e.target;
    }else{
        src = e.srcElement;
    }
    if (src.tagName == "TD") {
        if(b==true) { // mouseOver, and highlight {
         var text = src.textContent;
         $("[name=keyword]").val(text);
        }else{        // mouseOut, restore original color
        }
    }
    $("#incremental").hide();
}

// マウスが候補一覧上にあるとき特定行の背景色を変更する
function highlight(e,b) {
    var src;
    // Netscape イベント処理用の分岐
    if (navigator.appName == "Netscape"){
        src = e.target;
    }else{
        src = e.srcElement;
    }
    if (src.tagName == "TD") {
        if(b==true) { // mouseOver, and highlight {
            src.originColor=src.style.backgroundColor;
            src.style.backgroundColor="gray";
        }else{        // mouseOut, restore original color
            src.style.backgroundColor=src.originColor;
        }
    }
}

// マウスが候補検索一覧上にあって背景色が変更された特定行からマウスを違うところへ移動したとき背景色を元に戻す
function onMouseOut(e,b){
    var src;
    // Netscape イベント処理用の分岐
    if (navigator.appName == "Netscape"){
        src = e.target;
    }else{
        src = e.srcElement;
    }
    if (src.tagName == "TD") {
        if(b==true) { // mouseOver, and highlight {
            src.originColor=src.style.backgroundColor;
            src.style.backgroundColor="white";
        }else{        // mouseOut, restore original color
            src.style.backgroundColor=src.originColor;
        }
    }
}

// 入力テキストに文字が入力されたときajaxで候補一覧を取得しこれを候補一覧に表示する
function onCange(){

var value = $("[name=keyword]").val();
if(value == ""){
return;
}
jQuery.ajax({
url: "incremental/search",
cache: true,
data: {"send_data": value},
success: function(msg){
// テーブル内のHTMLを空にする
$("#incremental").empty();
// 取得したデータをテーブル内に反映する
var list = msg["retlist"];
for(var i = 0; i < list.length; i++){
var tr = $("<tr><td>" + list[i]["text"] + "</td></tr>");
$("#incremental").append(tr);
}
$("#incremental").show();
}
});
}

// 入力テキストからフォーカスが外れたとき200ミリ秒して候補一覧を非表示にする
function onBlur(){
setTimeout(function(){$("#incremental").hide();},200);
}

// 登録ボタンが押下されたとき入力テキストの入力内容をパラメータとしてajax通信し候補キーワードとして登録する
function register(){
var value = $("[name=keyword]").val();
if(value == ""){
$('#logarea').prepend('<p>キーワードを入力して下さい。' + new Date() + '</p>');
return;
}
jQuery.ajax({
url: "register",
cache: true,
data: {"send_data": value},
success: function(msg){
$('#logarea').prepend('<p>登録完了。' + new Date() + '</p>');
}
});
}

</script>
</head>
<body>
<p>search test</p>
<input type="text" name="keyword" style="width: 150px; height: 17px;">
<input type="button" value="register" id="regBtn" >
<div id="logarea"></div>
</body>



検索のajaxでのサーバ側は単純にリクエストされたテキストを取得して前述したクエリで検索すれば候補検索する一覧が取得できる。
登録のajaxはただただリクエストされたテキストをそのまま候補検索されるカインドにエンティティとして放り込むだけ。
本当はエラーハンドリングとかちゃんとやらないといけないけどそれまた。。。


▲Pagetop

【Java】slim3 で velocity 用のファイルを出力する【slim3】

2011-11-15 15:06:27 Tue

slim3のbuild.xmlはslim3jspファイルを自動で生成してくれる。
これをVelocity用のファイルに変えたかった。
Javaで書けばいいんだけどコンパイルとかしたくなかったしひとまずjavascriptで済ませた。

ただ、Velocityテンプレートファイルは残念なテキストファイル(antのpropertyfileタスクだからな…)が出力されるので中身をHTMLの記述に変更しないといけないのと、
コントローラクラスでフォワード先がjspファイルになっているので書き換えないといけない。

だせー。


<target name="gen-controller">
<input message="Input a controller path." addproperty="controllerPath"/>
<gen-controller srcdir="${srcDir}" testdir="${testDir}" wardir="${warDir}" controllerpath="${controllerPath}" useView="true"/>
<!--        <gen-view wardir="${warDir}" controllerPath="${controllerPath}"/>-->
<!--      <echo message="${controllerPath}"/>-->
<script language="javascript"><![CDATA[

// Systemのインポート
importClass(java.lang.System);

// 入力文字列の取得
var text = project.getProperty("controllerPath");

// 入力文字列のサイズ取得
var size = text.length();
System.out.println(size);
System.out.println(text);

var cnt = 0;
var str = "";
var list = new Array();

// "."区切りでの手動split
for(var i = 0; i < size; i++){
      var c = text.substring(i, i + 1);
   System.out.println(new String(c));
      if(c == "."){
   list.push(str);
   System.out.println(str);
        str = "";
      } else {
   str = str + c;
      }
   }

// "."区切りで区切り文字として最後の文字列
System.out.println(str);
project.setNewProperty("filename", str + ".vm");

// 区切られた文字列を"/"でディレクトリパスとして結合
// なぜかlistのサイズが取得できない…
var dirpath = "";
  for(var i = 0; i < size; i++){
var item = list[i];
if(item != undefined && item != ""){
     System.out.println("*********");
     System.out.println(item);
if(i == 0){
   dirpath = dirpath + item;
} else {
   dirpath = dirpath + "/" + item;
}
}
}

// プロパティに書き出し
project.setNewProperty("dirpath", dirpath);
]]></script>
  <echo message="${dirpath}"/>
  <echo message="${dirpath}/${filename}"/>
<mkdir dir="./war/WEB-INF/template/${dirpath}"/>
<propertyfile file="./war/WEB-INF/template/${dirpath}/${filename}">
</propertyfile>
</target>


ひとまずね。

▲Pagetop

【Java】slim3のリクエストパラメータ周り【Slim3】

2011-11-11 15:33:13 Fri

■リクエストのMap変換

import org.slim3.util.RequestMap;
new RequestMap(request)


PJOで動作/テストできるようにHttpServletRequest#getParameterをMapに変換してくれます。
おまじないです。

■リクエストスコープにデータ設定(JSPなど動的VIEWまで持っていく)

requestScope("userid", userid);


HttpServletRequest#setAttributeと同義です。
クライアントを介さないforwardをするときのリクエスト間の値引継ぎに。

▲Pagetop

【Java】Twitter4jを使ってStreamingAPIを利用する

2011-10-19 01:02:46 Wed

Twitter Streaming API


メインメソッドで動作するコード。

// Configureationを生成するためのビルダーオブジェクトを生成
ConfigurationBuilder builder = new ConfigurationBuilder();

// コンシューマーキーとアクセスキーを設定
builder.setOAuthConsumerKey("consumer key");
builder.setOAuthConsumerSecret("consumer secret");
builder.setOAuthAccessToken("access token");
builder.setOAuthAccessTokenSecret("token secret");

// Configurationを生成
Configuration conf = builder.build();

// TwitterStreamを生成
TwitterStreamFactory factory = new TwitterStreamFactory(conf);
TwitterStream twitterStream = factory.getInstance();

// イベントを受け取るリスナーオブジェクトを設定
twitterStream.addListener(new UserStreamAdapter(){
@Override
public void onStatus(Status status) {
synchronized (lock) {
String screenname = status.getUser().getScreenName();
Long userId = status.getUser().getId();
String text = status.getText();
text = text.replace("\r", "");
text = text.replace("\n", "");
text = text.replace("\t", "");
Date createdAt = status.getCreatedAt();
String date = DateUtil.format(createdAt);
System.out.println(screenname + " : " + userId + " : " + text + " : " + date);
}
}
@Override
public void onException(Exception ex)
{
ex.printStackTrace();
}
});

// 取得をスタート
long[] list = {1L};
// FilterQuery query = new FilterQuery(list);
String[] track = {"android"};
FilterQuery query = new FilterQuery();
query.track(track);
// query.follow(list);
twitterStream.filter(query);
// twitterStream.user();
// twitterStream.sample();



上記の「取得をスタート」を変更するとsampleやuser、filterなどのAPIを呼び出すことができます。

特定の人のIDでツイートをfilterする場合。

// 取得をスタート
long[] list = {1L};
FilterQuery query = new FilterQuery(list);
twitterStream.filter(query);


特定のキーワードでツイートをfilterする場合。

// 取得をスタート
String[] track = {"android"};
FilterQuery query = new FilterQuery();
query.track(track);
twitterStream.filter(query);



OAuth認証されているユーザ自身のツイートをStreamingAPIを介してほぼリアルタイム取得する場合。

// 取得をスタート
twitterStream.user();




ランダムなツイートをほぼリアルタイムに取得する場合。

// 取得をスタート
twitterStream.sample();




▲Pagetop

【Java】Twitter4jを使ってWebアプリでOAuth認証する

2011-10-19 00:54:08 Wed

Twitter Webアプリ OAuth認証


セッション使わない場合どうにかできるんでしょうか。
オブジェクトをシリアライズ化してDBやファイルに格納するとかしないとだめなのでしょうか。

なお、事前にTwitterでアプリケーションを作成しておきます。
このとき、クライアントアプリとWebアプリを選択することになりますが、Webアプリを選択します。
クライアントアプリを選択するとtwiccaやsoichaのようなアプリで利用する際の認証となり、PINコードを入力してOAuth認証する、という形になります。

1.こんなかんじでリクエストトークンを受け取る


// Configureationを生成するためのビルダーオブジェクトを生成
ConfigurationBuilder builder = new ConfigurationBuilder();

// コンシューマーキーとアクセスキーを設定
builder.setOAuthConsumerKey("consumer key");
builder.setOAuthConsumerSecret("consumer secret");
// Configurationを生成
Configuration conf = builder.build();
// このファクトリインスタンスは再利用可能でスレッドセーフです
Twitter twitter = new TwitterFactory(conf).getInstance();

// 既にこのアプリケーションでOAuth認証済みかどうかを確認するとき
// builder.setOAuthAccessToken("");
// builder.setOAuthAccessTokenSecret("");
// Configuration conf = builder.build();
// Twitter twitter = new TwitterFactory(conf).getInstance();
// User user = twitter.verifyCredentials(); // 認証済みならUserが取得できる。認証されてない場合例外がスローされる。

request.getSession().setAttribute("twitter", twitter);
RequestToken requestToken = null;
requestToken = twitter.getOAuthRequestToken("http://localhost:8888/twitter/end_oauth");
request.getSession().setAttribute("requestToken", requestToken);
String oauthUrl = requestToken.getAuthorizationURL(); // このURLをHTMLのリンク先として出力する
requestScope("oauthUrl", oauthUrl);
return forward("twitter_start.vm");



2.リクエストトークンを含んだGETリクエストでTwitterへアクセス

3.ログインしてもらう

4.1の戻りのURLでの処理でトークンシークレットを取得する


Twitter twitter = (Twitter)request.getSession().getAttribute("twitter");
RequestToken requestToken = (RequestToken)request.getSession().getAttribute("requestToken");
String verifier = request.getParameter("oauth_verifier");
// アクセストークンを取得
AccessToken accessToken = twitter.getOAuthAccessToken(requestToken, verifier);
request.getSession().removeAttribute("requestToken");
String token = accessToken.getToken();
String tokenSecret = accessToken.getTokenSecret();
requestScope("access_token", token); // 永続化する
requestScope("token_secret", tokenSecret); // 永続化する

return forward("oauth_end.vm");




これら4つがあれば
OAuth認証済みとしてアプリケーション権限に従いAPIにアクセスできるようになる。
(OAuth認証アクセストークンはいらないとか見た気もしますが覚えていないです。
 調べたらすぐ出ると思います。私は必要無いので調べていません。)

▲Pagetop
プロフィール

isann

  • Author:isann
  • ぺえぺえのうんこのあしあと跡地
    ZONOTEへお引越ししました。
    更新はこちらです。
最近の記事
カテゴリー
名言集
全記事(数)表示
全タイトルを表示
ブログ内検索
Loading