SJC-P受けてきました
本日、SJC-Pの1.5用を受けてきました。
結果は、
正答率: 55 / 72 = 76.3% 基準値: 59%
ということで、合格しました
間違っていたのは、主にSetの扱いまわり。
・TreeSetはComparableを持ったインスタンスしかaddできない。 ・HashSetはhashCode()とequals()が正しく実装されている必要はあるが、Comparableはどうでもいい。つうか、中身はHashMap ・SortedSetはinterface。classではない。使うときはTreeSetを使うこと。 ・Genericで継承関係を構築するなら、extendsかsuperを使うべし。というか、仕事で使っているのになんで間違えるかなぁ…。
他にもいろいろとありそうだけど、とりあえず今日は休みまし
JavamailでEvelope Fromを操作する
だいぶ間が開いてしまいました。
この間、なかなかに大変な案件に付き合っていて、まぁご想像にお任せする状態でしたが…元気です。
おかげでWebSphereのいじり方がだいぶ分かって、一つ成長できた…のかな。
しかし、GUIベースでメンテナンスするのは性に合わないですね。
「手順書」を書くよりも「これ使って」と設定ファイルをポンの方が、正確でトラブル避けられるよなぁ…と。
まぁ、管理上いろいろと良くないから、ダメなのは分かるのですが。
余計な話しついでにもう一つ。
一つ前のエントリーの件ですが、12/2に0.4.154.29で修正されました。
この間、クラッシュにはだいぶ苦しめられましたが…一安心です。
さて、閑話休題。
本題のほうと行きましょう。
ふと、JavamailでENVELOPE FROMを使いたくなりました。
今までならこういうとき、普通にjava.net.Socketを使って直接書いていたわけですが、行儀正しくするとどうなるのかの実験としてです。
しかし、どうすればいいのか。わからなかったので、探ってみました。
簡単にやるならProperty?
とりあえずググってみると、どうやらPropertyで指定可能のようです。
早速試してみます。
まずテスト用に、ローカルにRadish!をインストール。
次にJAF 1.1.1とSunのJavamail 1.4.1リファレンス実装を利用して、下のコードを動かし、動作を確認してみました。
import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.Properties; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.Message.RecipientType; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; public class A { public static void main(String[] args) { try { // サーバ設定 Properties properties = new Properties(); properties.setProperty("mail.smtp.host", "localhost"); Session session = Session.getInstance(properties); session.setDebug(true); // メール本体の提議 MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress("a@example.com", "aaa", "iso-2022-jp")); message.setRecipients(RecipientType.TO, "to@example.com"); message.setSubject("test"); message.setText("this is test mail", "iso-2022-jp"); message.setSentDate(new Date()); // ENV FROMの設定。あえてここでやってみる session.getProperties().setProperty("mail.smtp.from", "env_from@example.com"); // 送信 Transport.send(message); } catch (AddressException e) { e.printStackTrace(); } catch (MessagingException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } }
結果
(略)
EHLO Litchi
250 localhost.localdomain Helo localhost[127.0.0.1], Pleased to meet you.
DEBUG SMTP: use8bit false
MAIL FROM:
250 env_from@example.com... Sender ok
RCPT TO:
250 to@example.com... Recipient ok
(略)
なるほど、確かに。
しかし、これって定まった仕様なのでしょうか。
JSR-919 JavaMail 1.4 Specificationを見ると、このあたりについては
プロパティ値mail.protocol.userあたりの説明で
Specifies the protocol-specific default username for connecting to the Mail server.
とか、setFromの説明として
specifies the sender explicitly from an Address object parameter.
とか書いてあるくらいで、あまり明確には語っていません。
ということは、変更がありそうで不安ですが、行儀正しく行くにはここが限界なのでしょう。
しかし、こんなことでは困るのですが…。
しかもプロパティーじゃ…
しかもこれ、使用上の大きな問題が潜んでいます。
Webサーバ上で実行するとなると、javax.mail.Sessionのインスタンスはスレッド間で共有するかもしれません。
そしてENVELOPE FROMは、FROMに近い性質を持つ都合上、状況に応じて変更したくなるかもしれないと予測できます。
こうなると、Sessionに与えるプロパティでなんとかするのは、他のメール送信スレッドに影響を与える恐れがあるため、少々具合が悪いでしょう。
そもそもENVELOPE FROMは、通常はMimeMessage#setFrom()で与えられた値を見て設定します。
ということは、送信時にそのあたりの選択を行っていると考えるのが妥当でしょう。
ならば、そのあたりを読めば、何か逃げ道があるかもしれません。
ソース、固まってないの?
Javamailのソースコードは、サイトを見ると
pril 19, 2006
JavaMail is now open source! The source code for the JavaMail API Reference Implementation is now available under the CDDL open source license, as a part of Project GlassFish See the mail module page at GlassFish for more details.
とあるとおり、今はGlass Fishプロジェクトにあるようです。
そこで、Glass FishのJavamailのページを見てみると…
ん〜。アーカイブ化されたものはみあたりません。
探し方が悪いのかしら。
仕方なし、cvsからcheck outして取ってきます。
…どっかに固まってないですかねぇ…。
読んでみる
早速読んでみます。
まず、送信時に叩くjavax.mail.Transportですが、どうやらこれ、Sessionオブジェクトから作成されたインスタンスに処理を委譲していて、結局のところSunのJava実装だとcom.sun.mail.smtp.SMTPTransportにその定義がありました。
その中でEnvelope Fromをsetしているのは、983行目から始まるmailFrom()の中にありました。
protected void mailFrom() throws MessagingException { String from = null; if (message instanceof SMTPMessage) from = ((SMTPMessage)message).getEnvelopeFrom(); if (from == null || from.length() <= 0) from = session.getProperty("mail." + name + ".from"); if (from == null || from.length() <= 0) { Address[] fa; Address me; if (message != null && (fa = message.getFrom()) != null && fa.length > 0) me = fa[0]; else me = InternetAddress.getLocalAddress(session); if (me != null) from = ((InternetAddress)me).getAddress(); else throw new MessagingException( "can't determine local email address"); } String cmd = "MAIL FROM:" + normalizeAddress(from);
SMTPMessageならばgetEnvelopFromを取る
↓
でなければPropertiesから"mail." + name + ".from"をキーにして取る
↓
なければfromからアドレスを選択する。
↓
なければlocal addressを作成する。
という流れのようです。
このnameというのがどこからきているのかというと、プロトコル名称のようです。
つまり、smtpなら mail.smtp.from となるわけなので、結果「(通常は)mail.smtp.from に設定すればEnvelope Fromを制御できる」という言説になるのでしょう。
当然、smtpsならmail.smtps.fromになるわけですね。
さて、本題に戻ると、SMTPMessageを使えばSessionに影響を与えずにセットできそうということが分かりました。
早速試してみます。
import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.Properties; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.Message.RecipientType; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import com.sun.mail.smtp.SMTPMessage; public class D { public static void main(String[] args) { try { // サーバ設定 Properties properties = new Properties(); properties.setProperty("mail.smtp.host", "localhost"); Session session = Session.getInstance(properties); session.setDebug(true); // メール本体の提議 SMTPMessage message = new SMTPMessage(session); message.setFrom(new InternetAddress("a@example.com", "aaa", "iso-2022-jp")); message.setRecipients(RecipientType.TO, "to@example.com"); message.setSubject("test"); message.setText("this is test mail", "iso-2022-jp"); message.setSentDate(new Date()); // ENV FROMの設定 message.setEnvelopeFrom("env-from@example.com"); // 送信 Transport.send(message); } catch (AddressException e) { e.printStackTrace(); } catch (MessagingException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } }
結果
(略)
EHLO Litchi
250 localhost.localdomain Helo localhost[127.0.0.1], Pleased to meet you.
DEBUG SMTP: use8bit false
MAIL FROM:
250 env-from@example.com... Sender ok
RCPT TO:
250 to@example.com... Recipient ok
(略)
うまくいきました。
しかしこれって…
しかし、このSMTPMessageですが、あまり使うのはよくないかもしれません。
一つにはcom.sun.mail.smtpというパッケージ内に配置されている事が示す問題で、将来変更されるかもしれないし、排除されるかもしれません。
二つには、そのようなクラスを利用していることから分かるとおり、非常に実装依存な話であり、他の実装を利用せざるを得なくなった場合には使えない話と化します。
まぁ、普通javaでmailといえばSunのJavamailだよね〜…って言い切れると思うので、いいのかもしれませんが…。
更に言うと、JavamailのSessionはsmtp専用ではないため、EnvelopeFromの追加を提案するのもどうかと思いますし、たぶんSunのJavamailの実装者はソコを気にしてこういう実装にしただろう事は想像に難くありません。
はて、となると、どうすればいいのでしょう。
悩ましいものです。
Google Chrome 0.4.154.25
Google Chromeを0.4.154.25にしたところ、変なタイミングで意図しないタブのドラッグが起こるようになり、ついでにドロップ時にクラッシュするようになった。
再現性は100%ではなく、何のタイミングで起こるかまだはっきりと分かっていもので、ん〜。不便。
私の手元の環境だけなのだろうか。
とりあえず、報告は送ってみたのだけど、さてどうなることやら。
しかし問題はそれだけではなく、一つ前に戻そうと思っても古いバージョンをダウンロードをする方法が見つからない事が痛い。
どうしたらいいのだろう。
URLをResource経由で取得
最近は何かとSOAP通信が要求される。
これが単一のサーバ相手だったりするといいのだけど、当然テストと本番が分かれている。
とうぜん手元もテストと本番で分かれているわけだから、テスト機で通信するのか、本番機で通信するのか、はたまたローカルマシンで通信するのかで、接続先を変える必要がある。
普通のプログラマならば「なら設定ファイルで持っておけばいいじゃない」と発想してJavaならpropertiesファイルに接続先を突っ込むのだそうだ。
# と、言われたのだが、本当だろうか。俄かには信じがたい。
しかし待ってくれ。
その設定ファイル、テストのwarと本番のwarの両方に入らなきゃならないわけだけど、テストから本番に持っていくときに書き換えるのかい?
それはありえないだろ。
JavaのAPサーバなら、大抵そういう「リソース」を管理する仕組みがあるんだから、それに一丁頼ってみるのが、この場合よさそうじゃないか。
そこで、java.net.URLをtomcatのserver.xmlにResourceで登録してうまく動かないものかやってみた。
流れを読んでみる
まずResourceエレメントはどのように解釈されるのだろう。
Resourceエレメントが指定されると、tomcatではcatalinaエンジンがjavax.naming.Referenceのオブジェクトを作成する(ここが本当にCatalinaなのかは追いきれて居ない。ん〜)
そして、ResourceParams のオブジェクトをEnumerationとして、Referenceの中に入れる。(っぽい)
ResourceParams で factory があったら、値をクラス名としてインスタンスをリフレクションで作成し、ObjectFactoryにキャストする。
で、getObjectInstanceを呼んで帰ってきたオブジェクトを渡す…。
とまぁ、こんな流れっぽい。
ということは、適切なObjectoFactoryを使用すれば良さそうだ。
Bean標準でなんとかしてみる
そこで、java.net.URLに適合するFactoryを探してみた。
そのまま直に対応しているものはなかったのだが、次点ということでorg.apache.naming.factory.BeanFactory が目に付いた。
これはBeanInfoを受け取ってオブジェクトを作成する物。
保持するオブジェクトのクラスが、BeanInfoインターフェースを実装している事を期待している。
となると、java.net.URLのサブクラスを作り、BeanInfoインターフェースを適合すればよいということになる。
まずはスケルトンとばかりに、早速作ってみた。
C:\>copy con A.java import java.net.URL; class A extends URL { } ^Z 1 個のファイルをコピーしました。 C:\>c:\Develop\Language\j2sdk1.4.2_08\bin\javac.exe A.java
A「なぁ兄弟。いまどきJava1.4のプロジェクトってどう思う?」
B「そりゃとびっきりのジョークだ!」
そう、ジョークだったらよかったと思うよ…兄弟。
A.java:2: final java.net.URL からは継承できません。 class A extends URL { ^ A.java:2: シンボルを解決できません。 シンボル: コンストラクタ URL () 場所 : java.net.URL の クラス class A extends URL { ^ エラー 2 個 C:\>
まぁ、なんですか。
分かりやすく言うとURLクラスはfinalなので継承できないってことですね。
この方向はあえなく失敗。
Bean標準でなんとかしてみる
BeanInfoを実装する方向ではなくするとなると、ObjectFactoryを実装するしかない。
そっちからなら単純にできるのだけど、キット汎用性は無いよなぁ…。
というわけで、org.apache.naming.SendMailFactoryを参考にして書いてみる。
import java.net.URL; import java.util.Hashtable; import javax.naming.Context; import javax.naming.Name; import javax.naming.RefAddr; import javax.naming.Reference; import javax.naming.spi.ObjectFactory; public class URLFactory implements ObjectFactory { public Object getObjectInstance(Object refObj, Name name, Context context, Hashtable env) throws Exception { Reference ref = (Reference) refObj; if (!ref.getClassName().equals("java.net.URL")) { return null; } RefAddr attr = ref.get("url"); if (attr == null) { return null; } return new URL((String) attr.getContent()); } }
これをjarにして $CATALINA_HOME/common/lib につっこみ、server.xmlに対象リソースを記述。
<Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Connector port="8080" /> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost"> <Context path="/" docBase="foobar" reloadable="true"> <Resource auth="Container" name="url" scope="Shareable" type="java.net.URL"/> <ResourceParams name="url"> <parameter> <name>factory</name> <value>URLFactory</value> </parameter> <parameter> <name>url</name> <value>http://example.com</value> </parameter> </ResourceParams> </Context> </Host> </Engine> </Service> </Server>
で、立ち上げるとJNDIリソースが出来上がっているはず。
確認のため、これをServletから呼び出してみる。
public class TestServlet extends HttpServlet { protected void service(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException { try { Context context = new InitialContext(); URL url = (URL) context.lookup("java:comp/env/url"); System.out.println(url); } catch (NamingException e) { e.printStackTrace(); } } }
web.xmlも作成してwarにしてdeployする(ここは省略させてくだしあ)。
いや、warにしなくてもいいのですけど…。
実際にアクセスしてみる
準備が全て整ったところでアクセス。
対象のtomcatは5.0.28。なんでそんなバージョンかって?
キカナイデクレ。
2008/10/28 00:35:54 org.apache.coyote.http11.Http11Protocol init 情報: Coyote HTTP/1.1をポートhttp-8080で初期化します 2008/10/28 00:35:54 org.apache.catalina.startup.Catalina load 情報: Initialization processed in 0 ms 2008/10/28 00:35:54 org.apache.catalina.core.StandardService start 情報: サービス Catalina を起動します 2008/10/28 00:35:54 org.apache.catalina.core.StandardEngine start 情報: Starting Servlet Engine: Apache Tomcat/5.0.28 2008/10/28 00:35:54 org.apache.catalina.core.StandardHost start 2008/10/28 00:36:02 org.apache.catalina.core.StandardHost getDeployer 情報: Create Host deployer for direct deployment ( non-jmx ) 2008/10/28 00:36:02 org.apache.coyote.http11.Http11Protocol start 情報: Coyote HTTP/1.1をポート http-8080 で起動します 2008/10/28 00:36:02 org.apache.catalina.startup.Catalina start 情報: Server startup in 0 ms http://example.com
ということで、無事、リソースの取得に成功した。
某氏向けのちょっとした実験
class A { private int i; public A(int i) { this.i = i; } protected void finalize() { System.out.println("finalize![" + this.i + "]"); } public static void main(String[] args) { new A(0); System.gc(); System.out.println("GC!"); new A(1); } }
finalize![0] GC!
要するにSystem.exit(int)すると、GCが省略されるのでfinalize()が呼ばれないのではないかと。
いや、仕様書読んでないから予測でしかないわけですが。
認定証が届きました
先日、Ruby Association Certified Ruby Programmer Silverに合格したと書いたわけですが、月曜日に認定証が届きました。
ちなみに折れ曲がっているように見えるのは…輸送中の事故のようですorz
# 写真がなぜかモザイクかける前のものに戻っていたので、一旦写真だけ削除します
Inner Classのstatic有りと無しの違い
昨日、結婚式の招待状が届きました。
古い知人のモノで、当然行く予定。
日取りは10/19だそうで。
…って10/19って、情報処理技術者試験の平成20年度秋期試験期日じゃないか。うう。
ということで、あきらめて…悔しいので10月にSJC-Pあたりをとろうと考えてみました。
大体、いまだに取ってないのもアレだし。
そうすると、やはり細かいところが気になってきたわけで…。
class内classの種類
インナークラスはメソッド内に名有りで書いたり、名無しでインスタンス作成のために書いたりというのもあるけど、ここでは差が分かりにくいstaticなクラス内クラスとstaticではないクラス内クラスを比べます。
staticなクラス内クラスとは、
class A { static class X { } }
のXのように、staticで修飾されているクラス。
逆にstaticでないクラス内クラスとは、
class A { class Y { } }
のYのように、static修飾されてないクラスの事です。
こうすると、クラス.内部クラス というようにアクセスできるわけですが、面白いことにYのクラスでも同じようにアクセスが可能です。
C:\Develop>copy con A.java class A { static class X { } class Y { } void method() { A.X x = new A.X(); A.Y y = new A.Y(); } } ^Z 1 個のファイルをコピーしました。 C:\Develop>Language\jdk1.6.0_07\bin\javac.exe -g:none A.java C:\Develop>dir ドライブ C のボリューム ラベルがありません。 ボリューム シリアル番号は 3574-3602 です C:\Develop のディレクトリ 2008/09/20 00:52 <DIR> . 2008/09/20 00:52 <DIR> .. 2008/09/20 00:52 160 A$X.class 2008/09/20 00:52 207 A$Y.class 2008/09/20 00:52 258 A.class 2008/09/20 00:52 129 A.java 2008/08/27 15:35 <DIR> IDE 2008/09/20 00:11 <DIR> Language 2008/09/17 07:13 <DIR> Server 4 個のファイル 754 バイト 5 個のディレクトリ 160,092,176,384 バイトの空き領域 C:\Develop>
とまぁ、こうしてみると普通に成功します。
ちなみにコンパイルのオプションには、今回の解析で使いやすいようにデバッグ情報は削除しておきました。
そこで中身
クラス内クラスは、「親クラス$子クラス.class」というようなファイル名で出力されます。
見てのとおり、A$X.classとA$Y.classではファイルサイズが違うことから、中身も違うのは想像できるでしょう。
じゃぁ、javapで中を覗いてみるとします。
C:\Develop>Language\jdk1.6.0_07\bin\javap.exe -verbose A$X class A$X extends java.lang.Object InnerClass: #10= #2 of #8; //X=class A$X of class A minor version: 0 major version: 50 Constant pool: const #1 = Method #3.#7; // java/lang/Object."<init>":()V const #2 = class #9; // A$X const #3 = class #12; // java/lang/Object const #4 = Asciz <init>; const #5 = Asciz ()V; const #6 = Asciz Code; const #7 = NameAndType #4:#5;// "<init>":()V const #8 = class #13; // A const #9 = Asciz A$X; const #10 = Asciz X; const #11 = Asciz InnerClasses; const #12 = Asciz java/lang/Object; const #13 = Asciz A; { A$X(); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return } C:\Develop>Language\jdk1.6.0_07\bin\javap.exe -verbose A$Y class A$Y extends java.lang.Object InnerClass: #14= #3 of #12; //Y=class A$Y of class A minor version: 0 major version: 50 Constant pool: const #1 = Field #3.#10; // A$Y.this$0:LA; const #2 = Method #4.#11; // java/lang/Object."<init>":()V const #3 = class #13; // A$Y const #4 = class #16; // java/lang/Object const #5 = Asciz this$0; const #6 = Asciz LA;; const #7 = Asciz <init>; const #8 = Asciz (LA;)V; const #9 = Asciz Code; const #10 = NameAndType #5:#6;// this$0:LA; const #11 = NameAndType #7:#17;// "<init>":()V const #12 = class #18; // A const #13 = Asciz A$Y; const #14 = Asciz Y; const #15 = Asciz InnerClasses; const #16 = Asciz java/lang/Object; const #17 = Asciz ()V; const #18 = Asciz A; { final A this$0; A$Y(A); Code: Stack=2, Locals=2, Args_size=2 0: aload_0 1: aload_1 2: putfield #1; //Field this$0:LA; 5: aload_0 6: invokespecial #2; //Method java/lang/Object."<init>":()V 9: return } C:\Develop>
差異を見てみると、static修飾をされていないとthis$0の定義が走り、字面上は存在しない形でデフォルトコンストラクタの引数が一つ増えている。
enclosingしてみる
ということは、enclosingアクセスできるかどうかが違うということだろうか。
C:\Develop>copy con B.java class B { private int i = 10; class Y { private int x = B.this.i; } } ^Z 1 個のファイルをコピーしました。 C:\Develop>Language\jdk1.6.0_07\bin\javac.exe B.java C:\Develop>copy con C.java class C { private int i = 11; static class X { private int x = C.this.i; } } ^Z 1 個のファイルをコピーしました。 C:\Develop>Language\jdk1.6.0_07\bin\javac.exe C.java C.java:4: static でない 変数 this を static コンテキストから参照することはできま せん。 private int x = C.this.i; ^ エラー 1 個 C:\Develop>
なるほど。enclosingアクセスができるように初期化するコストが、static修飾のないクラス内クラスには必要になるということですね。
スコープが違う?
さて、そうなるとこのクラス内クラス、スコープが気になります。
C:\Develop>copy con D.java class D { class Y { } static void method () { F.Y y = new F.Y(); } } ^Z 1 個のファイルをコピーしました。 C:\Develop>Language\jdk1.6.0_07\bin\javac.exe D.java D.java:5: シンボルを見つけられません。 シンボル: クラス Y 場所 : F の クラス F.Y y = new F.Y(); ^ D.java:5: シンボルを見つけられません。 シンボル: クラス Y 場所 : F の クラス F.Y y = new F.Y(); ^ エラー 2 個 C:\Develop>copy con E.java class E { class Y { } } class ESub { void method() { E.Y y = new E.Y(); } } ^Z 1 個のファイルをコピーしました。 C:\Develop>Language\jdk1.6.0_07\bin\javac.exe E.java E.java:7: E.Y を含む囲うインスタンスが必要です。 E.Y y = new E.Y(); ^ エラー 1 個 C:\Develop>copy con F.java class F { static class X { } static void method() { F.X x = new F.X(); } } class FSub { void method () { F.X x = new F.X(); } } ^Z 1 個のファイルをコピーしました。 C:\Develop>Language\jdk1.6.0_07\bin\javac.exe F.java C:\Develop>
static修飾されないと、staticでないとアクセスできない場所からはアクセスできないらしい。
そりゃそうですよね。
つまるところ
つまり、static修飾されていないと、
- enclosingアクセスできるが、そのために初期化コストがかかる
- クラスの外部からのインスタンス作成を標準で抑制できる
というところが違うってことですか。
後者は私が文法を知らないだけの可能性があって、ちょいと微妙ですが…。
まぁ、リフレクション使えばイケそうな気がします。