Category Archives: Web技術

Mayaaでm:idの組み方が原因でページ全体がエラーになるのを防ごう(CompiledScript / ScriptEnvironmentの拡張例) #mayaa

これは Mayaa Advent Calendar 2015 の14日目です。昨日は「Spring BootのテンプレートエンジンにMayaaを使おうとしてみるリベンジ編」でした。

今日は11日目の続編を書きます。

CompiledScript / ScriptEnvironmentの拡張例

Mayaaでページを作っていると、次のようなエラーに遭遇すると思います。

org.seasar.mayaa.impl.cycle.script.rhino.OffsetLineRhinoException: TypeError: Cannot call method "equals" of null in script=

これは、m:idの中に書かれたRhinoスクリプト実行時にExceptionが発生したという意味です。

これがどのようなときに起こるかというと、例えば

こんなMayaaファイルで

<!--商品を繰り返します-->
<m:forEach m:id="LOOP_ITEM" var="item" items="items">
  <m:echo><m:doBody /></m:echo>
</m:forEach>
<!--LOOP_ITEMで繰り返されるアイテムの名称を出力します-->
<m:write m:id="ITEM_NAME_HERE" value="${item.getName()}" />

こんなテンプレートを書いた時

<span m:id="ITEM_NAME_HERE">アイテム名</span>
<div m:id="LOOP_ITEM" >
<span m:id="ITEM_NAME_HERE">アイテム名</span>
</div>

本来であればLOOP_ITEMの内側に書かなければいけないITEM_NAME_HEREを外に書いてしまいました。

このようなことは、デザイナーさんか、サイト管理者の方が自らテンプレートを編集した時に起こります。

これが起こると、エラー画面が表示されてしまいます。

また、発生した例外には、mayaaファイルの行番号が書いてありますが、それはシステムログを見ない限り読めませんので、デザイナーがこれを見た時、ただただパニックになるしかありません。

これではいけません。

エラーが起きてもとりあえず画面を出す

多分世の中に初めて公開すると思います。次のカスタマイズによって、このような前述の事態を防ぐことが出来ます。

public class EbisuScriptEnvironmentImpl extends ScriptEnvironmentImpl {

    @Override
    protected CompiledScript compile(ScriptBlock scriptBlock, PositionAware position, int offsetLine) {
        if (scriptBlock.isLiteral()) {
            return super.compile(scriptBlock, position, offsetLine);
        }

        return new MyCompiledScriptWrapper(super.compile(scriptBlock, position, offsetLine));
    }
}
public class MyCompiledScriptWrapper implements CompiledScript {

    CompiledScript inner;
    public MyCompiledScriptWrapper(CompiledScript compiledScript) {
        inner = compiledScript; 
    }
    public void setExpectedClass(Class expectedClass) { inner.setExpectedClass(expectedClass); }
    public Class getExpectedClass() { return inner.getExpectedClass();  }
    public String getScriptText()   { return inner.getScriptText(); }
    public boolean isLiteral()      { return inner.isLiteral(); }
    public Object execute(Object[] args) {
        try {
            return inner.execute(args);
        } catch (OffsetLineRhinoException | MyOffsetLineScriptException e) {
            e.printStackTrace(System.err);            
            return "**** TEMPLATE ERROR!!! ****";
        }
    }
    public void setMethodArgClasses(Class[] methodArgClasses) { inner.setMethodArgClasses(methodArgClasses); }
    public Class[] getMethodArgClasses() { return inner.getMethodArgClasses(); }
    public boolean isReadOnly()     { return inner.isReadOnly(); }
    public void assignValue(Object value) { inner.assignValue(value); }
}

org.seasar.mayaa.provider.ServiceProviderに以下の記述を追加します

    <scriptEnvironment class="jp.example.MyScriptEnvironmentImpl">
        <scope class="org.seasar.mayaa.impl.cycle.scope.ParamScope"/>
        <scope class="org.seasar.mayaa.impl.cycle.scope.HeaderScope"/>
        <scope class="org.seasar.mayaa.impl.cycle.scope.BindingScope"/>

        <!-- "_" = current - page - request - session - application -->
        <scope class="org.seasar.mayaa.impl.cycle.script.rhino.WalkStandardScope"/>
        <!-- extension: java.lang.System.getProperty()
        <scope class="org.seasar.mayaa.impl.cycle.scope.EnvScope"/>
        -->
        <parameter name="wrapFactory" value="org.seasar.mayaa.impl.cycle.script.rhino.WrapFactoryImpl"/>
    </scriptEnvironment>

このようにすると、上記の記述では

**** TEMPLATE ERROR!!! ****
アイテム1
アイテム2
アイテム3

のように出て、とりあえず事故は回避されました。

画面にエラーがあった箇所の行番号を出力する

ただ、このままだとどこでエラーが起きたかわからなくなります。このままだと

変なゲジゲジがでたー!!!Σ(゚∀゚ノ)ノキャー

と言われてしまいます。(言われません)
そこで、後は簡単なテクニックになります。

public Object execute(Object[] args)
の中でテンプレートのファイル名、行番号は次のようにして取得できます。

String pageName = CycleUtil.getServiceCycle().getOriginalNode().getSystemID();
int lineNumber = CycleUtil.getServiceCycle().getOriginalNode().getLineNumber();
String message = errorMessage + "(" + pageName + ":" +  lineNumber +  ")";

あとは、この行番号と、exceptionのgetMessage()などを、CycleUtil.getRequestScope().set/getAttribute()を使って格納していって、default.mayaaにm:idを作って、テンプレートエラーが起きたら画面の上からオーバーレイしてきて表示する仕掛けなどを作るのが良いと思います。

error

この他便利な使い方

CompiledScriptの便利使い方として、他によく使う技としては、よく使うスクリプトをJavaScriptコンパイルせずショートカットして高速化を図る技があります。

その他、Rhino以外のスクリプトエンジンに切り替えることも可能です。

そこまでガチなことをやらなくても、ログに使って調査をしたりすることから始めても良いかもしれません。

結構ワクワクしますね。

まとめ

Mayaaにはこのようにわくわくする拡張ポイントがいっぱいあります。やり過ぎは厳禁ですが、今回紹介したように、トラブルを防ぐ手段として活用すると良いと思います。

Spring BootのテンプレートエンジンにMayaaを使おうとしてみるリベンジ編 #javaee #mayaa

これは Mayaa Advent Calendar 2015 の13日目です。昨日は「ぶっちゃけ、Mayaaでデザイナーと仲良くなれたの?」でした。

また、Java EE Advent Calendar 201510日目の続編です。

前回Spring Boot経験2時間で、Mayaaをフロントに使用する試みは残念ながら失敗してしまいましたが、その後、複数のコメントを頂き、再挑戦をしてみようと思います!
https://twitter.com/megascus/status/674901495137996800

https://twitter.com/making/status/674913610662064128

https://twitter.com/tty_twt/status/675299564476235776

https://twitter.com/tty_twt/status/675299730365145088

また、元の記事には、次のようなコメントも頂いています。

匿名
2015年12月11日 9:25 AM
SpringBoot というよりは Spring MVC ですが、
Spring MVC では AbstractView を継承したクラスを作成して、任意のViewへのアダプターを作ります。
例えば、
https://kinjouj.github.io/2013/12/spring-webmvc-4-abstractview.html
や、
http://www.codejava.net/frameworks/spring/spring-mvc-with-csv-file-download-example
などです。CSVダウンロードとかでも、AbstractViewを用います。

JSP と SpringBoot の相性が悪いのは、 SpringBoot が組み込みサーバ上で実行するため、
JSP のプリコンパイルなどの指定が難しいからだと思います。

「MayaaServletの登録をServletRegistrationBeanでやる」をやってみる

まずはこれが利くんじゃないかなという気がしました。というのも、ルートにindex.xhtmlを置いてアクセスしても、MayaaServletに処理が行っているようにまったく見えなかったからです。

最近のSpringは設定をアノテーションとクラスでやるようです。

import org.seasar.mayaa.impl.MayaaServlet;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MayaaServletRegistration {
    @Bean
    public ServletRegistrationBean mayaaServlet() {
        ServletRegistrationBean bean = new ServletRegistrationBean();
        bean.addUrlMappings("*.xhtml");
        bean.setServlet(new MayaaServlet());
        return bean;
    }
}

これで実行してみます。そうすると!

スクリーンショット 2015-12-13 17.00.01

おっ、エラーメッセージが変わりましたよ!どうもprefixがきいていないように見えます。では、次のようにしてみたらどうでしょうか?

@Controller
public class HelloController {
    @RequestMapping("/hello")
    public String index(ModelAndView modelAndView) {
        return "WEB-INF/view/helo.xhtml";
    }
}

スクリーンショット 2015-12-13 17.10.30

キタ━━━━(゚∀゚)━━━━!!

"forward:/helo.xhtml" をreturnしてみる。

@Controller
public class HelloController {
    @RequestMapping("/hello")
    public String index(ModelAndView modelAndView) {
        return "forward:/helo.xhtml";
    }
}

そうすると、このエラーに戻ります。

スクリーンショット 2015-12-13 17.00.01

ふむふむ。なんとなく、最後はこれのような気がします。

application.properties

#spring.view.prefix: /WEB-INF/view/
spring.mvc.view.prefix: /WEB-INF/view/
#spring.view.suffix: .xhtml
spring.mvc.view.suffix: .xhtml
@Controller
public class HelloController {
    @RequestMapping("/hello")
    public String index(ModelAndView modelAndView) {
        return "helo";
    }
}

スクリーンショット 2015-12-13 17.10.30

キタ━━━━(゚∀゚)━━━━!!

このままではまだMayaaとSpringコンテナそのものの連携という状態ではありませんが、Mayaaテンプレートにforwardしてしまえば、リクエストアトリビュートに入れるなりなんなりして、最低限MVCはできそうです。

きちんとやる場合は、ScriptEnvironmentあたりをカスタマイズして、きちんとDIしてあげる感じになるのかなあ。

まとめ

ということで、今回は ttyさんの予想がビンゴでした!

MayaaとSpring Bootは連携できるということで、めでたしめでたし。

ぶっちゃけ、Mayaaでデザイナーと仲良くなれたの?

これは Mayaa Advent Calendar 2015 の12日目です。昨日は「Mayaaの拡張ポイントベスト5」でした。

Spring Boot再挑戦記事は準備中です。今日はまだ間に合わないので、まったりトークでつなぎます!

Mayaaアドベントカレンダーは毎日参加者を募集しています)

MayaaのBefore / After

さて、僕の発表スライドでこんな図を出したことがあります。

スクリーンショット 2015-12-12 22.00.15

釣りっぽい画像ではないかと思われているかもしれませんので、今日は実際問題どうなのかを書こうと思います。

ただし、僕、エンジニア目線です。

Mayaa導入以前の孤独な日々

僕はシステム会社の一プログラマーで、ECサイトを作るときは、デザイナーさんがHTMLで書いたモックを提供してくれるので、それをJSPに埋め込む仕事をしていました。まだ大学生のアルバイトでプログラマーをしていましたが、業務と言ったらこの業務が多かったです。

当時tableレイアウト全盛なので、気が遠くなるほどネストしたtableタグやスペーサー画像を駆使したレイアウトは慣れてます。

こういう単純業務の存在は、大学生で未経験の僕にも仕事ができたチャンスであったとも言えたかもしれません。しかし、いつまでもこんなことをしているのかと思うと残念な気持ちでした。

デザイナーさんは、会社にいたりいなかったりしました。いる時もいない時も、話したことはほとんどありません。SEの方は話すのかもしれませんが、末端のプログラマーとなると、まあ、そんなものでしょう。

Mayaaを導入した日何が起きたか

そんなわけで、テンプレートエンジンが必要だとなりまして、作ります。作ってる時は、JSPからの単純移植なので、まだデザイナーさんとは話しません。

一通り移植できたところで、当時社内に一人だけいたデザイナーさんが呼び出されました。

「これは今うちではいしがみくんしか使えない技術なんだけど、これからはきみがこの技術を使ってサイトを作っていくことになるから、二人で使いやすいように整理してほしい」

という上司の言葉で、その日から1ヶ月ほどデザイナーさんと席を並べて、m:idの命名規則などの調整をする日が始まりました。

2人で全体を見渡してみると、m:idを組み込んだ人によって、名前付けのくせにばらつきがあることでした。結果として一番多く書いた僕のスタイルへ統一する方針になりました。

Excelスプレッドシートを使って旧IDから新IDにマッピング表を作って、これをもとに変換する一回限りのスクリプトを作って変換を行いました。(一撃で全部のmayaaファイルとxhtmlを書き換えるやつです。今思うとすごい勇ましいスクリプトですね(^^;)

その一方で今後作られるm:idは規則を守るようにルールも社内公開しました。その後、誰かが規則破りのm:idを作ったりすると、僕が気づくよりも前にデザイナーさんが怒ってくれたりします。

「いしがみくんのm:idが一番わかりやすいので、いしがみくんのに合わせてください!」

(僕がきちんと指導できなかったのが問題ですが、そんなこと言われたらちょっと嬉しかった!)

互いに理解する

デザイナーさんとしては初めて聞くシステムの言葉もあったでしょう。これは何か、これはどういう意味か、色々苦労されたようで、システム用語をなんとか分かるように説明する必要がありました。

この作業の中で、HTMLページの中で、デザインとはどの部分かという感覚が身につきました。例えばinput type="hidden"タグはデザインではないです。

デザイナーさんが優秀だったからかもしれませんが、m:id="なになに"という書式は抵抗なく受け入れてくれました。一方mayaaファイルはコメント一つ変えることも怖くてできないようでした。その結果、m:idの説明書一覧表が大変重要なポイントであり、ここを死守することが連携を成功させるのに必須であることがわかりました。

JavaScriptはどちらも苦手だけど力を合わせると最強になれる

最近はJavaScriptを使いこなすデザイナーさんが増えましたが、当時はjQuery一般的ではなかったので、デザイナーさんはJavaScriptを使えません。一方、僕はJavaScriptの文法はわかりますが、これを使って作れるウィジットにどんなものがあるのかは詳しくありません。

結局、いつも出だしはデザイナーさんが色々なところから一生懸命集めてきたスクリプトを組み合わせて、うまくいかない、僕が呼ばれてデバッグして解決する。。。ということの繰り返しでした。その過程で

「プログラマーすごい!」

などとも言われました。まじですか!仕事とはいえ、女性の方に、技術的なことで「すごい」なんて言われたことありません><

僕からしたら、こんな色んな部品を持ってきてサイトを作れるデザイナーってすごいと思いますよ!

そして次第に話さなくなる

デザイナーさんと密に連携したのは初めの半年〜1年くらいでした。そこから先はお互いノウハウが蓄積したこともあって、ほとんど話さずに業務ができるようになってきました。

たまにJSPの案件の時などに、デザイナーさんの席に僕が足を運んで

「ここをこんな風にしたいのだけど、どんなタグの打ち方をするべきか?あと、ここにこういうアイコンを作って欲しいのだけどどこかに素材ありますか?なければお願いできますか?」

と言って、作業をしたりするので、JSPの方がむしろ共同作業をしているようです。

というか、こんな無理なお願いをしながらスイスイアイコンとか作ってくれるデザイナーってマジ神!

更に発展するとこうなった

さらに組織が大きくなると、デザインチームはデザイナーとコーダー、フロントエンドエンジニアに分かれ、開発はSEとPMとプログラマー、プログラマーも分担制となり。。。こうなってくると、もうテンプレートエンジン云々というより、もっと上位レイヤーの体制が必要になってきます。

JavaScriptの技術も最近の若い子の方がむしろできたりするのですが、ありがたいことに、未だに僕のところに、デザインチームの方々が質問に来てくれます。

そこでは、単純にスクリプティングのことよりも、システム上、これはできるのか?お客様からこう言われ、SEはこう言っているけどどうするべきか?という、全体的なことが多いようです。

なるほど、これはまだ若いエンジニアにはできない。

デザイナー脳とエンジニア脳のどちらも必要

これまで、いろんなデザイナーさんの質問に答えてきた経験から、ある傾向があることに気づきました。

(これは一方的な視点なので間違っていたら申し訳ないですが。)

デザイナーさんは、デザインという、結果からスタートして、手段へブレークダウンしていきます。エンジニアは技術という手段からスタートして結果という目的へ向かっていきます。

その結果、何が起こるかというと、デザイナーさんはものすごい色んなことができる一方、罠にハマるとどうしたら良いのかわからなくなってしまうことが多いように見えます。

一方エンジニア肌の人は、問題ごとを細かく切り分け、デバッグし、テストし、解決することが得意です。そのため、僕はjQueryなんとかを使って画面の表現を作ることはできませんし、デザイナーさんから質問を受けるライブラリはいつも初めて見るものばかりでしたが、最終的に全て解決することができたし、時にはライブラリにパッチを作ってGitHubでfork/pushすることもありました。

これがデザイナーとエンジニアの相性が良いと言われる所以なのかなとも思います。

まとめ

一人で両方できるのがベストなのかもしれません。

しかし、一人だけでは大きな仕事はできませんから、複数人でコラボしたほうが断然大きな仕事ができます。それにあたって、デザイナーとエンジニアというのは、良い役割分担だと思います。

両者の連携を支えるのに、テンプレートエンジンはまだまだ重要だと思いますし、そういう方向性で今後も進化し続けなければならないと思います。