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

2012年6月25日月曜日

Grails + CAS、CASクライアント編

Acegiプラグインを使ってCASでSSOします。

まず、Grails + CAS、CASサーバ編で作成したユーザ管理アプリをCASクライアントにしてみる。

ユーザ管理アプリのgrails-app/conf/SecurityConfig.groovyに以下の内容を追加する。

// CAS
useCAS = true // false
cas.casServer = 'localhost'
cas.casServerPort = '8080' // '443'
cas.casServerSecure = false // true
cas.localhostSecure = false // true
cas.failureURL = '/index.gsp' // '/denied.jsp'
cas.defaultTargetURL = '/'
cas.fullLoginURL = 'http://localhost:8080/cas/login' // 'https://localhost:443/cas/login'
cas.fullServiceURL = 'http://localhost:8080/cas' // 'https://localhost:443/cas'
cas.authenticationProviderKey = 'cas_key_changeme'
cas.userDetailsService = 'userDetailsService'
cas.sendRenew = false
cas.proxyReceptorUrl = '/' // '/secure/receptor'
cas.filterProcessesUrl = '/j_spring_cas_security_check'

コメントアウトはDefaultSecurityConfig.groovyの設定。
cas.failureURLはdenied.jspを用意するのが面倒なので。
cas.proxyReceptorUrlはCASサーバがリクエストしてくるので'/'に変更。'/secure/receptor'のままだとCASサーバ側でエラーが発生してしまい、Single Sign OutができなくなったSpring SourceのCASサンプルが'/secure/receptor'に設定してるから?

あとはweb.xmlにSingleSignOutフィルタを書く。

grails install-templates

<filter>
   <filter-name>CAS Single Sign Out Filter</filter-name>
   <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>

<filter-mapping>
   <filter-name>CAS Single Sign Out Filter</filter-name>
   <url-pattern>/j_spring_cas_security_check</url-pattern>
</filter-mapping>

<listener>
   <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>

これでOK。
認証が必要なページに遷移するとCASのログイン画面が表示される。ログインすると、Spring Security的にもログインする。ログアウトはCASのログアウトリクエストを送る必要がある。そうしないと、Cookieに情報が残ったままになるので

CASの仕組みについてはcas-server-coreの処理を読めばだいたい分かるのでそちらを見てください。ちなみによく見たクラスは以下。
  • CentralAuthenticationServiceImpl
  • TicketGrantingTicketImpl
  • ServiceTicketImpl


そろそろサンプルをどこかにおきたい。次回はCASサーバが接続するデータベースとCASクライアントのアプリが接続するデータベースが異なる場合についてまとめる。

2010年11月1日月曜日

GrailsとTika

アップロードしたファイルのテキストを抽出したいのでApache Tikaを使う。

grails-app/conf/BuildConfig.groovy

grails.project.dependency.resolution = {
dependencies {
build 'org.apache.tika:tika-parsers:0.7'
}
}


grails-app/controller/UploadFileController.groovy

import org.springframework.web.multipart.MultipartFile
import org.apache.tika.metadata.Metadata
import org.apache.tika.parser.AutoDetectParser
import org.apache.tika.sax.BodyContentHandler

class UploadFileController {

:

def save = {
def uploadFileInstance = new UploadFile()
def file = params.file
if (file instanceof MultipartFile && file.originalFilename) {
uploadFileInstance.name = file.originalFilename
uploadFileInstance.size = file.size
uploadFileInstance.contentType = file.contentType
uploadFileInstance.bytes = file.bytes
def stream
try {
stream = file.inputStream
def parser = new AutoDetectParser()
def writer = new StringWriter()
def handler = new BodyContentHandler(writer)
def metadata = new Metadata()
parser.parse(stream, handler, metadata)
uploadFileInstance.text = writer.toString()
}
catch (Exception e) {
log.warn(e.message, e)
}
finally {
stream?.close()
}
}
if (uploadFileInstance.save(flush: true)) {
flash.message = "${message(code: 'default.created.message', args: [message(code: 'uploadFile.label', default: 'UploadFile'), uploadFileInstance.id])}"
redirect(action: "show", id: uploadFileInstance.id)
}
else {
render(view: "create", model: [uploadFileInstance: uploadFileInstance])
}
}

:
}

2010年10月28日木曜日

Grailsでファイルアップロード

こんなドメインクラスを作ってファイルをアップロードする。


class UploadFile {

String name
Long size
String contentType
byte[] bytes

static constraints = {
}
}


これだと一覧表示でUploadFile.list()すると、不要にファイルの内容まで引っ張ってきてしまって遅い。なのでこうする。


class UploadFile {

String name
Long size
String contentType
UploadData data

static constraints = {
}
}

class UploadData {

byte[] bytes

static belongsTo = [UploadFile]

static constraints = {
}
}


これだとUploadDataへの関連がデフォルトでlazy:trueなので、一覧表示が速くなる。DataBindingはできなくなるけど仕方ない。他にいい方法ないかなぁ。

2010年8月16日月曜日

grailsのメッセージファイル(自分用)

1.3.x用に更新

default.doesnt.match.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]パターンと一致していません。
default.invalid.url.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、URLではありません。
default.invalid.creditCard.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、正当なクレジットカード番号ではありません。
default.invalid.email.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、メールアドレスではありません。
default.invalid.range.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]から[{4}]範囲内を指定してください。
default.invalid.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]から[{4}]以内を指定してください。
default.invalid.max.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最大値[{3}]より大きいです。
default.invalid.min.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最小値[{3}]より小さいです。
default.invalid.max.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最大値[{3}]より大きいです。
default.invalid.min.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最小値[{3}]より小さいです。
default.invalid.validator.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、カスタムバリデーションを通過できません。
default.not.inlist.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]リスト内に存在しません。
default.blank.message=[{1}]クラスのプロパティ[{0}]の空白は許可されません。
default.not.equal.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]と同等ではありません。
default.null.message=[{1}]クラスのプロパティ[{0}]にnullは許可されません。
default.not.unique.message=クラス[{1}]プロパティ[{0}]の値[{2}]は既に使用されています。

default.paginate.prev=戻る
default.paginate.next=次へ
default.boolean.true=はい
default.boolean.false=いいえ
default.date.format=yyyy/MM/dd
default.number.format=0

default.created.message={0}{1}を作成しました。
default.updated.message={0}{1}を更新しました。
default.deleted.message={0}{1}を削除しました。
default.not.deleted.message={0}{1}を削除できませんでした。
default.not.found.message={0}{1}が見つかりません。
default.optimistic.locking.failure={0}{1}は他のユーザによって更新されました。

default.home.label=ホーム
default.list.label={0}一覧
default.add.label={0}追加
default.new.label=新規{0}
default.create.label={0}作成
default.show.label={0}詳細
default.edit.label={0}編集

default.button.create.label=作成
default.button.edit.label=編集
default.button.update.label=更新
default.button.delete.label=削除
default.button.delete.confirm.message=本当に削除しますか?

# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
typeMismatch.java.net.URL=プロパティ[{0}]には有効なURLを入力してください。
typeMismatch.java.net.URI=プロパティ[{0}]には有効なURIを入力してください。
typeMismatch.java.util.Date=プロパティ[{0}]には有効な日時を入力してください。
typeMismatch.java.lang.Double=プロパティ[{0}]には有効な数値を入力してください。
typeMismatch.java.lang.Integer=プロパティ[{0}]には有効な数値を入力してください。
typeMismatch.java.lang.Long=プロパティ[{0}]には有効な数値を入力してください。
typeMismatch.java.lang.Short=プロパティ[{0}]には有効な数値を入力してください。
typeMismatch.java.math.BigDecimal=プロパティ[{0}]には有効な数値を入力してください。
typeMismatch.java.math.BigInteger=プロパティ[{0}]には有効な数値を入力してください。

2009年12月22日火曜日

Quartzプラグインで予約と取消


grails create-app quartz-app
cd quartz-app
grails install-plugin quartz
grails create-job sample
grails create-controller schedule


やりたいのは任意のタイミングで実行するジョブの予約と取消。
grails-app/jobs/SampleJob.groovy

class SampleJob {
static triggers = {
}
def execute(context) {
println "execute: $context"
}
}

空のtriggersを定義しないとデフォルトで1分ごとに設定されるので注意。

grails-app/jobs/ScheduleController.groovy

class ScheduleController {

def index = { }

def jobManagerService

// 5秒後にSampleJobを実行するように予約する。
def schedule = {
def scheduleDate = new Date()
use(org.codehaus.groovy.runtime.TimeCategory) {
scheduleDate += 5.second
}
def trigger = new org.quartz.SimpleTrigger('myTriggerName', "myTriggerGroup", scheduleDate)
try {
SampleJob.schedule(trigger) // 返却値はscheduleDate
flash.message = 'schedule success'
} catch (org.quartz.ObjectAlreadyExistsException e) {
flash.message = 'schedule failure'
}
redirect(action: index)
}

// 5秒以内なら上の予約を取り消す。
def unschedule = {
if (jobManagerService.unscheduleJob ("myTriggerGroup", 'myTriggerName')) {
flash.message = 'unschedule success'
}
else {
flash.message = 'unschedule failure'
}
redirect(action: index)
}
}

scheduleアクションでは実行前で予約済みのトリガ名を登録すると例外が発生するで連続で二回すると例外が発生する。unscheduleアクションでは、予約取消できない場合はfalseが返ってくる。

grails-app/views/schedule/index.gsp

<html>
<head>
<title>Welcome to Grails</title>
<meta name="layout" content="main" />
</head>
<body>
<g:if test="${flash.message}">
<div class="message">
${flash.message}
</div>
</g:if>
<div class="dialog" style="margin-left:20px;width:60%;">
<ul>
<li class="controller">
<g:link controller="schedule" action="schedule">schedule</g:link>
</li>
<li class="controller">
<g:link controller="schedule" action="unschedule">unschedule</g:link>
</li>
</ul>
</div>
</body>
</html>

実行すれば試せます。

grails run-app

2009年11月7日土曜日

Grails + CAS、CASサーバ編

CASでSSOやることになったのでメモ。
ユーザ情報は既存のユーザ管理アプリのDBを使用する。

おっきく分けて3つ。
  1. Grails Spring Security Pluginでユーザ管理アプリを作る
  2. CASサーバの構築
  3. Grails Spring Security PluginでCASクライアント

3つめは次回。

ということで、まずは「Grails Spring Security Pluginでユーザ管理アプリを作る」は本題ではないのでちゃちゃっと作る。

grails create-app ums
cd ums
grails install-plugin acegi
grails create-auth-domains
grails generate-manager

データベースはpostgresql 8.3。
データベースユーザは"sa"でパスワードなし。
データベースはlocalohst:5432:umsdb
ユーザはPersonのまま(テーブル:person、カラムはusername、passwd、enabledなど)

次に、今回のメイン「CASサーバの構築」。やることは
  1. ダウンロード、展開
  2. ファイルを2つ編集
  3. ビルドして配備
まずはCAS ServerをJASIG Communityから落としてくる。
http://www.jasig.org/cas/download

今回使うのはCAS Server 3.3.4 Final。
cas-server-3.3.4-release.zipを落として展開する。

で、cas-server-3.3.4/cas-server-webapp/pom.xmlを編集、61行目のldapをjdbcにして、ついでにJDBCドライバとデータソース用のライブラリも追加しておく。

<dependency>
    <groupId>org.jasig.cas</groupId>
    <artifactId>cas-server-support-jdbc</artifactId>
    <version>${project.version}</version>
</dependency>
<dependency>
    <groupId>postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>8.3-603.jdbc3</version>
</dependency>
<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.2</version>
</dependency>

cas-server-3.3.4/cas-server-webapp/src/main/webapp/WEB-INF/deployerConfigContext.xmlを編集、データソースとユーザテーブルの情報を設定する。
データソースのbeanを定義して
92行目のSimpleTestUsernamePasswordAuthenticationHandlerをSearchModeSearchDatabaseAuthenticationHandlerに差し替える。ユーザテーブル、ユーザIDカラム、パスワードカラム、パスワードのエンコード、データソースを設定してあげる。

<bean class="org.jasig.cas.adaptors.jdbc.SearchModeSearchDatabaseAuthenticationHandler">
    <property name="tableUsers"><value>person</value></property>
    <property name="fieldUser"><value>username</value></property>
    <property name="fieldPassword"><value>passwd</value></property>
    <property name="dataSource" ref="dataSource"/>
    <property name="passwordEncoder">
        <bean class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"> 
            <constructor-arg value="SHA1" />
        </bean>
    </property>
</bean>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName">
        <value>org.postgresql.Driver</value>
    </property>
    <property name="url">
        <value>jdbc:postgresql:umsdb</value>
    </property>
    <property name="username"><value>sa</value></property>
    <property name="password"><value></value></property>
</bean>

maven2でビルドする。

cd cas-server-3.3.4/cas-server-webapp/
mvn package


最後にTomcatに配備してhttp://localhost:8080/cas/login/にアクセスして確認しておk。

これでCASサーバの準備ができたので、次回はCASクライアント編。
JDBCTicketRegistryについても調査済みなので次々回あたりでまとめる。

参考にしたページ:
JDBC - CAS User Manual - JA-SIG Wiki

2009年10月30日金曜日

Grailsの新しい本が出てた

Grails: A Quick-Start Guideを買いました。

ついでにGrails in Actionも買っちゃいました。

で、ざっと見た感想です。

Grails: A Quick-Start GuideはなんていうかStart Guideでした。例に習ってけばいい感じに身に付く感じ。はじめての人はこちらな感じがしました。

Grails in Action はどちらかというとUser Guideな感じ? 本家のUser Guide + いくつかプラグインの使い方という印象を受けました。

Grailsに触れてみたいならGrails: A Quick-Start Guide
Grailsに慣れてきたらGrails in Action
日本語がいいならGrails徹底入門

2009年10月28日水曜日

Custom Constraintsを使ってみた

制約を自由に追加できるプラグインがリリースされたので早速使ってみた。

grails install-plugin constraints

とりあえずalpha(英字のみ許す)制約で作成する。

grails create-constraint alpha

するとgrails-app/utils/AlphaConstraint.groovyが生成される。同時にテストケースも作成される。あとはvalidatorを書く感じにvalidateクロージャを実装する。

class AlphaConstraint {

def defaultMessageCode = 'default.not.alpha.message'

def supports = { type ->
return type!= null && String.class.isAssignableFrom(type)
}

def validate = { propertyValue ->
propertyValue ==~ /[A-Za-z]+/
}
}

defaultMessageCodeを定義すると、定義したキーでメッセージが使える。
supportsはどの型用の制約かを定義する。

メッセージ定義に以下を追加する。埋め込みオブジェクトは、プロパティ名、クラス名、プロパティ値、制約のパラメタ。

default.not.alpha.message=Property [{0}] of class [{1}] with value [{2}] must be alphabet

これでドメインクラスに制約として使える。今までvalidatorに実装してたのも使い回しができるようになって最高です。validatorと違うところはメッセージキーを返せないところ。validateクロージャの実行結果がBoolean(boolean)にならないとClassCastExceptionが発生した。
使い方はこんな感じ。

class Book {

def title

static constraints = {
title(blank: false, alpha: true)
}
}

つづいて他のプロパティを参照する系を試してみる。
例としてleProperty(他のプロパティ以下であること)制約を作る
他のプロパティを参照する場合は、やっぱりvalidatorの実装と同様に2つ目のパラメタを用意してあげればいい。

class LePropertyConstraint {

def defaultMessageCode = 'default.not.leProperty.message'

def validate = { propertyValue, target ->
target."$params" ? propertyValue <= target."$params" : true
}

}

paramsは制約のパラメタ。
使うときは以下のような感じ。

class Project {
Date dateStarted
Date dateEnded
static constraints = {
dateStarted(leProperty: 'dateEnded')
dateEnded()
}
}

制約を修正するとリロードするけど、その制約を評価したときに例外が発生する。そこは今後に期待です。

2009年10月27日火曜日

Searchableプラグインであいまい検索

saveでindex作成するときにSynonymLookupProviderが提供するシノニムをindexに混ぜ込むかんじ。Searchable.groovyにURLが張ってあるからその通りにやってみた。


grails create-app myapp
cd myapp
grails install-plugin searchable
grails install-searchable-config
grails create-domain-class book
grails create-controller book


grails-app/conf/Searchable.groovy

compassSettings = [
'compass.engine.analyzer.myAnalyzer.filters':
'myAnalyzerfilter',
'compass.engine.analyzerfilter.myAnalyzerfilter.type':
'synonym',
'compass.engine.analyzerfilter.myAnalyzerfilter.lookup':
'sample.MySynonymLookupProvider'
]


src/groovy/sample/MySynonymLookupProvider.groovy

package sample

import org.compass.core.CompassException
import org.compass.core.config.CompassSettings
import org.compass.core.lucene.engine.analyzer.synonym.SynonymLookupProvider

class MySynonymLookupProvider implements SynonymLookupProvider {

String[] lookupSynonyms(String string) {
def synonyms = [string]
if (string == 'artifact') {
synonyms << 'artefact'
}

return synonyms as String[]
}

void configure(CompassSettings settings) throws CompassException {
}

}

grails-app/domain/Book.groovy

class Book {

String title

static searchable = {
analyzer 'myAnalyzer'
// title(analyzer: 'myAnalyzer') カラムごとに指定するなら
}

}


これで new Book(title: 'artifact').save() すると、artefactで検索に引っかかるようになる。日本語でやるならSen入れないとだめぽい。

2007年9月12日水曜日

Grailsで素敵にJSON

JSonBuilderを使用してみた。結論から言えば、単純なテーブルっぽいのを返すなら使える。複雑なのを返そうとするなら素敵とは言えない。Grails - Ajaxには使い方が少ししか載ってないので、ソースコードを追ってみたらテストケースにいろいろ書いてあった。テストケースはgrails-src-0.6.zipなどを解凍して、test\commons\grails\util\JSonBuilderTests.java。テスト用のparseメソッドでいろいろ試せるのがよい。

render(builder:'json') {
A{
B("BB") {
C("CC") {
// D("DD") 例外が発生する。
}
}
}
}
Dがないと動く。結果は{"A":[{"B":"BB","C":"CC"}]}。配列を使う場合は繰り返し文を使用しないといけないよう。例えば、
render(builder:'json') {
A {
B(C:"C1", D:"D1")
B(C:"C2", D:"D2")
}
}
とは書けない。何かしらeachでもforでも回してあげるとうまくいく。
render(builder:'json') {
A {
BList.each {
B(C:it.C, D:it.D)
}
}
}
こんな感じ。他にもいろいろ試してみた。

render(builder:'json') {
A {
B("BB") {
C {
D(E:"EE", F:"FF") {
G("GG") {
// H("HH") { 例外が発生する。
I {
J("JJ")
}
// }
}
}
}
}
}
}

Hの箇所で例外が発生する。3階層が駄目なのか?上記のコードの結果は下記。結果が微妙すぎる。名前のみのAとCは配列になるみたい。

{"A":[
{"B":"BB", "C":[
{"E":"EE","F":"FF"},
{"G":"GG","I":[
{"J":"JJ"}
]}
]}
]}

とりあえず配列を2つ持つようなのの返し方がわからなかった。あと配列もたせる場合に、その他の要素?を2つ以上持たせる方法もわからなかった。そもそも階層がずれてるあたりは意味がわからない。parseメソッドで例外が発生するタイミングもよくわからないので解析する気にならない。それくらいなら自分でビルダー組んだ方が良いかも。そのうち直るのかな?複雑な使い方はやめた方が良い感じ。

JSonBuilderのソースコードを見たら'a'とか'o'とかでarrayとobjectを何とかしようとしてるみたいだけど、これもよくわからず。今日はここまで。

2007年9月3日月曜日

Extでツリー表示

JSONを使用したstaticなツリーを試してみた。サーバからJSON返せれば素敵だけど、それは先のお話。

<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=utf-8">
<title>Sample - Tree</title>
<link rel="stylesheet" type="text/css" href="css/ext-all.css" />
<script type="text/javascript" src="js/ext-base.js"></script>
<script type="text/javascript" src="js/ext-all.js"></script>
<script type="text/javascript"><!--
Ext.onReady(function(){
var json =
[
{'text':'A', 'id':1, 'leaf':false, 'children':[
{'text':'AA', 'id':11, 'leaf':false, 'children':[
{'text':'AAA', 'id':111, 'leaf':true},
{'text':'AAB', 'id':112, 'leaf':true},
]},
{'text':'AB', 'id':12, 'leaf':false, 'children':[
{'text':'ABA', 'id':121, 'leaf':true},
{'text':'ABB', 'id':122, 'leaf':true},
]}
]},
{'text':'B', 'id':2, 'leaf':false, 'children':[
{'text':'BA', 'id':21, 'leaf':false, 'children':[
{'text':'BAA', 'id':211, 'leaf':true},
{'text':'BAB', 'id':212, 'leaf':true},
]},
{'text':'BB', 'id':12, 'leaf':false, 'children':[
{'text':'BBA', 'id':221, 'leaf':true},
{'text':'BBB', 'id':222, 'leaf':true},
]}
]}
];

var tree = new Ext.tree.TreePanel('tree', {
// jsonを内部でcreateNodeするために必要
loader: new Ext.tree.TreeLoader(),
// 要素のドラッグ&ドロップを許可
enableDD:true,
// ルート要素の可視設定
rootVisible:true,
});

var root = new Ext.tree.AsyncTreeNode({
text: 'Root',
id:'root',
// ルート要素のドラッグを許可しない
draggable:false,
// 上記の変数jsonを設定
children: json
});

tree.setRootNode(root);
tree.render();

// ルート要素を展開
root.expand();
});
//--></script>
<style type="text/css">
#tree {
height:200px;
width:100px;
border:1px solid #c3daf9;
overflow:auto;
}
</style>
</head>
<body>
<div id="tree" />
</body>
</html>

最終的にはGrailsと連携させる予定。GrailsのJsonビルダーが素敵。

Grails - Ajax