Lift + Maven で始める Webサイト開発
先日のScalaハッカソンでは、あまりコーディングはできませんでしたが、LiftWebフレームワークを使っての開発を @NKJason さんにいろいろ教えて頂いたので、忘れないうちに書いておきます。LiftとATNDのAPIを使ってATNDのイベント検索機能を実装しました。自分でも実装してみながら、流れを書きます。ビルドツールは何でもいいのだと思いますが、今回はMavenです。
環境は、
- MacOSX10.6
- Maven2.2.1
- Scala2.8.0
プロジェクトの準備
MavenのリポジトリからデフォルトのLiftプロジェクトをダウンロードして配置します。
#!/bin/bash mvn archetype:generate -U \ -DarchetypeGroupId=net.liftweb \ -DarchetypeArtifactId=lift-archetype-basic_2.8.0.RC7 \ -DarchetypeVersion=2.1-SNAPSHOT \ -DarchetypeRepository=http://scala-tools.org/repo-snapshots \ -DremoteRepositories=http://scala-tools.org/repo-snapshots \ -DgroupId=$2 -DartifactId=$1
何度も使いそうなので、シェルにしました。
この辺の設定内容は、もう少し詳しく確認してから追記します。
ひとまず、-DgroupIdはパッケージ名を、-DartifactIdはプロジェクト名を
指定するところなので、オプションで指定するようにしました。
lift_setup Hello net.knserve.lift.hello
上記のコマンドを実行するとカレントディレクトリにデフォルトのプロジェクトが作られます。
ソースを変更する前にとりあえず動かしてみましょう。
作成されたプロジェクトのルートディレクトリ(pom.xmlがある場所)に移動して、
mvn package mvn jetty:run
と順番に打つとビルドされ、jettyが起動した状態になります。
localhost:8080にアクセスすれば、何も変更されていないLiftによる画面が見えるはずです。
開発作業
Liftでは主にテンプレートとスニペットというクラスを使った開発を行うようです。
- スニペットのクラスの中でビューとなるhtmlタグをbindするように書く。
- bindしたhtmlタグをテンプレートから呼び出す。
というような開発手順になります。その辺りの情報は公式wikiで言うとこの辺りでしょうか。
webapp配下です
─ webapp ├── WEB-INF │ └── web.xml ├── images │ └── ajax-loader.gif ├── index.html ├── static │ └── index.html └── templates-hidden ├── default.html └── wizard-all.html
Scalaのソースコードが置かれているパッケージ配下です。
─ net └── knserve └── lift └── hello ├── comet ├── lib │ └── DepencyFactory.scala ├── model │ └── User.scala ├── snippet │ └── HelloWorld.scala └── view
HelloWrold.scalaをSample.scalaという名前にコピーして編集していきます。
編集前のソースです。
package net.knserve.lift.hello { package snippet { import _root_.scala.xml.{NodeSeq, Text} import _root_.net.liftweb.util._ import _root_.net.liftweb.common._ import _root_.java.util.Date import net.knserve.lift.hello.lib._ import Helpers._ class HelloWorld { lazy val date: Box[Date] = DependencyFactory.inject[Date] // inject the date def howdy(in: NodeSeq): NodeSeq = Helpers.bind("b", in, "time" -> date.map(d => Text(d.toString))) /* lazy val date: Date = DependencyFactory.time.vend // create the date via factory def howdy(in: NodeSeq): NodeSeq = Helpers.bind("b", in, "time" -> date.toString) */ } } }
どういうページにするか考え中...。
結局あんま思いつかなかったのでソースを解説します。
基本的に@NKJason さんのコードのままですが、
メソッド名などは変えて、コメントを加えました。
package com.snssuite.atnd { package snippet { import _root_.scala.xml.{NodeSeq, Text} import _root_.net.liftweb.util._ import _root_.net.liftweb.common._ import _root_.java.util.Date import com.snssuite.atnd.lib._ import Helpers._ //追加でインポート import scala.xml._ import net.liftweb.util.HttpHelpers._ import net.liftweb.http._ class AtndSearch { def searchBox( in: NodeSeq ): NodeSeq = { //searchパラメータを取得して格納 //検索窓のデフォルト値として利用 var keyword = S.param("search") openOr "" //検索パラメータを付けたURLへリダイレクト def search() = { S.redirectTo("/static/index?search=" + urlEncode( keyword )) } //コンポーネントをバインド Helpers.bind("searchbox", in , "input" -> SHtml.text( keyword, keyword = _ ) , "submit" -> SHtml.submit("検索", search)) } def resultList( in: NodeSeq ): NodeSeq = { //searchパラメータを取得 val keyword = S.param("search") openOr "" //searchパラメータからURLを作成して、xmlで結果を得る val xml = XML.load("http://api.atnd.org/events/?keyword_or=" + urlEncode( keyword ) + "&format=xml") //events配下のeventの一覧をリストで取得 val xmlList = ( xml \\ "events" \\ "event" ) xmlList.flatMap( xm => Helpers.bind("resultlist", in , "title" -> (xm \\ "title").map(_.text).mkString , "event_id" -> SHtml.link("http://atnd.org/events/" + (xm \\ "event_id").map(_.text).mkString , () =>"" , scala.xml.Text((xm \\ "event_id").map(_.text).mkString)) )) } } } }
<lift:surround with="default" at="content"> <!-- searchBoxメソッドのバインド --> <lift:AtndSearch.searchBox form="POST"> <searchbox:input /> <searchbox:submit /> </lift:AtndSearch.searchBox> <table border="1"> <tr> <td>title</td> <td>eventLink</td> </tr> <!-- resultListメソッドのバインド --> <lift:AtndSearch.resultList> <tr> <td><resultlist:title /></td> <td><resultlist:event_id /></td> </tr> </lift:AtndSearch.resultList> </table> </lift:surround>
Lift + Maven で始める Webサイト開発
先日のScalaハッカソンでは、あまりコーディングはできませんでしたが、LiftWebフレームワークを使っての開発を @NKJason さんにいろいろ教えて頂いたので、忘れないうちに書いておきます。LiftとATNDのAPIを使ってATNDのイベント検索機能を実装しました。自分でも実装してみながら、流れを書きます。ビルドツールは何でもいいのだと思いますが、今回はMavenです。
環境は、
- MacOSX10.6
- Maven2.2.1
- Scala2.8.0
プロジェクトの準備
MavenのリポジトリからデフォルトのLiftプロジェクトをダウンロードして配置します。
#!/bin/bash mvn archetype:generate -U \ -DarchetypeGroupId=net.liftweb \ -DarchetypeArtifactId=lift-archetype-basic_2.8.0.RC7 \ -DarchetypeVersion=2.1-SNAPSHOT \ -DarchetypeRepository=http://scala-tools.org/repo-snapshots \ -DremoteRepositories=http://scala-tools.org/repo-snapshots \ -DgroupId=$2 -DartifactId=$1
何度も使いそうなので、シェルにしました。
この辺の設定内容は、もう少し詳しく確認してから追記します。
ひとまず、-DgroupIdはパッケージ名を、-DartifactIdはプロジェクト名を
指定するところなので、オプションで指定するようにしました。
lift_setup Hello net.knserve.lift.hello
上記のコマンドを実行するとカレントディレクトリにデフォルトのプロジェクトが作られます。
ソースを変更する前にとりあえず動かしてみましょう。
作成されたプロジェクトのルートディレクトリ(pom.xmlがある場所)に移動して、
mvn package mvn jetty:run
と順番に打つとビルドされ、jettyが起動した状態になります。
localhost:8080にアクセスすれば、何も変更されていないLiftによる画面が見えるはずです。
開発作業
Liftでは主にテンプレートとスニペットというクラスを使った開発を行うようです。
- スニペットのクラスの中でビューとなるhtmlタグをbindするように書く。
- bindしたhtmlタグをテンプレートから呼び出す。
というような開発手順になります。その辺りの情報は公式wikiで言うとこの辺りでしょうか。
webapp配下です
─ webapp ├── WEB-INF │ └── web.xml ├── images │ └── ajax-loader.gif ├── index.html ├── static │ └── index.html └── templates-hidden ├── default.html └── wizard-all.html
Scalaのソースコードが置かれているパッケージ配下です。
─ net └── knserve └── lift └── hello ├── comet ├── lib │ └── DepencyFactory.scala ├── model │ └── User.scala ├── snippet │ └── HelloWorld.scala └── view
HelloWrold.scalaをSample.scalaという名前にコピーして編集していきます。
編集前のソースです。
package net.knserve.lift.hello { package snippet { import _root_.scala.xml.{NodeSeq, Text} import _root_.net.liftweb.util._ import _root_.net.liftweb.common._ import _root_.java.util.Date import net.knserve.lift.hello.lib._ import Helpers._ class HelloWorld { lazy val date: Box[Date] = DependencyFactory.inject[Date] // inject the date def howdy(in: NodeSeq): NodeSeq = Helpers.bind("b", in, "time" -> date.map(d => Text(d.toString))) /* lazy val date: Date = DependencyFactory.time.vend // create the date via factory def howdy(in: NodeSeq): NodeSeq = Helpers.bind("b", in, "time" -> date.toString) */ } } }
どういうページにするか考え中...。
GoogleAppEngine+Slim3のファイルアップロードをScalaで書き直す
【変更前】元ネタは、 http://slim3demo.appspot.com/upload/;jsessionid=dBGn20s5q7i28ZwKBGOrFw
package slim3.demo.service; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.slim3.controller.upload.FileItem; import org.slim3.datastore.Datastore; import org.slim3.util.ByteUtil; import slim3.demo.meta.UploadedDataFragmentMeta; import slim3.demo.meta.UploadedDataMeta; import slim3.demo.model.UploadedData; import slim3.demo.model.UploadedDataFragment; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.Transaction; public class UploadService { //1つのデータの最大値は1MなのでFragmentのサイズを900000に設定 private static final int FRAGMENT_SIZE = 900000; //DataとDataFragmentそれぞれのエンティティへのマッピングクラスを取得 private UploadedDataMeta d = UploadedDataMeta.get(); private UploadedDataFragmentMeta f = UploadedDataFragmentMeta.get(); //アップロードしたDataの一覧を取得 public List<UploadedData> getDataList() { return Datastore.query(d).asList(); } //DataをDataFragmentに分割しながらアップロード public UploadedData upload(FileItem formFile) { //ファイルが空だったら、nullを返す if (formFile == null) { return null; } List<Object> models = new ArrayList<Object>(); UploadedData data = new UploadedData(); //datastoreに保存するデータの models.add(data); //各種Dataのパラメータ指定 data.setKey(Datastore.allocateId(d)); data.setFileName(formFile.getShortFileName()); data.setLength(formFile.getData().length); //formFileをバイト化 byte[] bytes = formFile.getData(); //bytesをFragmentのサイズに分割 byte[][] bytesArray = ByteUtil.split(bytes, FRAGMENT_SIZE); //byteArrayの長さ分のKeyを生成 Iterator<Key> keys = Datastore .allocateIds(data.getKey(), f, bytesArray.length) .iterator(); for (int i = 0; i < bytesArray.length; i++) { //FragmentData用のbyte[] byte[] fragmentData = bytesArray[i]; //インスタンス生成しmodelsに格納 UploadedDataFragment fragment = new UploadedDataFragment(); models.add(fragment); //Fragmentに各種プロパティを設定 fragment.setKey(keys.next()); fragment.setBytes(fragmentData); fragment.setIndex(i); fragment.getUploadDataRef().setModel(data); } //トランザクション開始 Transaction tx = Datastore.beginTransaction(); //Data,Fragmentを含むモデルオブジェクトの一覧をdatastoreに追加していく for (Object model : models) { Datastore.put(tx, model); } //トランザクション終了 tx.commit(); //dataのオブジェクトを返す return data; } //Dataの情報を取得 public UploadedData getData(Key key, Long version) { return Datastore.get(d, key, version); } //取得したDataの情報からFragmentをかき集めて修復 public byte[] getBytes(UploadedData uploadedData) { //Dataが空だった場合にNullPointExceptionを投げる if (uploadedData == null) { throw new NullPointerException( "The uploadedData parameter must not be null."); } //DataからFragmentの一覧を取得 List<UploadedDataFragment> fragmentList = uploadedData.getFragmentListRef().getModelList(); //各々のFragmentからbyteを取得してbyte[][]に格納 byte[][] bytesArray = new byte[fragmentList.size()][0]; for (int i = 0; i < fragmentList.size(); i++) { bytesArray[i] = fragmentList.get(i).getBytes(); } //bite[][]をjoinしてリターン return ByteUtil.join(bytesArray); } //削除するDataのKeyを指定してDataとFragmentの両方を削除 public void delete(Key key) { Transaction tx = Datastore.beginTransaction(); //削除するエンティティのKey一覧 List<Key> keys = new ArrayList<Key>(); //削除するエンティティ一覧にDataとFragmentのKeyを追加 keys.add(key); keys.addAll(Datastore.query(f, key).asKeyList()); //トランザクション内で削除とトランザクション完了 Datastore.delete(tx, keys); Datastore.commit(tx); } }
【変更後】
package slim3.demo.service; import java.util.ArrayList import java.util.Iterator import java.util.List import org.slim3.controller.upload.FileItem import org.slim3.datastore.Datastore import org.slim3.util.ByteUtil import slim3.demo.meta.UploadedDataFragmentMeta import slim3.demo.meta.UploadedDataMeta import slim3.demo.model.UploadedData import slim3.demo.model.UploadedDataFragment import com.google.appengine.api.datastore.Key import com.google.appengine.api.datastore.Transaction import scala.collection.JavaConversions._ class UploadService( //DataとDataFragmentそれぞれのエンティティへのマッピングクラスを取得 var d: UploadedDataMeta = UploadedDataMeta.get(), var f: UploadedDataFragmentMeta = UploadedDataFragmentMeta.get() ){ object UploadService { //1つのデータの最大値は1MなのでFragmentのサイズを900000に設定 val FRAGMENT_SIZE: Int = 900000; } //アップロードしたDataの一覧を取得 def getDataList(): java.util.List[UploadedData] = { Datastore.query(d).asList(); } //DataをDataFragmentに分割しながらアップロード def upload( formFile: FileItem ): UploadedData = formFile match { //★caseにしてみた case null => null case _ => { val models: List[Object] = Nil val data: UploadedData = new UploadedData() //datastoreに保存するデータの models.add(data); //☆この辺のオブジェクトのパラメータを指定してくとこをシンプルにする方法ない? data.setKey(Datastore.allocateId(d)) data.setFileName(formFile.getShortFileName()) data.setLength(formFile.getData().length) //Fragmentのサイズに分割 val bytesArray: Array[Array[Byte]] = ByteUtil.split(formFile.getData, UploadService.FRAGMENT_SIZE); //Key一覧を生成 val keys: Iterator[Key] = Datastore.allocateIds(data.getKey(), f, bytesArray.length).iterator(); for { i <- 0 to bytesArray.length - 1 } { //FragmentData用のbyte[] var fragmentData = bytesArray(i) //インスタンス生成しmodelsに格納 var fragment: UploadedDataFragment = new UploadedDataFragment() models.add(fragment) //★この辺のオブジェクトのパラメータを指定してくとこをシンプルにする方法ない? fragment.setKey(keys.next()) fragment.setBytes(fragmentData) fragment.setIndex(i) fragment.getUploadDataRef().setModel(data) } //トランザクション開始 val tx: Transaction = Datastore.beginTransaction() //★modelsのmodelを全部datastoreに追加 models.map( model => Datastore.put(tx, model) ) //トランザクション終了 tx.commit() //dataのオブジェクトを返す data } } //Dataの情報を取得 def getData(key: Key, version: Long): UploadedData = { Datastore.get(d, key, version) } //取得したDataの情報からFragmentをかき集めて修復 def getBytes(uploadedData: UploadedData): Array[Byte] = uploadedData match { //☆ケースにしてみた case null => throw new NullPointerException( "The uploadedData parameter must not be null." ) //★引数のフラグメントをmapで各々バイトに変換してArrayに格納,Arrayを最後にjoin case _ => ByteUtil.join(uploadedData.getFragmentListRef().getModelList().map( item => item.getBytes() ).toArray ) } //削除するDataのKeyを指定してDataとFragmentの両方を削除 def delete(key: Key): Unit = { val tx: Transaction = Datastore.beginTransaction(); //削除するエンティティのKey一覧 val keys: List[Key] = Nil //削除するエンティティ一覧にDataとFragmentのKeyを追加 keys.add(key); //★全部追加するメソッドとか元々ある keys.addAll(Datastore.query(f, key).asKeyList()); //トランザクション内で削除とトランザクション完了 Datastore.delete(tx, keys); Datastore.commit(tx); } }
CentOSにvsftpd-2.2.2をソースからインストール
tar -xzvf vsftpd-2.2.2.tar.gz cd vsftpd-2.2.2 make
すると、vsf_findlibs.sh の実行のところで
/lib/libcap.so.1、/lib/libpam.so.0 が読めないとか言うエラーが出る。
これはCentOSの64bit版を使っていることが原因のようで、
ひとまず対処策として、vsf_findlibs.sh 内の /lib/libcap.so.1 を /lib64/libcap.so.1 に
/lib/libpam.so.0 を /lib64/libpam.so.0 にそれぞれ変更することでひとまず make 完了。
vsf_findlibs.sh ではライブラリの場所の設定を行っているようで。
利用できるライブラリのパスが追加できればOK。
/usr/share/empty/ と ユーザ nobody がいない場合はそれぞれ作成。
/var/ftp と ユーザ ftp がいない場合はそれぞれ作成。
make install
インストール完了
ちなみに今回みつからなかったライブラリの
PAMとCAPについてだが、
PAM:
CAP: