ロケール対応プラグインを作ってみる(Grails 0.5)
ためしに、リクエストのロケールに対応するGSPビューを出力するプラグインを作ってみる。やりたいことは、
- リクエストのロケールがjaの場合でviews/hogehoge/list_ja.gspが存在する場合、これを出力する。
- リクエストのロケールがenの場合でviews/hogehoge/list_en.gspが存在しない場合、list.gspを出力する。
と、ロケールによって出力するビューそのものを変えるようなプラグインを作成する。以下の方法は、いろいろとやってみてこれで動いたみたい!!な内容なので、Grailsの仕様とかにあっているかは保証しません。あしからず。
まずは、適当なディレクトリでプラグインプロジェクトを作成する。
grails create-plugin locale-view-switcher
次に、作成したプロジェクトディレクトリ配下の、src/java/jp/ne/hatena/d/noryksj/grails/pluginsに以下のようなソースを作成。
package jp.ne.hatena.d.noryksj.grails.plugins; import java.util.Locale; import grails.util.GrailsUtil; import org.codehaus.groovy.grails.web.servlet.view.GrailsViewResolver; import org.springframework.context.ApplicationContext; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.web.servlet.View; /** * A plug-in that switches gsp views according to locale of request. * @author noryksj */ public class LocaledViewResolver extends GrailsViewResolver { private static final String GSP_SUFFIX = ".gsp"; private static final String GROOVY_PAGE_RESOURCE_LOADER = "groovyPageResourceLoader"; private ResourceLoader resourceLoader; /** {@inheritDoc} */ public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; super.setResourceLoader(resourceLoader); } /** * Copied from GrailsViewResolver * @return ResourceLoader */ private ResourceLoader establishResourceLoader() { ApplicationContext ctx = getApplicationContext(); if(ctx.containsBean(GROOVY_PAGE_RESOURCE_LOADER) && GrailsUtil.isDevelopmentEnv()) { return (ResourceLoader)ctx.getBean(GROOVY_PAGE_RESOURCE_LOADER); } return this.resourceLoader; } /** {@inheritDoc} */ protected View createView(String viewName, Locale locale) throws Exception { ResourceLoader resourceLoader = establishResourceLoader(); String prefix = getPrefix(); String path = prefix + viewName + "_" + locale.getLanguage() + GSP_SUFFIX; Resource res = resourceLoader.getResource(path); if (res != null && res.exists()) { viewName = path.substring(prefix.length(), path.length() - GSP_SUFFIX.length()); } return loadView(viewName, locale); } /** {@inheritDoc} */ protected Object getCacheKey(String viewName, Locale locale) { return viewName + "_" + locale.getLanguage(); } }
作成するのは、GrailsViewResolverを継承して作成する。なぜかは、デバッガでいろいろとみてみたところ、これが良さそうかなーと思ったからだ。GrailsViewResolverでは、使用しているResourceLoaderその他サフィックスなど(GSP_SUFFIXとか、GROOVY_PAGE_RESOURCE_LOADERのことね)が全てprivateなので、これをコピーして同等な機能をまず作成する。protectedとかだったら良かったのに。
やりたいことは、以下の箇所だ。とりあえず、ロケールの言語のみの対応で。
/** {@inheritDoc} */ protected View createView(String viewName, Locale locale) throws Exception { ResourceLoader resourceLoader = establishResourceLoader(); String prefix = getPrefix(); String path = prefix + viewName + "_" + locale.getLanguage() + GSP_SUFFIX; Resource res = resourceLoader.getResource(path); if (res != null && res.exists()) { viewName = path.substring(prefix.length(), path.length() - GSP_SUFFIX.length()); } return loadView(viewName, locale); } /** {@inheritDoc} */ protected Object getCacheKey(String viewName, Locale locale) { return viewName + "_" + locale.getLanguage(); }
GrailsのJavaDocをみていただければわかるのだが、GrailsViewResolverは、org.springframework.web.servlet.view.AbstractCachingViewResolverの子孫クラスとなっている。実際、production環境で起動すると結果をキャッシュする。これが、ResourceLoaderではなくGraisViewResolverを継承することとした理由でもある。
createView(String,Locale)では、ビューが存在するかをチェックし存在する場合は、viewNameを変更して結果を返却するようにしている。また、getCacheKey(String, Locale)では、上記createView(String,Locale)の結果と合うように結果を返却する。このメソッドをオーバライドしてあげないとキャッシュされた結果がおかしくなる。というのは、org.springframework.web.servlet.view.UrlBasedViewResolver#getCacheKey(String, Locale)では単にパラメタを返却するだけの動作にオーバライドされているからだ。
次に、プロジェクトディレクトリに作成されたLocaleViewSwitcherGrailsPlugin.groovyファイルを以下のように修正する。
import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes; import jp.ne.hatena.d.noryksj.grails.plugins.LocaledViewResolver; /** * A plug-in that switches gsp views according to locale of request. * * @author noryksj */ class LocaleViewSwitcherGrailsPlugin { def version = 0.1 def dependsOn = [:] def doWithSpring = { jspViewResolver(LocaledViewResolver) { viewClass = org.springframework.web.servlet.view.JstlView.class prefix = GrailsApplicationAttributes.PATH_TO_VIEWS suffix = ".jsp" templateEngine = groovyPagesTemplateEngine if(grails.util.GrailsUtil.isDevelopmentEnv()) { resourceLoader = groovyPageResourceLoader } } } def doWithApplicationContext = { applicationContext -> // TODO Implement post initialization spring config (optional) } def doWithWebDescriptor = { // TODO Implement additions to web.xml (optional) } def onChange = { event -> // TODO Implement code that is executed when this class plugin class is changed // the event contains: event.application and event.applicationContext objects } def onApplicationChange = { event -> // TODO Implement code that is executed when any class in a GrailsApplication changes // the event contain: event.source, event.application and event.applicationContext objects } }
修正したのは、コメント、import、doWithSpringクロージャのみ。doWithSpringクロージャの中身はsrc/groovy/org/codehaus/groovy/grails/plugins/web/ControllersGrailsPlugin.groovyから抜粋して変更した(パクッたとも言う)。これで、使用するViewResolverをがっつりと変更だ!
最後に、
grails package-plugin
として、他のプロジェクトでgrails install-pluginとして使用してみた。当然、ビューを事前に作っておかなければならない(grails create-viewsとかして)。その後、list.gspとかをコピーしてlist_ja.gspを作成して起動する。
ただ、index.gsp(トップページ)とかがダメみたい orz。多分、ResourceLoaderとかに手を入れないといけないような気が...。これはまた今度...