Category Archives: Java

MayaaでHTML5のスマートフォンページを作る際にはまったこと

僕はMayaaが好きです。仕事でもかなり使っています。

今回、スマートフォン(iPhoneおよびAndroid対応)向けECサイトのフロントエンドに、Mayaaを使用しました。そこではまったことを報告します。

metaタグにContentTypeが省略できるようになったため、ドキュメント判別の手段が減ってしまった

HTML5では、

<meta charset="UTF-8">

のような書き方が許されるようになりました。

従来は

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

という書き方をしていました。
これで何が問題なのかというと、Mayaa自体がこのContent-Type指定を読んでいてそれによって処理を判別(例えばapplication/xhtml+xmlかどうかなど)もしているので、動作が異なってしまう場合があります。

私たちの場合は、内部的な事情で、テンプレートファイルの拡張子に.xhtmlを使用していたため、すべてのContentTypeヘッダがapplication/xhtml+xmlになってしまい、ブラウザのバリデーションエラーで画面が真っ白になるという現象が発生しました。

回避方法は、前者の省略記法を使用せず、後者の従来通りの書き方をすることです。この書き方はHTML5文法としても正式です。

aタグがブロック要素を包含できるようになったのに、うまくいかない

この問題は意外と深かったです。テンプレートに

<a href="javascript:alert('hello');">
       <p>aaa</p>
       <p>aaa</p>
</a>

のように記述すると、実行時に勝手に以下のように書き換えられてしまいます。

<a href="javascript:alert('hello');">
       </a><p><a href="javascript:alert('hello');">aaa</a></p>
<p>aaa</p>

これは、Mayaa依存ライブラリのNekoHTMLが、パースした段階でDOMツリーを再構成してしまうためで、NekoHTMLがHTML5に対応していないため、XHTMLやHTML4以前の仕様に基づいて、DOMツリーを構成してしまいます。

この件について、現時点で一番良い回避法は、
http://ml.seasar.org/archives/mayaa-user/2011-March/000923.html
こちらで対応してくださった方法を使用することです。しかし、この場合は、imgやbrなども含めて必ず閉じタグを書かなければなりません。テンプレートコーディング者が第三者の場合などその徹底が難しい場合は使用が難しいと思います。

NekoHTMLについては、本家MLに問い合わせたところ、最新版でもHTML5への特別な対応はしていないとの返答をいただきました。

最終手段として、私は、NekoHTMLのソースを直接書き換えました。修正箇所は、Mayaaが使用しているNekoHTML-0.9.5をベースとして、

org.cyberneko.html.HTMLElements の、

184:     new Element(A, "A", Element.INLINE, BODY, null),

184:     new Element(A, "A", 0, BODY, null),

に書き換えることです。

NekoHTMLにはテストコードが付属しているので、実行してみたところ、2箇所でエラーになりました。

test:
[tester] Parsing test files and generating output...
[tester] Comparing parsed output against canonical output...
[tester] test36.html:5 strings don't match
[tester] [in: )A]
[tester] [out: (P]
[tester] test50.html:5 strings don't match
[tester] [in: )A]
[tester] [out: (P]
[tester] Finished with errors.

BUILD FAILED

これは、まさに今回の修正の対象だったので、テストケースを修正します。
/data/html/canonical/test36.html
修正前:

(HTML
(BODY
(A
Aname foo
)A
(P
(A
Aname foo
"Blah
)A
)P
)BODY
)HTML

修正後:

(HTML
(BODY
(A
Aname foo
(P
"Blah
)P
)A
)BODY
)HTML

/data/html/canonical/test50.html
修正前

(HTML
(BODY
(A
Ahref foo
)A
(P
(A
Ahref foo
"Blah
)A
)P
)BODY
)HTML

修正後

(HTML
(BODY
(A
Ahref foo
(P
"Blah
)P
)A
)BODY
)HTML

test:
[tester] Parsing test files and generating output...
[tester] Comparing parsed output against canonical output...
[tester] Done.

BUILD SUCCESSFUL

これで、独自にビルドしたnekohtmlのjarをアプリのlibにコピーし、nekohtml-0.9.5.jarを削除すれば差し替え完了です。

Mayaaファイルを命名規則から一括作成するEmEditorマクロ

業務でMayaa使っています。
私が所属する開発チームでは、Mayaaのプロセッサとひもづけるid(m:id)に、一定の命名規則を持たせています。

ルールは以下の4種類しかありません。

  1. その場に値を出力する(m:writeプロセッサ):"〜_HERE"
  2. そのタグの属性を変化させる(m:echo, m:attribute):"〜_TAG"
  3. その要素を条件によって出し分ける(m:if):"IF_〜"
  4. その要素を繰り返す(m:for or m:forEach):"LOOP_〜"

このようにしておけば、仮に〜_HEREがm:writeではなく、m:insertであったとしても、テンプレートを書くデザイナーさんには同じように見えるので、よって上記4ルールで徹底運用しています。

さて、日々の業務を楽にするための努力は欠かしません。名前が規則に従っているなら、プログラムも自動生成できてしまうのではないか?

Mayaaは、テンプレートに定義されていないm:idを記述すると、警告で教えてくれる機能があります。

[WARN] EqualsIDInjectionResolver - the injection ID(IF_*******) is not found on the template, /xxx/yyy/zzz.xhtml#410.

この文字列をコピペして食べさせれば、.mayaaファイルの雛形を生成するEmEditorマクロを作りました。

document.selection.Replace(
"[WARN] EqualsIDInjectionResolver - the injection ID(",
"",
eeFindNext | eeFindReplaceCase | eeFindReplaceEscSeq | eeReplaceAll);
document.selection.Replace(
"\\) is not found on the template.*$",
"",
eeFindNext | eeFindReplaceCase | eeFindReplaceEscSeq | eeReplaceAll | eeFindReplaceRegExp);
document.selection.Replace(
"^.*_HERE$",
"\t<!-- を出力します。 -->\n"
+ "\t<m:write m:id='\\0' value='${}' />",
eeFindNext | eeFindReplaceCase | eeFindReplaceEscSeq | eeReplaceAll | eeFindReplaceRegExp);
document.selection.Replace(
"^.*_TAG$",
"\t<!-- します。属性が変化します。 -->\n"
+ "\t<m:echo m:id='\\0'>\n"
+ "\t\t<m:attribute name='' value='${}' />\n"
+ "\t</m:echo>",
eeFindNext | eeFindReplaceCase | eeFindReplaceEscSeq | eeReplaceAll | eeFindReplaceRegExp);
document.selection.Replace(
"^IF.*$",
"\t<!-- の時にのみ表示します。 -->\n"
+ "\t<m:if m:id='\\0' test='${}'>\n"
+ "\t\t<m:echo><m:doBody /></m:echo>\n"
+ "\t</m:if>",
eeFindNext | eeFindReplaceCase | eeFindReplaceEscSeq | eeReplaceAll | eeFindReplaceRegExp);
document.selection.Replace(
"^LOOP.*$",
"\t<!-- を繰り返します。 -->\n"
+ "\t<m:for m:id='\\0'\n"
+ "\t\t\tinit='${}'\n"
+ "\t\t\ttest='${}'\n"
+ "\t\t\tafter='${}'>\n"
+ "\t\t<m:echo><m:doBody /></m:echo>\n"
+ "\t</m:for>",
eeFindNext | eeFindReplaceCase | eeFindReplaceEscSeq | eeReplaceAll | eeFindReplaceRegExp);

便利です!
自分だけですが。。。

問題なのは会社でEmEditorを愛用しているのが僕だけということです。。。秀丸マクロ?書けません!

Javaによるお気楽なサンドボックス環境の作り方(セキュリティーマネージャ)

Javaで #appengine のようなサンドボックス環境を作る場合、SecurityManagerを使用するのがよさそうです。

http://itref.fc2web.com/java/security.html

具体的には、ポリシーファイルというものを書くという形になります。

(以下は転載)

grant {
permission java.util.PropertyPermission "test_prop", "write,read";
permission java.io.FilePermission "c:/temp/test.txt", "read,write";
//permission java.io.FilePermission "<>", "read,write";
};

しかし、今時のWebアプリなら、実行環境ごとに設定をお願いするのではなく、warをデプロイするだけで、必要な設定はなされていてくれる方がありがたいです。また、policyファイルのような外部ファイルを使うのではなく、純粋にJavaだけで制御すればデバッガもろもろが使えて便利ですよね?

policyファイルを使用しない方法は、java.lang.SecurityManager を独自にオーバーライドすることで実現できます。

http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/SecurityManager.html

そして、こんな感じにします。

public class MySecurityManager extends SecurityManager {
@Override
public void checkExit(int status) {
throw new SecurityException("system exit is not supported.");
}
}
System.setSecurityManager(new MySecurityManager());

しかし、これだと、環境にセキュリティーマネージャが設定されている場合は、その実装が殺されてしまいます。ということは、セキュリティーが考慮された環境でこれを行った場合、ヘタをすると、セキュリティーを低下させてしまいかねません。

※もちろん、よく設定された環境では、セキュリティマネージャの設定自体が許されない可能性もあります。

そう思うと気が重いことです。なかなか手をつけられません。

そこで、既存のセキュリティマネージャを生かしつつ、独自のセキュリティーチェックをJavaだけで実装しようとする場合は、以下のようにすれば良さそうです。

public class NullSecurityManager extends SecurityManager {
@Override
public void checkPermission(Permission perm, Object context) {
// なにもしない
}
@Override
public void checkPermission(Permission perm) {
// なにもしない
}
}
public class MySecurityManager extends NullSecurityManager {
private SecurityManager innerSecurityManager;
public MySecurityManager(SecurityManager innerSecurityManager) {
if (innerSecurityManager == null) {
innerSecurityManager = new NullSecurityManager();
}
this.innerSecurityManager = innerSecurityManager;
}
@Override
public void checkPermission(Permission perm) {
innerSecurityManager.checkPermission(perm);
}
@Override
public void checkPermission(Permission perm, Object context) {
innerSecurityManager.checkPermission(perm, context);
}
@Override
public void checkCreateClassLoader() {
innerSecurityManager.checkCreateClassLoader();
}
public void checkExit(int status) {
innerSecurityManager.checkExit(status);
throw new SecurityException("system exit is not supported.");
}
// 以下あらゆるメcheck系メソッドを委譲
}

あとは、Servletの起動時に

try {
SecurityManager securityManager = System.getSecurityManager();
if (!(securityManager instanceof MySecurityManager)) {
System.setSecurityManager(new MySecurityManager(securityManager));
}
} catch (SecurityException e) {
System.out.println("cannot set my SecurityManager");
}

こんな感じで動きました。