概要
Grailsでは、namespaceという機能を使って、同名のコントローラを別パッケージに分けて、使い分けることができます。
使いどころとして、例えばUser
というドメインが有る場合にscaffold用Controllerを生成するとUserController.groovy
となります。
でも普通のユーザ用の個人ページ用にもUserControler.groovy
という名前って使いそうですよね?
そこで、今回のnamespaceを利用することでパッケージを分けて、同名のControllerが混在する環境を実現することができます。
コントローラの設置
前提条件:
アプリケーション名(デフォルトパッケージ):ntest
テスト用に3つのコントローラを作成します。すべて同名のコントロラーですが、パッケージが異なります。
最初のコントロラー(ntest/TestController.groovy)は通常のコントローラです。
package ntest
class TestController {
def index() {
render "Test Controller in ntest Package!"
}
}
package ntest.hoge
class TestController {
static namespace = 'hoge'
def index() {
render "Test Controller in ntest.hoge Package!"
}
}
package ntest.piyo
class TestController {
static namespace = 'piyo'
def index() {
render "Test Controller in ntest.piyo Package!"
}
}
ntest.hogeパッケージとntest.piyoパッケージのコントローラで少し見慣れないstatic namespace = 'hoge(piyo)'
という記述があります。
これがnamespaceの正体で、この値をUrlMappings.groovy
で利用します。
URLの設定
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
// この2行を追加した
"/namespace/a/$controller/$action?/$id?(.$format)?" (controller: "test", namespace: 'hoge')
"/namespace/b/$controller/$action?/$id?(.$format)?" (controller: "test", namespace: 'piyo')
"/"(view:"/index")
"500"(view:'/error')
}
}
コードを見れば一目瞭然ですが、各URLに紐づくコントローラの指定と同時にnamespaceも指定しています。
ここで利用する値が、先ほどコントローラ内で記述したstatic namespace = 'hoge(piyo)'
の値になります。
なお、"/$namespace/$controller/$action?"()
としておけば、ネームスペース名/コントローラ名/アクション名/で統一的にアクセスできるようになります。
確認
実際にGrailsを起動して、それぞれのコントローラにアクセスしてみると確認できます。
http://localhost:8080/ntest/test/index (通常のコントローラ)
http://localhost:8080/ntest/namespace/a/test/index (ntest.hogeパッケージ)
http://localhost:8080/ntest/namespace/b/test/index (ntest.piyoパッケージ)
それぞれ出力結果が異なることが解ると思います。
namespaceを利用することで、明らかに最適なコントローラ名なのにすでに同じものがあって泣く泣くプレフィックスなどを付けて回避するということがなくなります。
ただし! うっかりnamespaceをつけ忘れてしまうと、Grailsが同名のコントローラの内どちらを使うかは決まっていません。(今回のサンプルははnamespaceが付いていない同名コントローラが存在しないので問題ない。)
コンパイルエラー等で事前に察知することができないので利用する際には注意しましょう。
リンクを生成する際の注意
公式ドキュメントなどでは、g:link
タグにnamespace
属性をしているとありますが、少なくとも Grails2.3.7では動作しません
そのため、回避策としてUrlMappingsで指定した今回のURLに名前を付けてあげる必要があります。
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
// name 任意の名前 : 任意のURL {コントローラとかネームスペースの指定}とうフォーマット
name hogeTest : "/namespace/a/$controller/$action?/$id?(.$format)?"{
controller = "test"
namespace = 'hoge'
}
name piyoTest: "/namespace/b/$controller/$action?/$id?(.$format)?" {
controller = "test"
namespace = 'piyo'
}
"/"(view:"/index")
"500"(view:'/error')
}
}
で、実際にgspなどでリンクを生成する際には、 mapping という属性に今回新たに追加したnameを指定してあげます。
<g:link mapping="hogeTest" action="index">TEST</g:link><br />
<g:link mapping="piyoTest" action="index">TEST</g:link>
<g:form mapping="hogeTest">
<g:submitButton name="click!" />
</g:form>
これでちゃんとnamespaceの反映されたリンクが生成されます。
SpringSecurityCore2.0を導入している際の問題と回避方法
SpringSecurityCore2.0RCを使っていると、namespaceを指定しているコントローラに @Secured アノテーションを記述しても 有効になりません。
@Securedアノテーションの指定してあるnamespaceが指定されているページにアクセスすると以下のようなメッセージが表示されます。
Sorry, you're not authorized to view this page.
おそらく、SpringSecurityCore2.0RC自体がnamespaceを想定していないのではないか、と思われます。(ソースを読んでいないのでただの予想)
回避方法として、Config.groovy
に、UrlMappings.groovyで指定したnamespace用のURLにアクセスした際の挙動をgrails.plugin.springsecurity.controllerAnnotations.staticRules
に追加してあげます。
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
'/': ['permitAll'],
'/index': ['permitAll'],
'/index.gsp': ['permitAll'],
'/**/js/**': ['permitAll'],
'/**/css/**': ['permitAll'],
'/**/images/**': ['permitAll'],
'/**/favicon.ico': ['permitAll'],
'/**/namespace/a/**': ['ROLE_ADMIN'] // これを追加した
'/**/namespace/b/**': ['ROLE_ADMIN'] // これを追加した
]
最後の2行のURLを追加しました。
これで無事SpringSecurityCoreを導入していて、namespaceを利用することができるようになります。
このようにConfig.groovyで、指定したURLにアクセスした際の動作をControllerの@Secureアノテーション同様に制御できます。
参考
http://grails.jp/doc/latest/guide/single.html#namespacedControllers
https://jira.grails.org/browse/GPSPRINGSECURITYCORE-246