SyntaxHighlighter

ラベル Slim3 の投稿を表示しています。 すべての投稿を表示
ラベル Slim3 の投稿を表示しています。 すべての投稿を表示

2011年8月16日火曜日

GAE, Slim3, Scenic3 環境下でAjaxなBlobstore利用

検証コード書いてちゃんと動作したので備忘録的な感じで。
後日丁寧に書く予定。とりあえず箇条書き

前提
・jQuery.upload利用
・コントローラ側は「アップロードURL返す」「Blobstoreアップロードからの戻り直後」「戻り直後からリダイレクトされJSON生成」の3段階用意


1,アップロードボタン押す
2,UploadUrlリクエスト、Jsonで取得しそのままアップロード
3,戻りURLをコントローラを指定しておいて、そこに戻す。そのタイミングではRedirectでなければ戻せない約束事なので一旦別のコントローラにリダイレクト(画像URLを一緒に渡す)
4,別のコントローラは画像URLをJSON化してクライアントに戻す
5,クライアントはAJAXな動きでBLOBSTOREを利用することができる


リクエスト用HTML&JavaScript
<!--
BlobStore直接アップロード検証コード
 -->

<script type = "text/javascript" src="/jquery.upload-1.0.2.js"></script>
<input type="file" name="newprofileimage" id="newprofileimage" />
<input type="button" name="entry" value="登録" onclick='javascript:submitUpload("newprofileimage");' />
<br />
<br />

<script type="text/javascript">
function submitUpload(targetid)
{
 var jsonUrl = "/image/getdirectupdurl";
 var uploadurl = "";

 $.getJSON(jsonUrl, function(urldata) {

  if(urldata)
  {
   uploadurl = urldata.url;

   $('#' + targetid ).upload(
    uploadurl,
	function(data)
	{
	 alert('File uploaded[' + data[0].imageurl + ']');
	 $('#imagearea').html('<img src="' + data[0].imageurl +  '" />');
	},
	'json'
   );
   }
  });
}
</script>

<div id="imagearea">アップロード後の画像が表示される場所</div>

サーバサイドコード
/**
 * BlobStoreへの直接アップロード用URLを取得する.
 * JSON形式にて返す
 * @return
 */
@ActionPath("getdirectupdurl")
public Navigation getDirectUploadUrl()
{
 Navigation nav = null;

 String returnUrl = "/image/dupdcomplete";
 String uploadUrl = service.createUploadUrl(returnUrl); //BlobstoreServiceのcreateUploadUrlを呼び出している

 Map<String,String> jsonData = new HashMap<String,String>();
 jsonData.put("url", uploadUrl);

 try
 {
  nav = this.responseJson(jsonData);
 }
 catch (IOException e)
 {
  e.printStackTrace();
  nav = this.redirect("errorurl");
 }

 return nav;
}


/**
 * Blobstoreに直接アップロードした後、リダイレクトされる場所
 * @param blobUrl
 * @return
 */
@ActionPath("dupdcomplete")
public Navigation directUploadComplete()
{
 Navigation nav = null;

 BlobKey blobKey = service.getBlobKey(request, "newprofileimage");

 nav = this.redirect( "/image/dupdrd?bloburl=" + service.getServingUrl(blobKey));

 return nav;
}

/**
 * Blobstoreに直接アップロードした後リダイレクトされたものをさらにリダイレクトされる場所.
 * @param blobUrl
 * @return
 */
@ActionPath("dupdrd")
public Navigation forDirectUploadRedirect(
  @RequestParam("bloburl") String blobUrl
)
{
 Navigation nav = null;

 if(blobUrl != null && blobUrl.isEmpty() == false )
 {
  List<Map<String,String>> jsonDataList = new ArrayList<Map<String,String>>();

  Map<String,String> jsonData = new HashMap<String,String>();
  jsonData.put("imageurl", blobUrl);
  jsonDataList.add(jsonData);

  try
  {
   nav = this.responseForImageJson(jsonDataList);//この前のエントリの内容
  }
  catch (IOException e)
  {
   e.printStackTrace();
  }
 }

 return nav;
}

2011年8月15日月曜日

GAE/J Slim3でScenic環境下でjQuery uploadを利用&Jsonで戻りデータ取得

結論からいいますと、やっつけデス。。。
コードはかなり美しさにかけて、めっちゃ遅いんちゃう!?くらいの勢いですがとりあえず動きます。

ポイント。

  • jQuery.uploadはjsonでリクエストした際、iframeを利用してシームレスなアップロードを実現するためJsonデータが戻ってくる際のContentTypeが「text/html」でないと<pre>タグがついてしまってうまく処理できない
  • Scenic側でNavigationオブジェクトを渡す際、responseJsonで渡すと強制的に「text/JavaScript」になってしまう

そらそうや、ってお話です。JSONですし。
responseJsonした後にContentTypeを強制的に変えれればなんとかなりそうだったんですが、全く変えれず。。。setContentTypeは意味をなさない。
responseJsonしたあとにキャッシュ的なんと_out〜とかいう変数に1が入ってるのがポイントなんじゃろか?

response( String ) でJson文字列を自分で作って渡してやればいいんじゃないか!?と思いましたが、強制的に「text/Plain」になりました。

結果、responseDirect( byte[], String(=ContentType) ) を使うことにしました。
以下、ざっくり手順を。


  1. まず、StringBufferなどでJSON文字列を作成します。
  2. 次にStringBuffer.toString.toCharArrayでchar[]配列を取り出します
  3. char[]配列の長さでbyte[]配列を作成
  4. ひたすらchar[]配列をループして1つずつbyte[]配列にコピー!!!(これなんかええのんないかなー?)
  5. 最後にresponseDirectに渡してやって完了。

あ、ContentTypeは「"text/html; charset=UTF-8"」みたいな感じで。

とりあえず動きました。ええ、とりあえず。。。

まぁここの部分はファイルアップロードで1個画像を扱うだけなんで、もういいかな?とも思ったり。
まぁ後で時間できたときに見直しですねぇ。今は動けばいいや(涙

2011年7月7日木曜日

【備忘録】Slim3のModelRefでクエリ

ModelRefで問い合わせしたいなーと思い、ネットの海を彷徨ってみつけた情報。
とってもこれ貴重やわぁ。。。助かります。

そんなわけで備忘録。

参考サイト:ふじやん雑記帳さん
※中断から下あたりにある「ModelRefで問い合わせ」

ModelRefで問い合わせ

  • ModelRefAttributeMetaを使えば、ModelRefで問い合わせが可能
  • 例えば、部署(1)対従業員(多)の場合、
// 部署
@Model(schemaVersion = 1)
public class Dept implements Serializable {
}

// 従業員
@Model(schemaVersion = 1)
public class Employee implements Serializable {
    private ModelRef<dept> deptRef = new ModelRef<dept>(Dept.class);
    
    public ModelRef<dept> getDeptRef() {
        return deptRef;
    }
}
とある場合に、「ある部署に所属する従業員全員」という問い合わせは下記のコードで可能。
Key deptKey = [ある部署のKey];

EmployeeMeta employeeMeta = new EmployeeMeta();
ModelRefAttributeMeta<employee, ModelRef<Dept>, Dept> refMeta = employeeMeta.deptRef;
List<employee> list = Datastore.query(Employee.class).filter(refMeta.equal(deptKey));
  • これを使えば、親子関連を持たせたい場合に、entityGroupを使わなくても良い。

2011年6月15日水曜日

備忘録:Slim3でのデータストア暗号化IFについて

備忘録です。

Slim3のモデル暗号化についての内容です。

※ハマっちゃいました。filterの書き方大事。
    ☓ MyModel userData = Datastore.query(t)
          .filter("myname", FilterOperator.EQUAL, "hoge").asSingle();
    ◯ MyModel userData = Datastore.query(t)
          .filter( t.myname.equal("hoge")).asSingle();
なんでオレ、上の書き方してたんやろ・・・


以下、Slim3公式の説明より引用

元となったディスカッションはこちら
http://groups.google.com/group/slim3-user-japan/browse_thread/thread/4999dfd7553c5f38?pli=1

Cipher Text

https://sites.google.com/site/slim3appengine/slim3-datastore/defining-data-classes/cipher-text

個人情報や機密情報を安全に扱うために暗号化をする必要がある場合があります。Slim3は暗号化機能をサポートしています。暗号化アルゴリズムと対象のフィールド型は次の通りです:

  • アルゴリズム: AES (Advanced Encryption Standard)
  • 暗号利用モード(Block cipher mode): CBC (Cipher Block Chaining)
  • 暗号鍵: 128bit (192 bit と 256bit はサポートしていません)
  • フィールド型: java.lang.String, com.google.appengine.api.datastore.Text

Slim3でBigtableのデータを暗号化するのはとても簡単です:

  1. 対象のフィールドに @Attribute(cipher = true) アノテーションを使用します。
  2. 暗号鍵をSlim3 Datastoreに設定します。
  3. Slim3 Datastoreを使用した操作をします。

モデル定義の例です:

@Attribute(cipher=true) private String myString; @Attribute(cipher=true, lob=true) private String myLobString; @Attribute(cipher=true) private Text myText;

暗号鍵を設定する例です:

Datastore.setLimitedCipherKey("hogehogehogehoge"); Datastore.setGlobalCipherKey("hogehogehogehoge");

setLimitedCipherKeyは、スレッドローカルの暗号鍵を設定します。その鍵は現在のスレッド内の暗号化・復号化で使用されます。

FrontControllerは各コントローラを呼び出した後に暗号鍵をクリアします。このメソッド(setLimitedCipherKey)はコントローラー毎に異なる暗号鍵を使用する場合を想定しています。

setGlobalCipherKeyは暗号鍵をスタティック領域に設定します。その鍵は全てのスレッドで使用されます。FrontControllerはsetGlobalCipherKeyメソッドで設定された暗号鍵はクリアしません。このメソッドはシステム内で固定の暗号鍵を使用する場合を想定しています。

LimitedCipherKeyの方がGlobalCipherKeyよりも優先されます。さらに両方の値が未設定の場合、例外がスローされます。128bit(16文字)の暗号鍵を設定してください。

GlobalCipherKeyの値は appengine-web.xml で指定できます:

<system-properties>  <property name="slim3.cipherGlobalKey" value="hogehogehogehoge"/> </system-properties>

暗号化と復号化は自動的に行われます。

暗号化フィールドに対して、インメモリのフィルタとソートは全てサポートされています。

暗号化フィールドに対するデータストアクエリのフィルタには制限があります。以下のフィルタがサポートされています:

  • EqualCriterion
  • IsNotNullCriterion
  • NotEqualCriterion

データストアクエリのソートはサポートされていません。

2011年6月13日月曜日

備忘録:Scenic3でのJSON利用

備忘録です。

Scenic3では「ScenicPage.responseJson」を呼び出すことで、データをJSONとして返すことができます。

ただ、環境がそのままではエラーになってしまうので、「JSONIC」を入れてやります

  1. サイトからZipファイルをダウンロードします
  2. 解凍してあらわれた「jsonic-*.*.*.jar」を「war/lib/」「war/WEB-INF/lib」に配置します
  3. ビルドパスに「jsonic-*.*.*.jar」を追加します
※7/20修正

以上で「ScenicPage.responseJson」を使う環境が整います。
※ほんとSlim3+Scenic3はラクチン

今回動作確認した環境
  • Slim3:1.0.11
  • Scenic3:0.4.2
  • JSONIC:1.2.5

2011年6月10日金曜日

Slim3のモデルでメンバ名の接頭辞に「_」をつけると

Eclipseでの接頭辞に「_(アンダーバー)」がついた際のsetter・getterでメンバ名に接頭辞「_」がついた際のsetter・getter生成について記述しました。

しかし、Slim3のモデルでこれをやると怒られちゃいます。
[SLIM3GEN1011] The getter method[get_accountName] is not found.
こんな感じ。存在しているgetterは「getAccountName」なので、見つからないといわれるわけです。

素直に「accountName」と接頭辞「_」を外して定義するのが正解ですね。
※まぁデータベースの項目名に「_」のような接頭辞につけることなんてそうそうありませんし。

Slim3でのURLマッピング〜その2

Twitterで @higayasuo さん @bluerabbit777jp さんとお話して、確認してみました。
KeyとPOSTデータについて「asString」「getAttribute」「getParameter」でそれぞれデータ取得

結果画面

やっぱり、AttributeにPOSTデータのみが入って、ParameterにKeyだけが入ってる。
setAttributeでコピーしてたんで、Attributeに全てのデータが入って・・・・あ!

パスつくり直す前・・・POSTデータのみ。ここでsetAttribute、AttributeにPOSTデータが全部コピーされる
パス直してForword・・・このときはsetAttributeしない?先ほどのPOSTデータがAttributeに残ってて、Parameterは今回のGET引数となるKeyが入っている

こう考えると納得だなぁ

確認に使ったソースはこんな感じです

check.jsp

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Checktitle>
head>
<body>
<p>Hello Check !!!p>
<div>
<form method="POST" enctype="multipart/form-data" action="/check/tweet">
<div><h2 class="tweet_do"><span>POSTテストspan>h2>div>
<div><textarea name="maindata" rows="2" cols="10" wrap="on" style="width:635px;height:60px;">textarea>div>
<div>
<input type="hidden" name="url" value="/check" />
<input type="submit" name="createArticle" value="POSTする" class="submit" />
div>
form>
div>
<hr />
key_getattlist_forward
<ol>
<c:forEach var="att" items="${key_getattlist_forward}">
<li>${att}li>
c:forEach>
ol>
<hr />
key_getattlist_include
<ol>
<c:forEach var="inc" items="${key_getattlist_include}">
<li>${inc}li>
c:forEach>
ol>
<hr />
key_getparamlist
<ol>
<c:forEach var="prm" items="${key_getparamlist}">
<li>${prm}li>
c:forEach>
ol>
<hr />
<div>as_key : ${as_key}div>
<div>as_maindata : ${as_maindata}div>
<div>as_url : ${as_url}div>
<hr />
<div>att_key : ${att_key}div>
<div>att_maindata : ${att_maindata}div>
<div>att_url : ${att_url}div>
<hr />
<div>param_key : ${param_key}div>
<div>param_maindata : ${param_maindata}div>
<div>param_url : ${param_url}div>
<hr />
body>
html>



slim3rootPackage.CheckController.java
package jp.tyato.verify.controller;

import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;

public class CheckController extends Controller {

    final String KEY_NAME = "key";

    @Override
    public Navigation run() throws Exception {

        try {
            requestScope("key_getattlist_forward",
                    request.getAttribute("javax.servlet.forward.query_string"));
            requestScope("key_getattlist_include",
                    request.getAttribute("javax.servlet.include.query_string"));
            requestScope("key_getparamlist",
                    request.getParameterValues(KEY_NAME));

            requestScope("as_key", asString(KEY_NAME));
            requestScope("as_maindata", asString("maindata"));
            requestScope("as_url", asString("url"));

            requestScope("att_key", request.getAttribute(KEY_NAME));
            requestScope("att_maindata", request.getAttribute("maindata"));
            requestScope("att_url", request.getAttribute("url"));

            requestScope("param_key", request.getParameter(KEY_NAME));
            requestScope("param_maindeta", request.getParameter("maindata"));
            requestScope("param_url", request.getParameter("url"));
        } catch (Throwable e) {
            e.printStackTrace();
        }

        return forward("Check.jsp");
    }

}

2011年6月8日水曜日

FrontControllerを使いたいときはデフォルトコンストラクタを

Slim3において、ふつーにマッピングしてる間は何も考えずにControllerクラスを生成すればOK。

ただし、URLマッピングしたりとかでControllerクラスに「引数つきコンストラクタ」なんか定義しちゃうと、そのままでは通常の名前からのController呼び出し( /aaa → slim3route.AaaController )が失敗しちゃいます。

他のコンストラクタを書いちゃったときは、暗黙の際に自動で定義されているデフォルトコンストラクタ(引数なしコンストラクタ)を定義してやりましょう。

つまり、
public class AaaController extends Controller
{
}
か、
public class AaaController extends Controller
{
	//これが必ず存在するようにする!
	public AaaController()
	{
	}

	public AaaController( type arg)
	{
	}
}
でないとダメと

2011年6月7日火曜日

Slim3でのURLマッピング

再度確認しました
 結論としては、@higayasuo さんがお話されていた通り「当たり前の挙動」を示しているだけなのかなーと。
 サーブレットに詳しくない自分としてはちょっとツラい感じです

※6/9修正しました。ソース部分をasXxxを使った形に書き換え&こちらを参考にrequestなどの取得について修正

確認しました。
POSTが噛んだときの、マッピングの判断条件の考え方が間違っていました。

RequestMapのgetは、内部でHttpRequestServletのgetAttribute()を呼び出します。
で。
  • Formのenctypeが「multipart/form-data」の場合
    POSTがない場合はこれでGETの引数としてのマッピング先(?key={key})が取得できるのですが、POSTデータがある場合はこの値がPOSTの内容になるようです。
    GETの値はどこかというと、「request.getParameter("key")」で取得できる模様

  • Formのenctypeが「text/plain」の場合
    RequestMapのgetは、GETの値、つまりURLマッピングの「?key={key}」が入っています。
    POSTの値は・・・getParameterでも取れませんでした。

とりあえずURLマッピングを絡めてPOSTを扱う場合は、「multipart/form-data」にしておくことをオススメします。
以下修正しました。
ひと通り動く版。POSTは「multipart/form-data」で。

参照リンク
http://sites.google.com/site/slim3documentja/documents/slim3-controller/url-mapping

Slim3は通常、
リクエストURLパス コントローラ
/ slim3.rootpackage.controller.IndexController
/xxx slim3.rootpackage.controller.XxxController
/xxx/ slim3.rootpackage.controller.xxx.IndexController
/xxx/yyy slim3.rootpackage.controller.xxx.YyyController
/xxx/yyy/zzz slim3.rootpackage.controller.xxx.yyy.ZzzController
※slim3.rootpackage は web.xml で定義されています。
というようにマッピングルールに基づいて、実行するコントローラが決まります。

リンク先に書いてあるのをちゃんと読んでいなかったのですが、このURLマッピングはある程度制御することができます

コントローラを取得する前にリクエストURLパスの書き換えをすることもできます。
URLリライティングを使うには slim3.rootpackage.controller.AppRouter クラスを先に定義する必要があります。

とのこと。実際にやってみましょう

slim3.rootpackageが「jp.tyato.test」だとすると、「jp.tyato.test.controller.AppRouter」クラスを生成します
  • 「jp.tyato.test.controller」のパッケージを用意する
  • 「jp.tyato.test.controller」配下に「AppRouter」クラスを生成。内容は次のような感じ

6/8修正
package jp.tyato.test.controller;

import org.slim3.controller.router.RouterImpl;

public class AppRouter extends RouterImpl
{
    Public AppRouter()
    {
        addRouting("/xxx/{key}", "/xxx?key={key}");
    }
}

次にIndexControllerクラスを生成します
  • 「build.xml」を開き、アウトラインから「gen-controller-without-view」を選択、実行から「1 Ant ビルド」を実行する
  • 名前の入力を求められるので、「/xxx/Index」と入力
  • 「jp.tyato.test.controller.xxx」配下に「IndexController.java」が作成される
  • メソッド「run」の中を書き換える

final String KEY_NAME = "key";

@Override
public Navigation run() throws Exception
{
    Navigation result = null;

    try
    {
        String keyword = asString(KEY_NAME);

        if(keyword == null)
        {
            //getParameterで再度試行
            keyword = request.getParameter(KEY_NAME);
        }

        if( keyword != null )
        {
            keyword = keyword.toLowerCase();
        }

        //Factoryパターン用コントローラ親クラスの定義
        Controller con = this.GetFromFactory(keyword);

        result = con.runBare();
    }
    catch (Throwable e)
    {
        e.printStackTrace();
    }

    return result;
}

public Controller GetFromFactory(String keyword)
{
    Controller con = null;

    try
    {
        if (keyword == null || keyword.isEmpty() || keyword.startsWith("index") )
        {
            //keywordがnull、空文字、indexで始まる文字のいずれかである場合、Index表示処理
            con = new ForDefaultController();
        }
        else if (keyword.equals("yyy"))
        {
            //keyが「yyy」である場合、yyy用の処理へ
            con = new YyyController();
        }
        else if (keyword.equals("zzz"))
        {
            //keyが「zzz」である場合、zzz用の処理へ
            con = new ZzzController();
        }
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }

    return con;
}
後は実際に処理を行うControllerクラスを実装します。
この場合は「ForDefaultController」「YyyController」「ZzzController」について
  • 「build.xml」を開き、アウトラインから「gen-controller-without-view」を選択、実行から「1 Ant ビルド」を実行する
  • 名前の入力を求められるので、「/xxx/???」と入力
  • 「jp.tyato.test.controller.xxx」配下に「???Controller.java」が作成される
???の部分をそれぞれ「ForDefault」「Yyy」「Zzz」に置き換えて読んでください。

しかしこのままですと、各Controllerを呼び出してもそれぞれの変数である
「protected ServletContext servletContext」
「protected HttpServletRequest request」
「protected HttpServletResponse response」
がnullのままです。

ですので、利用する前に各Locaterのgetを使って、値を取得してやります。

YyyControllerであれば、こんな感じです
package jp.tyato.test.controller.xxx;

import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
import org.slim3.util.RequestLocator;
import org.slim3.util.ResponseLocator;
import org.slim3.util.ServletContextLocator;

public class YyyController extends Controller
{
    @Override
    public Navigation run() throws Exception
    {
        this.CheckParentMember();

        //以降、実処理
    }

    protected void CheckParentMember()
    {
        if( request == null )
        {
            request = RequestLocator.get();
        }

        if( response == null )
        {
            response = ResponseLocator.get();
        }

        if( servletContext == null )
        {
            servletContext = ServletContextLocator.get();
        }
    }
}
「ForDefaultController」「ZzzController」についても同様に定義してやります。
※まぁ実際に使うときに〜Locator.get()を呼び出してやればいいんですが。

こうすることで、
  • Indexに一旦集約
  • その都度の値により狙った処理を行うように制御
といったことが可能になります。

2011年5月25日水曜日

備忘録(GAE1.5.0でのSlim3について)

GAEを1.4系から1.5.0にアップデートした際、Slim3を1.5.0対応版の「1.0.11」に更新する必要がある

Slim3更新履歴
http://sites.google.com/site/slim3documentja/release-notes

以下、引用

Version 1.0.11 - May 14, 2011

org.slim3.datastore.ModelQuery.asQueryResultList(ModelQuery.java:295)でNullPointerExceptionが発生する問題を修正しました:
http://code.google.com/p/slim3/issues/detail?id=81
App Engine SDK 1.5.0のローカルテストでNullPointerExceptionが発生する問題を修正しました:
http://code.google.com/p/slim3/issues/detail?id=87
AppEngine SDK 1.5.0でテストしました