yamicha.com's Blog - Presented by yamicha.com
Blog yamicha.com's Blog - 2018/10 の記事
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19] [20] [21] [22] [23] [24] [25] [26] [27] [28] [29] [30] [31]

yamicha.com's Blog
 諸事情により、現在更新休止中。ご了承ください。もし今後ブログを再開することがあるとすれば、その際にはこのブログスクリプトではなく、新しく開発したものによるかもしれません。
 当ブログ管理者についてはこちらをご参照。
開発魔法(737)
社会問題(733)
お知らせ(11)
質問・バトン回答(15)
ゲスト出演(8)
経済・知的財産(150)
ゲーム開発(182)
[Ada] 勝手に補足
- Note
- 金配りの次の一手


- Endless Ultimate Diary
- 銃世界

漢字バトン
- うるる雑記帳
- 漢字接力棒

ツキアイゲノムバトン
- ブログ@うにうに画像倉庫
- あぶ内閣

縺イ縺セ縺、縺カ縺励ヰ繝医Φ
- 月夜のボヤキ
- 騎士サーラバトン
パスワードを使う
名無し (2012/02/27)


開発者解放裁判
yamicha.com (2010/03/14)
Winnyに関しては、私も「純白」とまでは考えておりませんし、使用し..

開発者解放裁判
通りすがり (2010/03/08)
winnyに関しては「ダウンロードソフト板」なんてところを拠点に開発..

新型インフルエンザの恐怖
いげ太 (2009/11/03)
> C#などの「int Some(int , int)」は、F#では「(int * int) ->..

時効に関する思考
yamicha.com (2009/08/31)
>いげ太さんコメントありがとうございます。手元にドキュメントが少..
Homepage
Blog Top
Normal View
List View
Search
Calendar
Comment List
Trackback List
Blog RSS
Send Trackback
Phone Mode
Administrator
yamicha.com
Blog
るううるる。
Source
法令データ提供システム
FindLaw
Development
Java2 Platform SE 6
Java EE 6 API
MySQL Developer Zone
PHP Reference
MSDN Library
Ada Reference Manual
Objective Caml
Python Documentation
Erlang
Prolog Documents
カテゴリ表示
 カテゴリ 経済・知的財産 に当てはまるもののみを表示します。

 存在する記事 150 件の中から 36-40 件を表示しています。
大阪・吹田脱線事故
2007/05/08(Tue)19:43:20
 ジェットコースター事故。色々批判も出ているようですが、このようなことは事故の後だから言えるのです。まさに「後悔先に立たず」といいましょうか。
 パーク側の行為に関しては、「検査を連休後に予定していた」ことについて批判を受けているようですが、これは少々不毛な批判になりかねません。以前、JR事故の際に「列車が軽量なスチールではなく鋼鉄でできていれば、あそこまで潰れることはなかった」などといった意見がありましたが、実際にあの列車が鋼鉄でできていれば、下手すると列車が突っ込んだマンションが崩壊した恐れがありました。「連休後の検査」が批判されていることについても、「スチールか、鋼鉄か」と似たような議論になる恐れがあります。
 機械は当然、使用することによって磨耗します。連休中にかなりの磨耗が起こる可能性は十分にあります。仮に連休前に検査をしておいて、連休で機械が予想以上に減耗し、母の日辺りで事故を起こしたらどうなっていたでしょうか。我々は「連休後に検査をしないとは何たること」とパークを非難していたのではないでしょうか。
 ただ、いい加減な検査報告を行っていた問題については批判されるべきです。JISで定められた必要な検査(探傷試験)を行うことなく、建築基準法に基づく報告内で「台車・車輪装置」を「A」評価にしていたというではありませんか。危機管理の観点からすれば、このようなずさんな報告を続けている限り、そのうち必ず事故が起きます。
 ちなみに、これに対するパーク側の主張は「今回の事故で折れた車軸は、JISの検査で定められた車軸ではない」とのこと。要するに、正しく検査を行っていたとしても今回の事故は防げなかったと言いたいようです。しかし、今回の事故が防げるかどうかにかかわらず、虚偽の報告を出していた行為自体が問題なのであり、この虚偽報告と事故の間に因果関係がなかったとしても、虚偽報告した責任は免れません。
 しかし実際のところ、遊具の検査にはどの程度の信頼性があるのでしょうか。故意か過失かにかかわらず、似たような報告をしている運営会社は必ずしも少なくないと考えられます。そして、仮にそのような報告が行われていたとしても、実際のところは誰にも分からないのです。「事故が起こっていない」遊園地やアトラクションについても、安全対策が万全で事故が起こっていないのか、偶然事故が起こっていないだけなのか、誰にも判断できません。さらに言えば、どれほど安全対策に尽力していても、事故を100%起こさないことは不可能です。
 ともかく、安全管理がなされたアトラクションに乗車する行為であっても、利用者がリスクを負うことはどうしても免れないといったところでしょうか。絶対安全など存在しないのですから。それなら最初からそんなものに乗らない方が良いではないかと言うなかれ、プールに行くのであっても、釣りに行くのであっても、エレベーターに乗るのであっても、散歩をするのであっても、ごく普通のブランコや滑り台であっても、果ては読書であっても、それ相応のリスクがあります。ことさら「遊園地は危ない」と強調できるような問題ではありません。
 鉄道や飛行機、自動車の事故に比べれば、アトラクションやプールなどといったリスクは非常に低い部類に入るでしょう。そうでなければ、命がけでそうしたものを利用する人はほとんどいないものと考えられます。
 ただ、日常で利用の機会が多い自動車や鉄道、十分検査されたアトラクションなどであっても、リスクは必ず存在するのです。それをいちいち気にしていては何もできませんから、「リスクがゼロの近似値である」ことを多くの人がリスクゼロとみなして行動しているだけに過ぎません。実際、列車の場合なら何万本、何十万本に1本という確率で事故が起きますし、1億3000万人のうち年8000人ほどが車の事故で亡くなります。
 だからといって事故が起こって良いとは言いませんが、問題は事故後です。JR事故の場合のように、社員が休暇でボウリングに行ったことまでウジウジ追求するようなことはせず、本当に事故の原因になったような不適切な行為のみをピンポイントで追及するようにすべきでしょう。トンチンカンなことばかり追求されるようでは、本当に事故の原因となった「要点」がかすんでしまい、事故が繰り返されるリスクを減らせません。

 フランスでサルコジ氏がロワイヤル氏を破る。サルコジ氏は競争路線を掲げており、弱者保護路線のロワイヤル氏は及ばなかった形です。さすがに日本のように「サルコジ氏しか名前を知らないからサルコジ氏にします」のようなふざけた投票理由はないでしょうから、これがフランス国民の決定なのでしょう。島国日本とは違い、周囲に多くの国があったり、EUのような共同体があることから、サルコジ路線が適切と判断されたのかもしれません。
 毎日新聞は社説で「格差社会は日本でも問題だ。フランスの選択が成功するか注視したい」と述べていますが、正直言って日本の参考にはなりにくいでしょう。フランスは週35時間労働制、かたや日本には有名無実の制限しかない上、下手すると残業分の賃金が不要なのです(今のところ違法ですが、かなり黙認されています。さらに、これを合法化しようという動きが極めて活発で、安倍内閣も合法化をもくろんでいます)。
 外交面では、サルコジ氏は米国との協調路線とのこと。この点で印象深いのが、独で親米路線を掲げたメルケル氏です。独に加えて仏も協調路線に動いたことになります。その米国もイラク戦争ではだいぶ参ったようですし、政権も民主党に移る可能性が高くなっています。そうしたことを見越しての上でしょうか。それとも、国民が「やはり米国との協調が必要」と考えた結果でしょうか。
 とにかく、ここまで保護路線が重視されていた仏で、国民が大きな路線転換を選んだ意味は大きいです。かなりの賭けといいましょうか。要するに、これから数年間のサルコジ路線の評価が、仏における自由主義の評価に直結するわけです。次の選挙では保護主義と自由主義のどちらが勝つのか、自由主義を掲げる候補はいなくなっているのか、はたまた自由主義候補が主流になっているのか。数年も後のことながら、今から気になっています。
 こうした対立軸による明確な選挙が日本でも行われれば面白いのでしょうが、日本を見てみれば「首相、靖国に供物奉納」。これを問われた政府は「ノーコメント」。何でも「参拝賛成派への配慮」らしいですが、要するに支持や票が欲しいのでしょう。首相のこのような行為1つで支持や投票先を定めたりする日本国民とは一体。果てはインターネットで政党を根拠なく中傷し、政党ばかりかインターネットの信頼性を貶める最低な人間もいるようです。日本がこれだから、外国の選挙が面白いのでしょうか。

 後は経済関係をいくつか。まずMSとYahooの合併交渉ですが、例によってあまり良い方向には進んでいないようです。Googleに対抗するために合併しようと考えてはみたものの、目指すものがあまりに違いすぎるといったところでしょうか。とはいえ、仮に目指すものが同じであるにしても、この交渉がすんなりまとまるわけがありませんが。
 かく言う私はGoogleを主に使っており、Yahooなどいまやめったに使いませんが、これは「なるべくしてなった」ものでしょう。シンプルで正確性が高く使いやすい上、キャッシュなどの以前から欲しかった機能も充実しており、これを体験すればもうYahooなど使ってはいられません。1度Googleに触れて以降は、もう他の検索エンジンは使えなくなりました。
 しかし正直、Googleは検索エンジンとしては好きなのですが、私は最近のGoogleには良い印象を持てなくなってきています。言うなれば「検索業界のMSと化している」のです。具体的には、Googleが広く広告サービスを始めたころから、少しずつおかしなことになってきたような印象があります。あれだけの企業ともなれば広告収入が重要なのは事実でしょうが、スポンサードリンク辺りならまだしも、ページ内容に応じた広告には辟易させられます。しかもその内容が明らかに詐欺、あるいは不正としか考えられないものであったりして、正直Googleの出してくる広告は信用なりません。
 さらに、最近のGoogleは「Web 2.0」とやらに力を入れているようです。このせいで、かつてのGoogleのウリであった「シンプル」がみるみるうちに破壊されてきています。検索フォームが1つだけあり、それにワードを打ち込めば検索できる、というだけで利用者は満足なのです。これはさすがにあり得ないと信じたいですが、仮にWeb検索までAjaxを使ったようなWeb 2.0的なものになれば、私はもうGoogleを捨てるでしょう。
 国内では、これは取り上げるべきか迷いましたが、話のタネとして。回転寿司の「びっくり本舗」が「マリンポリス」を子会社化することになったようです。外食産業の動きとしては面白いのですが、さらに面白いのはマリンポリスグループが手がける店舗の名称です。「シージャック」(またはひらがなで「しーじゃっく」)というのです。
 シージャックといえば「船乗っ取り」。果たしてマリンポリスがシージャックなどして良いのでしょうか。それにしても、以前のすかいらーくの例といい、ファミリーレストラン系は特に少子化のあおりをまともに受けるようです。紳士服産業は団塊退職の影響をまともに受けるようですし、こういう動向を見ているだけでもなかなか面白いものです。

 MSといえばソフトウェア業界のマンモス、恐竜ですが、GoogleはMSと争う姿勢のようですし、SunのJava陣営も同様です。Java陣営にはIBM、オラクルなど様々な有力企業が参加しています。TopLink APIを提供しているのはオラクルですし、Java DBとしてJava SE 6に搭載されているソフトウェアはApache Derbyであり、実はこれはIBMによって寄贈されたCloudscapeなるデータベースが元になっています。巨大なMSに対し、様々な有力企業が規格やコミュニティを作成し、争っている図式です。
 Javaの良いところといえば、何といっても透明性でしょう。何らかの案が策定されるとその仕様が公開され、誰でもそれに基づいた実装を作ることができます。複数のベンダーがそれを実装しているのであれば、好きなものを選ぶことができます。MSのような1社による規格でこうしたことを望むのはなかなか難しいのです。例えば、おそらく.NET製品はMSのものがほとんどでしょうが、Java EEならglassfish/Sun、Oracle、IBM、Bea WebLogic、Novel、JBoss、Tomcatなど様々なもので動かすことができるようです。その上glassfishやTomcatはオープンソースというオマケ付き。これはもう、私がJava好みなのは当然といいましょうか。
 それで、Googleが重視しているのが「Web 2.0」。果たしてこのようなものがWebを変えてくれるのでしょうか。こうしたことを理解するには、実際に作ってみるのが何よりも重要です。Ajaxによって「それらしい」ものを作ってみるとしましょうか。
 仕様としましては、

1.上部にタブを設け、タブをクリックするとページ変移なしでそのタブの内容を表示。タブの内容はタブがクリックされるたびに読み込まれなければならない
2.タブがクリックされると、一旦タブ以下の部分が収縮し、再び展開されて表示される
3.詳細を表示する際には、ウィンドウが展開されて表示される。ただし、新しいウィンドウを開くのではなく、同一画面上でページ変移なしに行わなければならない

 色々条件をつけましたが、果たして何とかなるのでしょうか。
 まず2番から考えてみます。これは要するに「滑らかに開閉するメニュー」とでも表現できるでしょうか。タブをクリックした瞬間に内容が変わったり、いきなり内容が閉じた後で開いたりするのではなく、スライドするように閉じた後、スライドするように開く、というわけです。
 方法が決まれば後は実装することになりますが、例えば単純にこのようなプログラムではどうでしょうか。
<html>
<head>
<title>Test</title>
<script language="JavaScript">
var open_motion = 0;
var open_animation = 25;	// アニメのフレーム数
var size_max = 100;	// 最大サイズ
var size_left = 10;	// 最小サイズ
var open_wait = 15;	// アニメーションの待機時間
var move_vector = 0;
function motion(){
	open_motion = open_animation;
	move_vector = -1;
	tableMotion();
}
function tableMotion(){
	if(open_motion <= open_animation && open_motion >= 0){

		// 大きさを変更
		var height = Math.round(open_motion * 
			(size_max - size_left) / open_animation) + size_left;

		var content = document.getElementById("content");
		content.height = height + "%";

		if(open_motion <= open_animation || move_vector < 0){
			if(open_motion == 0)
				move_vector = 1;
			setTimeout('tableMotion()' , open_wait);
		}
		open_motion += move_vector;
	}
}
</script>
</head>

<body>

<form onSubmit="motion();return false">
<input type="submit" value="motion">
</form>

<table border="0" width="75%" height="100%" align="center" id="content">
<tr><td bgcolor="#CCDDFF"></td></tr>
</table>

</body>
</html>
 これを実行すると、確かにテーブルが閉じて開きます。100%(画面全体)の高さから、10%まで縮小した後、再び100%になります。しかし、動くには動くのですが、どうも「角ばっている」印象があるのです。ウィンドウが小さければそれほどでもないのですが、ウィンドウが大きいと不自然さが目立ちます。フレーム数を多くすれば不自然さは減少しますが、モーションに時間がかかるようになります。一体どうしたものでしょうか。
 まず考えたのが三角関数です。要するに、開いた状態が「p = 1 - COS(PI / 2)」であり、閉じた状態が「p = 1 - COS(0)」であると考えます。すると、当初はかなり勢いよく縮小するものの、最終的にはスピードが落ちるような閉じ方になることが想像できます。
 この方法による実装は次の通りです。
var height = Math.round((1 - Math.cos(Math.PI / 2 * open_motion / 
	open_animation)) * (size_max - size_left) + size_left);
 ありがたいことに、これでなかなか滑らかにスライドしてくれるようになります。三角関数は習得しておくと便利です。
 しかし、人間とは欲の尽きない生き物です。確かにスライドの不自然さはなくなりましたが、今度はどうもスライドに爽快さがないような。もっと良い方法はないものでしょうか。
 そこでさらに考えた結果が「定率法」でした。定率法は減価償却の手段で、始めの方に多額の償却を行い、年を追うごとに償却額が減少していく償却方法です。陳腐化が進みやすく、価値の暴落が激しい機械類などに有効とされています。
 それで、なぜそのような概念がJavaScriptの実装で出てくるのか。グラフでご覧いただきましょう。



 この通り、スマートなグラフとなっています。なかなか自然にスライドできるのではないでしょうか。定率法の概念を用いた実装は次のようになります。
var height = Math.round(size_max * Math.pow(size_left / size_max , 
	(open_animation - open_motion) / open_animation));
 縮小及び展開のアニメーションにはこれを使うとしましょう。
 後の条件ですが、1番はAjaxで実装できます。3番は本当にウィンドウを使おうとすれば何ともなりませんが、レイヤーを使うことで何とかなります。これらを上手く使用すれば、Flashに迫るほどの表現を行うことができそうです。
 こうしたことを踏まえ、次のようなプログラムを考えてみます。

・皆様よりご提供いただいた画像の閲覧JSP
1.作者名のタブをクリックすると、そのタブが点滅し、テーブルが収縮して展開するアニメーションの後、Ajaxによって画像一覧が読み込まれて表示される
2.画像をクリックすると、クリックした箇所からレイヤーの枠線が展開されるエフェクトの後、レイヤー内でAjaxによって画像の詳細が読み込まれて表示される
3.レイヤー内には「さらに詳細を表示」及び「レイヤーを閉じる」リンクを置く。詳細の表示は新しいウィンドウによって行い、「レイヤーを閉じる」がクリックされたら即座にレイヤーを閉じる

 以下、私が行った実装です。ただし、最初に書いておきますが、以下のJavaScriptの実装はあくまでJavaScriptがお遊び用の言語だからこそ行っているものであり、プログラミングの手本にはしないでください。
<%@ page import="javax.servlet.http.* , 
java.sql.* , javax.sql.* , javax.annotation.*" 
contentType="text/html; charset=Shift_JIS" %>

<%!
@Resource(name="jdbc/MySQL") private DataSource ds;
%>

<%
Connection c = ds.getConnection();
request.setCharacterEncoding("UTF-8");

String mode = request.getParameter("mode");
if("content".equals(mode)){
 int type = 0;
 try{
  type = Integer.parseInt(request.getParameter("number"));
 }catch(NumberFormatException e){
 }
%>
<table border="0" cellspacing="6">
<tr><td>
<%
 if(type == 0){
%>
<b>Web 2.0 Test Home</b><br>
 これは「Web 2.0」と呼ばれる「Web 革命現象」の真実を検証するための 
JSP + Ajax スクリプトです。<br>
 上記のタブよりカテゴリを選択すると、
ここにそのカテゴリの内容が表示されます。<br>
<br>

<b>Recent Contents</b><br>
<%
  try{
   PreparedStatement ps = c.prepareStatement(
    "SELECT c.number , c.title , c.thumb , t.number AS 'tabnumber' , t.name " + 
    "FROM web2_types t INNER JOIN web2_contents c ON t.number = c.type " + 
    "ORDER BY number DESC LIMIT 5");
   ResultSet rs = ps.executeQuery();

   while(rs.next()){
    int number = rs.getInt("number");
    String title = rs.getString("title");
    String thumb = rs.getString("thumb");
    String name = rs.getString("name");
    int tabnumber = rs.getInt("tabnumber");

%>
<table border="0" cellspacing="4">
<tr>
<td><img src="<%=thumb%>" 
onClick="alayer.showStart('number=<%=number%>')"></td>
<td valign="top">
<b><%=title%></b><br>
<%=name%><br>
<a href="#" 
onClick="tab.start('tab_<%=tabnumber%>','number=<%=tabnumber%>');
return false">
「<%=name%>」 のタブを開く</a>
</td>
</tr>
</table>
<%
   }
  }catch(SQLException e){
   out.println("データベースエラーです。:" + e);
  }
 }else{
  try{
   PreparedStatement ps = c.prepareStatement(
    "SELECT tab , name , url , notes FROM web2_types WHERE number = ?");
   ps.setInt(1 , type);
   ResultSet rs = ps.executeQuery();

   String tab = "";
   String name = "";
   if(rs.next()){
    tab = rs.getString("tab");
    name = rs.getString("name");
    String url = rs.getString("url");
    String types_notes = rs.getString("notes");

    out.println("<b>" + name + "</b><br>");
    if(url != null){
     out.println("<a href=\"" + url + "\">" + url + 
      "</a><br>");
    }
    out.println("<br><u>Description</u><br>" + types_notes);
   }

   ps = c.prepareStatement(
    "SELECT COUNT(*) AS 'value' FROM web2_contents WHERE type = ?");
   ps.setInt(1 , type);
   rs = ps.executeQuery();
   rs.next();

%>
<br><br>

<table border="0">
<tr><td bgcolor="#DDE7FF">
<i>Check</i><br>
 項目 <b><%=name%></b> には、
現在 <b><%=rs.getInt("value")%></b> 件の項目が存在しています。<br>
 画像をクリックすると、その詳細が表示されます。
表示された詳細を閉じるには、詳細ウィンドウ内の
「レイヤーを閉じる」をクリックしてください。
他のページに移りたい場合には、上のタブをクリックしてください。
他のページに移動すると、詳細表示は自動的に閉じられます。
</td></tr></table>
<br>

<b>Contents</b><br>
<%
   ps = c.prepareStatement(
    "SELECT number , title , notes , thumb FROM web2_contents " + 
    "WHERE type = ? ORDER BY number");
   ps.setInt(1 , type);
   rs = ps.executeQuery();

   int loop = 0;

   while(rs.next()){
    int number = rs.getInt("number");
    String title = rs.getString("title");
    String notes = rs.getString("notes");

    notes = notes.replaceAll("<[a-zA-Z!#/]?.*?(?:>|\\z)" , "");

    if(notes.length() > 80){
     notes = notes.substring(0 , 80) + "...";
    }
    String thumb = rs.getString("thumb");

%>
<table border="0" cellspacing="4">
<tr>
<td><img src="<%=thumb%>" 
onClick="alayer.showStart('number=<%=number%>')"></td>
<td valign="top">
<b><%=title%></b><br>
<%=notes%>
</td>
</tr>
</table>
<%
    loop ++;
   }

   if(loop == 0){
    out.println("- Empty -");
   }
  }catch(SQLException e){
   out.println("データベースエラーです。:" + e);
  }
 }
%>
</td></tr></table>
<%
}else if("detail".equals(mode)){
 int number = 0;
 try{
  number = Integer.parseInt(request.getParameter("number"));
 }catch(NumberFormatException e){
 }

 try{
  PreparedStatement ps = c.prepareStatement(
   "SELECT title , notes , thumb , url FROM web2_contents " + 
   "WHERE number = ? ORDER BY number");
  ps.setInt(1 , number);
  ResultSet rs = ps.executeQuery();

  if(rs.next()){
   String title = rs.getString("title");
   String notes = rs.getString("notes");
   String thumb = rs.getString("thumb");
   String url = rs.getString("url");
%>

<table border="0" cellspacing="0" width="100%" height="100%">
<tr>
<td class="small" valign="top">

<table border="0" cellspacing="4" align="left">
<tr><td><img src="<%=thumb%>"></td></tr>
</table>

<b><%=title%></b><br>
<%=notes%>
</td>
</tr>

<tr>
<td align="right" valign="bottom">
<a href="<%=url%>" style="color:#000000" target="_blank">
<img src="sys_detail.gif" border="0"></a><br>
<a href="#" style="color:#000000" 
onClick="alayer.hidden();return false"><img src="sys_close.gif" border="0"></a>
</td>
</tr>
</table>
<%
  }else{
%>
<table border="0" cellspacing="0" width="100%" height="100%">
<tr><td valign="top">
<b>詳細がありません</b><br>
 このデータには詳細が存在しません。
</td><tr>
<tr><td align="right" valign="bottom">
<a href="#" style="color:#000000" 
onClick="alayer.hidden();return false">
<img src="sys_close.gif" border="0"></a>
</td></tr>
</table>
<%
  }
 }catch(SQLException e){
%>
<table border="0" cellspacing="0" width="100%" height="100%">
<tr><td valign="top">
<b>Error</b><br>
 データベースアクセス時にエラーが発生しました。<br>
<%=e%>
</td><tr>
<tr><td align="right" valign="bottom">
<a href="#" style="color:#000000" 
onClick="alayer.hidden();return false">
<img src="sys_close.gif" border="0"></a>
</td></tr>
</table>
<%
 }
}else{
%>

<html>
<head>
<title>Web 2.0</title>
<script language="JavaScript">

function init(){
 if(!window.event)
  document.addEventListener('mousemove' , MouseMotionListener , false);

 tab.setShowEnded('home');
 tmotion.args = "number=home";
 tmotion.setTableOpened();
 tmotion.onOpenShowEnd();
}
function MouseMotionListener(e){
 window.event = new Object();
 window.event.x = e.clientX;
 window.event.y = e.clientY;
}

function AnimationLayer(){
 this.AnimationLayer();
}
AnimationLayer.prototype.AnimationLayer = function(){
 this.process = 0;

 this.start_x = 0;
 this.start_y = 0;
 this.end_x = 0;
 this.end_y = 0;
 this.layer_width = 300;
 this.layer_height = 300;

 this.animation_frame = 20; // アニメーションのフレーム数
 this.wait_time = 30; // アニメーション変移の速度

 this.args = "";
}
AnimationLayer.prototype.showLayer = function(){
 this.process = 0;
 document.getElementById("layer").style.visibility="visible";
 this.clear();
 this.resizeLayer();
 this.animation();
}
AnimationLayer.prototype.showStart = function(args){
 if(args != null)
  this.args = args;

 this.start_x = event.x + getScrollX();
 this.start_y = event.y + getScrollY();
 this.showLayer();
}
AnimationLayer.prototype.clear = function(){
 if(this.timeid != null){
  clearTimeout(this.timeid);
  this.timeid = null;
 }
 ajax.cancel();
 document.getElementById("layercontent").innerHTML = "";
 document.getElementById("layertable").bgColor = "";
}
AnimationLayer.prototype.animation = function(){
 this.timeid = null;

 document.getElementById("layercontent").innerHTML = "";
 document.getElementById("layertable").bgColor = "";

 if(this.process <= this.animation_frame){
  var width = Math.round(this.layer_width * 
   this.process / this.animation_frame);
  var height = Math.round(this.layer_height * 
   this.process / this.animation_frame);
  var pos_x = Math.round((this.start_x * 
   (this.animation_frame - this.process) / this.animation_frame) + 
   (this.end_x * this.process / this.animation_frame));
  var pos_y = Math.round((this.start_y * 
   (this.animation_frame - this.process) / this.animation_frame) + 
   (this.end_y * this.process / this.animation_frame));

  var layer = document.getElementById("layer");
  layer.style.left = pos_x;
  layer.style.top = pos_y;
  layer.style.width = width;
  layer.style.height = height;

  var obj = this;
  if(this.process < this.animation_frame){
   this.timeid = setTimeout(function(){obj.animation();} , this.wait_time);
   this.process ++;
  }else{
   setTimeout(function(){obj.timeid = null; obj.onShowEnd();} , 0);
  }
 }
}
AnimationLayer.prototype.resizeLayer = function(){
 var layer = document.getElementById("layer");
 this.end_x = getClientWidth() + getScrollX() - this.layer_width - 50;
 this.end_y = getScrollY() + 50;
 if(this.process >= this.animation_frame){
  layer.style.left = this.end_x;
  layer.style.top = this.end_y;
 }
}
AnimationLayer.prototype.hidden = function(){
 this.clear();
 document.getElementById("layer").style.visibility="hidden";
}
AnimationLayer.prototype.visible = function(){
 document.getElementById("layer").style.visibility="visible";
}
AnimationLayer.prototype.onShowEnd = function(){
 var content = document.getElementById("layercontent");
 content.innerHTML = "Loading...";
 document.getElementById("layertable").bgColor = "#FFFFFF";

 ajax.cancel();
 ajax.onResponse = function(text){
  var layer = document.getElementById("layer");
  content.innerHTML = text;
 }

 var args = "";
 if(this.args.length > 0)
  args = "&" + this.args;
 ajax.send("mode=detail" + args);
}
var alayer = new AnimationLayer();

// Utilities
function getScrollX(){
 var s = document.body.scrollLeft;
 if(s == undefined)
  s = self.pageXOffset;
 return s;
}
function getScrollY(){
 var s = document.body.scrollTop;
 if(s == undefined)
  s = self.pageXOffset;
 return s;
 
}
function getClientWidth(){
 var c = document.body.clientWidth;
 if(c == undefined)
  c = window.innerWidth;
 return c;
}
function getClientHeight(){
 var c = document.body.clientHeight;
 if(c == undefined)
  c = window.innerHeight;
 return c;
}

function TableAnimation(){
 this.TableAnimation();
}
TableAnimation.prototype.TableAnimation = function(){
 this.motion_process = 0;
 this.open_animation = 25; // アニメのフレーム数
 this.size_max = 100; // 最大サイズ
 this.size_left = 5; // 最小サイズ
 this.open_wait = 15; // アニメーションの待機時間

 this.opened_wait = 0; // メニューが完全に開いた後のウェイト
 this.closed_wait = 50; // 閉じた後のウェイト

 this.motion_vector = 0; // 1:open , -1:close

 this.args = ""; // リクエスト引数
}
TableAnimation.prototype.forceTableOpen = function(){
 this.motion_process = 0;
 this.tableOpen();
}
TableAnimation.prototype.forceTableClose = function(){
 this.motion_process = this.motion_animation;
 this.tableClose();
}
TableAnimation.prototype.tableOpen = function(){
 this.clear();
 this.motion_vector = 1;
 this.tableMotion();
}
TableAnimation.prototype.tableClose = function(){
 this.clear();
 this.motion_vector = -1;
 this.tableMotion();
}
TableAnimation.prototype.clear = function(){
 if(this.timeid != null){
  clearTimeout(this.timeid);
  this.timeid = null;
 }
 document.getElementById("content").innerHTML = "";
}
TableAnimation.prototype.tableMotion = function(){
 this.timeid = null;

 this.validate();

 if((this.motion_vector > 0 && 
  this.motion_process <= this.open_animation) ||
  (this.motion_vector < 0 && this.motion_process >= 0)){
  // 定率法減価償却の演算でサイズを変更
  var height = Math.round(this.size_max * 
   Math.pow(this.size_left / this.size_max , 
   (this.open_animation - this.motion_process) / this.open_animation));

  var obj = this;
  if((this.motion_vector > 0 && 
   this.motion_process < this.open_animation) ||
   (this.motion_vector < 0 && this.motion_process > 0)){
   this.motion_process += this.motion_vector;

   this.timeid = setTimeout(function(){obj.tableMotion();} , this.open_wait);
  }else{
   if(this.motion_vector > 0 && this.motion_process == this.open_animation){
    this.timeid = setTimeout(
     function(){obj.timeid = null; obj.onOpenShowEnd();} , this.opened_wait);
   }
   if(this.motion_vector < 0 && this.motion_process == 0){
    this.timeid = setTimeout(
     function(){obj.timeid = null; obj.onCloseShowEnd();} , this.closed_wait);
   }
  }
 }
}
TableAnimation.prototype.onOpenShowEnd = function(){
}
TableAnimation.prototype.onCloseShowEnd = function(){
}
TableAnimation.prototype.validate = function(){
 if(this.motion_process > this.open_animation)
  this.motion_process = this.open_animation;
 if(this.motion_process < 0)
  this.motion_process = 0;
}
TableAnimation.prototype.moving = function(args){
 if(args != null)
  this.args = args;

 this.onCloseShowEnd = function(){
  this.onCloseShowEnd = function(){}
  this.tableOpen();
 }
 this.tableClose();
}
TableAnimation.prototype.setTableOpened = function(){
 this.motion_process = this.open_animation;
 document.getElementById("motion").style.height = this.size_max + "%";
}
TableAnimation.prototype.setTableClosed = function(){
 this.motion_process = 0;
 document.getElementById("motion").style.height = this.size_left + "%";
}
var tmotion = new TableAnimation();
tmotion.onOpenShowEnd = function(){
 document.getElementById("content").innerHTML = "Loading...";
 ajax.cancel();
 ajax.onResponse = function(text){
  document.getElementById("content").innerHTML = text;
 }

 var args = "";
 if(this.args.length > 0)
  args = "&" + this.args;
 ajax.send("mode=content" + args);
}

function TabFlash(){
 this.TabFlash();
}
TabFlash.prototype.TabFlash = function(){
 this.flash = 0;
 this.flash_max = 10;
 this.flash_wait = 50;
 this.end_wait = 150;
 this.id = null;
 this.timeid = null;

 this.normal_color = "#AACCFF";
 this.select_color = "#FF8888";
 this.colors = new Array(this.select_color , this.normal_color);
}
TabFlash.prototype.start = function(id , args){
 if(this.id == id)
  return;
 this.clear();

 this.flash = 0;
 this.id = id;
 this.args = args;

 this.animation();
}
TabFlash.prototype.clear = function(){
 if(this.timeid != null){
  clearTimeout(this.timeid);
  this.timeid = null;
 }
 if(this.id != null){
  document.getElementById(this.id).bgColor = this.normal_color;
  this.id = null;
 }
 ajax.cancel();
}
TabFlash.prototype.animation = function(){
 this.timeid = null;
 if(this.flash <= this.flash_max){
  var obj = this;
  if(this.flash < this.flash_max){
   var index = this.flash % this.colors.length;
   var col = this.colors[index];

   document.getElementById(this.id).bgColor = col;
   this.flash ++;
   this.timeid = setTimeout(function(){obj.animation();} , this.flash_wait);
  }else if(this.flash == this.flash_max){
   document.getElementById(this.id).bgColor = this.select_color;
   this.timeid = setTimeout(function(){obj.timeid = null;obj.onShowEnd();} , this.end_wait);
  }
 }
}
TabFlash.prototype.onShowEnd = function(){
}
TabFlash.prototype.setShowEnded = function(id){
 this.clear();
 this.flash = this.flash_max;
 this.id = id;
 document.getElementById(this.id).bgColor = this.select_color;
}

var tab = new TabFlash();
tab.onShowEnd = function(){
 alayer.hidden();
 tmotion.moving(this.args);
}

function YAjax(){
 this.YAjax();
}
YAjax.prototype.YAjax = function(){
 this.createRequest();
 this.headers = new Array();
 this.headers.unshift(new RequestHeader("Content-type" , 
  "application/x-www-form-urlencoded"));
 this.active = false;
}
YAjax.prototype.getRequestHeaders = function(){
 return this.headers;
}
YAjax.prototype.setRequestHeaders = function(h){
 this.headers = h;
}

YAjax.prototype.createRequest = function(){
 try{
  this.ajax = new ActiveXObject("Microsoft.XMLHTTP");
 }catch(e){
  this.ajax = new XMLHttpRequest();
 }
}
YAjax.prototype.init = function(){
 if(this.ajax == null){
  this.createRequest();
  if(this.ajax == null){
   return false;
  }
 }else if(this.active){
  this.ajax.abort();
 }

 if(this.ajax != null)
  this.prepareCallback();

 this.active = true;

 return true;
}
YAjax.prototype.cancel = function(){
 if(this.ajax != null && this.active){
  this.ajax.abort();
  this.ajax.onreadystatechange = function(){}
  this.active = false;
 }
}
YAjax.prototype.isSucceed = function(){
 if(this.ajax.readyState == 4 && this.ajax.status == 200)
  return true;
 return false;
}
YAjax.prototype.getAjax = function(){
 return this.ajax;
}
YAjax.prototype.requestCallback = function(){
 this.onStateChange();
 if(this.isSucceed()){
  this.onResponse(this.ajax.responseText);
 }else if(this.ajax.readyState == 4){
  this.onError();
 }
}
YAjax.prototype.onResponse = function(text){
}
YAjax.prototype.onError = function(){
}
YAjax.prototype.onStateChange = function(){
}
YAjax.prototype.prepareCallback = function(){
 if(this.ajax != null){
  var selfobj = this;
  this.ajax.onreadystatechange = function(){
   selfobj.requestCallback();
  }
 }
}
YAjax.prototype.request = function(url , args , method , async){
 this.init();
 this.onRequest(url , args , method , async);
}
YAjax.prototype.onRequest = function(url , args , method , async){
 if(method == null || method == "")
  method = "GET";
 if(url == null)
  url = "";
 if(args == null)
  args = "";
 if(async == null)
  async = true;

 var urls = this.parseURL(url , args , method)

 var ajax = this.getAjax();

 if(!async)
  ajax.onreadystatechange = function(){}

 ajax.open(method , urls[0] , async);

 for(var i = 0; i < this.headers.length; i++)
  ajax.setRequestHeader(this.headers[i].getName() , 
   this.headers[i].getValue());

 ajax.send(urls[1]);

 if(!async)
  this.requestCallback();
}
YAjax.prototype.parseURL = function(url , args , method){
 var send_url = url;
 var send_args = "";

 if(method == "POST"){
  send_args = args;
 }else{
  // ? が必要な場合は付加
  if(send_url.charAt(send_url.length() - 1) != "?" && args != ""){
   send_url += "?";
  }
  send_url += args;
 }

 return new Array(send_url , send_args);
}

function RequestHeader(name , value){
 this.RequestHeader(name , value);
}
RequestHeader.prototype.RequestHeader = function(name , value){
 this.name = name;
 this.value = value;
}
RequestHeader.prototype.getName = function(){
 return this.name;
}
RequestHeader.prototype.getValue = function(){
 return this.value;
}

var ajax = new YAjax();
ajax.send = function(args){
 this.request("?" , args , "POST" , true);
}
</script>
<style type="text/css">
td.small{
 font-size: 12px;
}
</style>
</head>

<body link="#0000FF" alink="#FF0000" vlink="#0000FF" 
bgcolor="#000000" onload="init()" onScroll="alayer.resizeLayer()" 
onResize="alayer.resizeLayer()">

<table border="0" cellspacing="0" cellpadding="0" 
width="75%" align="center">
<tr><td>

<table border="0" cellpadding="0" cellspacing="0" 
bgcolor="#AACCFF" width="100%">
<tr>

<!--
<td valign="top" id="close" 
onClick="tab.onShowEnd=function(){alayer.hidden();tmotion.tableClose();};
tab.start('close')">
<b>close</b>
</td>

<td valign="top" id="open" 
onClick="tab.onShowEnd=function(){tmotion.tableOpen();};
tab.start('open')">
<b>open</b>
</td>

<td valign="top" id="move" 
onClick="tab.onShowEnd=function(){tmotion.moving();};
tab.start('move')">
<b>move</b>
</td>
-->

<td align="center" id="home" onClick="tab.start('home','number=0')">

<table border="0" cellspacing="1">
<tr><td>
<b>Home</b>
</td></tr>
</table>

</td>

<%
 try{
  PreparedStatement ps = c.prepareStatement(
   "SELECT number , tab FROM web2_types ORDER BY number");
  ResultSet rs = ps.executeQuery();

  while(rs.next()){
   String tab = rs.getString("tab");
   int number = rs.getInt("number");
   String id = "tab_" + number;

%>
<td bgcolor="#000000" width="4"></td>

<td align="center" id="<%=id%>" 
onClick="tab.start('<%=id%>','number=<%=number%>')">

<table border="0" cellspacing="1">
<tr><td>
<b><%=tab%></b>
</td></tr>
</table>

</td>
<%
  }
 }catch(SQLException e){
 }
%>

</tr>
</table>

</td></tr>
</table>

<table border="0" cellpadding="0" cellspacing="0" 
width="75%" height="10%" align="center" id="motion">
<tr><td bgcolor="#CCDDFF" id="content" 
valign="top"></td></tr>
</table>

<div align="center">
<small style="color:#FFFFFF">
(C)Copyright 2007 
<a href="http://www.yamicha.com/" 
style="color:#FFFFFF">yamicha.com</a>
/Various Creators<br>
ソースの無断転載・無断使用は禁じませんが、
画像の著作者以外の画像の転載・使用は堅く禁じます
</small>
</div>

<div id="layer" class="layerstyle" 
style="position:absolute;visibility:hidden">
<table border="1" cellpadding="0" cellspacing="0" 
bordercolor="#3377FF" style="border-style:solid" 
width="100%" height="100%" id="layertable">
<tr><td valign="top" id="layercontent" class="small"></td></tr></table>
</div>

</body>
</html>

<%
}
c.close();
%>
 繰り返しますが、JavaScriptの実装はほとんど決め打ちで、極めて応用性に乏しいです。これはお遊び言語であるJavaScriptだから行える実装なのであり、Javaなどの本格言語でこのような実装を行っては絶対にいけません。Javaでこのようなコードを作り、それをサンプルコードとして例示でもしようものなら、世界X万人の開発者からいっせいに石が飛んでくることでしょう。
 JavaScriptをオブジェクト指向のように扱っている理由は、継承による柔軟性と再利用性の強化というより、単に変数や関数の衝突を避けるためです。たとえJavaScriptであっても、一定以上の長さのプログラムを組むのであれば、変数の衝突は避けられない問題となってきますので、こうしてオブジェクト化することは非常に重要です。
 そのために出てきているのが「setTimeout()では何らかのオブジェクトに属する関数を直接呼び出せない」問題なのですが、これは次のようなコードで回避しています。
ClassName.prototype.setTimeoutFunction = function(){
 var obj = this;
 setTimeout(function(){obj.callFunction();} , time);
}
 後はデータベースにデータを登録し、リソースを用意すれば動作させることができます。データ登録の際には、表示用のサムネイル(大きさに制限はありませんが、100x100前後が妥当かと)と「さらに詳細を表示」が行われた際に表示する画像やHTMLページなどのリソースが必要です。これらのリソース(特にサムネイル)はデータベースにBLOBで登録しても良かったのですが、今回は外部ファイルとしました。
 まずは「さらに詳細を表示」と「レイヤーを閉じる」用の画像リソースを用意します。どのようなものでも構いませんが、私は次のようなものを作成しました。

sys_close.gif
sys_detail.gif

 これらを上記JSPを置いたディレクトリと同じディレクトリに設置します。ただし、Apacheとの連携でTomcatなどのServletコンテナを動かしている場合、URLに「localhost:8080」のようにServletのポート名をつけて(連携させずに)表示するか、中身すべてがServletコンテナで処理されるように指定されたディレクトリに置くか、あるいはJSPのみをServletのディレクトリに置き、画像ファイルはApacheの同名のディレクトリに置くようにしなければ、画像が表示されません。
 それが終わったら、とにかくテーブルを作成します。テーブルがある状態ならデータが表示されないだけですが、テーブルがなければデータベースエラーが発生しますので、動作を試すのは少なくともテーブルを作成した後にしましょう。
CREATE TABLE web2_types(number INT AUTO_INCREMENT , 
tab VARCHAR(255) , name VARCHAR(255) , url VARCHAR(255) , notes TEXT , 
PRIMARY KEY(number));

CREATE TABLE web2_contents(number INT AUTO_INCREMENT , type INT , 
title VARCHAR(255) , notes TEXT , thumb VARCHAR(255) , url VARCHAR(255) , 
PRIMARY KEY(number) , FOREIGN KEY(type) REFERENCES web2_types(number) 
ON DELETE CASCADE ON UPDATE CASCADE);
 web2_typesには画像の作者情報を登録します。tabがタブに表示される名称(長くない方が良い)、nameが正式名称、urlが作者のサイトURL、notesが備考です。web2_contentsはtitleが画像タイトル、notesが備考、thumbがサムネイル設置場所のURL、urlが「詳細を表示」した際に新しいウィンドウで表示されるURL(表示さえ可能なら、画像をそのまま指定しても、HTMLでも、引数をつけたCGIやJSPでも良い)になります。これらのテーブルに登録するデータによっては、「リンク集」や「オンラインショッピングもどき」を作ることも比較的容易に可能でしょう。
 テーブルを作成したら、データベースにデータを登録し、サムネイルを準備し、詳細表示用の画像を用意します。今回の実装では、「詳細を表示」時には画像ファイルが直接表示されることになっています。
テスト用に用意した画像
1.原画
s_sara.jpg
r_ilias.jpg
r_sara.jpg
r_harden.jpg
2.サムネイル
t_s_sara.jpg
t_r_ilias.jpg
t_r_sara.jpg
t_r_harden.jpg

テスト用に登録したデータ
INSERT INTO web2_types VALUES(1 , 'sham' , 'sham/影本 奈月' , NULL , 
' sham/影本 奈月 さんに描いていただいた画像の加工前原画です。');
INSERT INTO web2_types VALUES(2 , 'ruva' , 'うにうに/ruva' , 
'http://sousya.umu.cc/' , 
' うにうに/ruva さんに描いていただいた画像の加工前原画です。');
INSERT INTO web2_types VALUES(3 , '雛世' , '雛世/ひさめ' , NULL , 
' 雛世/ひさめ さんに描いていただいた画像の加工前原画です。');
INSERT INTO web2_types VALUES(4 , 'Blog' , 'ブログのロゴ' , 
'http://void.yamicha.com/blog/' , 
' ブログで使用した歴代ロゴのうちのいくつかです。');

INSERT INTO web2_contents VALUES(NULL , 1 , '騎士サーラ' , 
' いくら騎士サーラでも、たまにはこのような表情になることもあるのでしょうか。
普段は絶対に鎧を脱がないこの人ですが、
何と鎧を着ていない、レアな格好の画像です。<br>
 しかし実際、寝る時にはどうしているのでしょう。
本当に鎧のまま寝るのだとしても、サーラさんらしいともいえますが・・・。' , 
't_s_sara.jpg' , 's_sara.jpg');

INSERT INTO web2_contents VALUES(NULL , 2 , '魔道士イリアス' , 
' Tactical Revolution のフェイス画としても使用した、
魔道士イリアスの顔(+上半身)グラフィックです。<br>
 この人が最終的に騎士サーラをしのぐほど強くなるとは信じられません。
そこはさすがに開発技術者といったところでしょうか。' , 
't_r_ilias.jpg' , 'r_ilias.jpg');

INSERT INTO web2_contents VALUES(NULL , 2 , '騎士サーラ' , 
' Tactical Revolution のフェイス画としても使用した、
騎士サーラの顔(+上半身)グラフィックです。<br>
 この控えめな人が魔道士イリアスと張り合えるのかは不明でしたが、
実際のところはものすごい人気でした。
本当に現れたらサイン会や写真会を開催しかねないほどに。
このフェイス画もかなり人気のようです。<br>
 騎士道を貫くキャラというのは、やはり人気が出るのでしょうか。' , 
't_r_sara.jpg' , 'r_sara.jpg');

INSERT INTO web2_contents VALUES(NULL , 2 , '剣聖ハードゥン' , 
' Tactical Revolution のフェイス画としても使用した、
剣聖ハードゥンの顔(+上半身)グラフィックです。<br>
 決して華があるわけではありませんが、地味に強い人。
騎士の中では騎士サーラ、聖騎士シェインに次ぐ3番目の実力者です。<br>
 フェイスもキャラ設定もそういう印象です。' , 
't_r_harden.jpg' , 'r_harden.jpg');
 さて、何とかデータは登録できましたが、果たしてこれで動作するのでありましょうか。
 まずタブをクリックしてみると、



 タブが点滅した後(画像では点滅の様子が分かりませんが)、テーブルが縮小し、



 再び展開されてデータが表示されます(以下に示す画像では、うにうに/ruvaさんよりいただいたフェイス画の一覧)。
 表示されたデータの中から何らかの画像をクリックすると、クリックした部分から枠線が展開され、



 ウィンドウ(レイヤー)になります。



 このウィンドウは常に画面右上に表示され、スクロールしても同じ場所に表示されます。ウィンドウ表示中に他の画像をクリックすると、現在のウィンドウは消され、新しいウィンドウが展開されて表示されます。



 ここで詳細を確認できる他、「さらに詳細を表示」を行ったり、邪魔になったら「レイヤーを閉じる」ことができます。


※ブログのロゴにも使用したこの画像は、sham/影元 奈月さんに描いていただいたものです(正式な場では今回が初使用)。この場を借りてお礼申し上げます。やはりサーラさんは変な人。

 IE 6、Netscape 7、Firefox 2で正常な動作を確認しましたので、Ajaxさえサポートしていれば、大抵のブラウザで動くのではないでしょうか。
 それにしてもJavaScriptは疲れます。Javaによる実装はかなりサクサク行えるのですが、JavaScriptは恐ろしく実装効率が悪いです。私がJavaScript嫌いなのもあるのでしょうが、JavaScriptのような簡単・非厳密な言語に比べ、Javaなどの「複雑な」言語は一定以上難しいプログラムにおいて極めて効率が良いようです。

 ところで、今回作成したこのプログラムですが、絶対に使用・流用をされないようお願い申し上げます。無論、ソースコードは私の著作物であり、無断使用は法律で禁じられている、などという理由ではありません。Apache Software Foundationなどを見るまでもなく、自由に利用できるオープンソース化は世界の開発者が行っていることです。
 また、JavaScriptなどの簡素な言語で顕著な傾向なのですが、一定以上複雑なプログラムには厳しい著作権制限がない一方、誰でもすぐ作れるような非常に簡単なプログラムの多くに「無断転載禁止」などと書いてあったりします。すなわち、大したことのないプログラムを作って自己満足するような人間が著作権を非常に大きく主張する一方、ある程度優れた開発者はそのようなバカげたことは絶対にしないのです。
 それでは、なぜ私は皆様に使用・流用をしないようにお願いしているのか。答えは簡単、こうしたプログラムを自分で作って使っている分には良いのですが、実際にこのようなサイトが存在しては困るためです。移動のたびにいちいちエフェクトを表示されたり、わざわざAjaxでデータを読み込むなど、想像するだけでぞっとします。JavaScriptの使用は表現上絶対に必要な場合のみにとどめ、Ajaxの使用もCometによるリアルタイムチャットなどどうしても必要な場合のみにとどめるべきでしょう。
 ただ、一応はっきりさせておきますが、「使用・流用しないように」というのはあくまで「お願い」であって「禁止」ではありません。著作権的なスタンスはこれまでと同様に「使用・流用・加工はご自由に」です。しかし、これを使用した挙句に多くの人に煙たがられても責任は負いません。あしからず。
 とにかくこれが「Web 2.0」なのですか。最近の流れを見る限り、この傾向は止まるどころか強まっていきそうな気配さえあります。しかし、このWeb 2.0とやらが一般化し、どこのサイトに行ってもこのようなページを見せ付けられることを想像してみてください。相当にストレスがたまるのではないでしょうか。
 「専用のソフトウェアがなければ不可能であった操作・表現をブラウザ・Web上で」というのがWeb 2.0として語られたりしますが、果たしてそれは必要なのでしょうか。ある程度のところで自制ができれば便利にはなるでしょうが、「Web 2.0を実装すれば技術を誇示できる」とばかりにこれを実装する企業や人間が増え、際限なく泥沼にはまるようなことはないでしょうか。
 その後、「Web 2.0は存在自体が間違いの技術だった」などと言われなければ良いですが。そうでなくとも、Ajaxなどはこれ単体でも個人サイトに広まってしまう可能性があり、そうなると素人がAjaxまみれのサイトを作る恐れすら出てきます。
 かつて、まだIEやNNが4程度の時代には、JavaScriptが極端なまでには使われていなかった気がします。ブラウザ依存も激しく、そう頻繁には使えなかったのでしょう。しかし、インターネットがジリジリと流行し始め、JavaScriptを使ったサイトも増えてきました。勝手にウィンドウをリサイズしたり、ページ変移時に邪魔なエフェクトを流したり、右クリックを妨害したりといった勘違いサイトも少なからず存在しています。
 本来、「Web 2.0」とはWebの特性をそのまま表現したもの、すなわち「情報の送り手と受け手の区別がなく、誰でも情報を発信でき、受け取れる」ことを示す用語であり、実際にはWebページやブログが雨後のたけのこのごとく誕生した時点で条件を満たしているのです。しかし、「2.0」という意味もなく革新的なイメージが先行したのか、いまやAjaxだろうがSOAPだろうが何だろうがWeb 2.0です。
 このような概念が果たして本当に必要なのか、またAjaxなどの乱用で本当に使いやすいサイトが構築できるのか、立ち止まって考えてみる必要があります。
カテゴリ [開発魔法][社会問題][経済・知的財産] [トラックバック 0][コメント 0]

日本漏えい録
2007/04/30(Mon)19:20:50
 「日本紳士録」が休刊。おそらく当面、あるいは永遠に復活することはないでしょう。日本紳士録といいますのは、高額所得者に対して記載の有無の確認を取った上、氏名、住所、学歴、配偶者などを記載して発行する人名録だそうです。最近は辞退の申し出が大量に寄せられており、これ以上の続行は困難と判断したようです。
 本人の同意を取るという点では本当に紳士的な人名録なのですが(無断記載する人名録が一般書店に存在するのかは知りませんが)、これはホットニュースといえるでしょう。個人情報意識の高まりが少しずつ実を結びつつあります。「個人情報は守られるべき」という当然過ぎる常識が確立するまでに、まさかこれほどの時間がかかるとは。
 今でこそ様々な個人情報漏えい行為が禁じられていますが、個人情報保護法が施行される前まではザルも同然でした。個人情報について注意しているだけで「神経質」とみなされ、二言目には「お前の個人情報など誰も欲しがらない」。ほんの数年前まで知識のない人間がぬけぬけとこのような発言をしていた時代があったのです。
 個人情報が実はかなり重要で恐ろしいものであることを知っていれば、最近頻繁に指摘される「個人情報保護法への過剰反応」などという事例が、実は過剰反応でも何でもないことが分かります。私が小学校のころは平然と連絡網が配られていましたが、「お菓子をあげるなどと言われても渡さないように」という指導はあったよう記憶しています。個人情報をかすめ取ろうというたくらみは当時から存在したことが分かります。
 そして、学校・学年及び電話番号がしっかり分かってしまう連絡網は、実のところ極めて危険な存在です。役所にテクテク歩いて行けば自由に見せてもらえた(もちろん現在では大変困難になっています)住民基本台帳に電話番号データは存在しませんから、これだけでも高価値な情報です。連絡網ですから名前や電話番号が間違っている恐れもありません。
 読売新聞の「本人の同意がなくても連絡網を作れるようにすべき」などという寝言(2007/4/26付社説)は、おそらく個人情報の恐ろしさを知らないから言えるのでしょうが、30〜40人もの良質な個人情報データを配布するなど論外です。その中の1人でも情報を漏らせば、すべてのデータが漏れてしまうのです。住民基本台帳の閲覧制限論のきっかけとなったのは、台帳を見て家を調べた上での強制わいせつ事件でしたが、連絡網でも似たような事件が起こる恐れがありますし、そうでなくても業者にとってはウハウハでしょう。
 ところで、最近の個人情報保護について「個人情報が隠蔽されることで、個人情報の価値が高くなり、不正な手段で収集された情報が裏で高額でやり取りされるようになる恐れがある」などと主張し、こうした流れに警鐘を鳴らす人もいるようですが、私はこれを見て「銃」を連想しました。長崎市長やバージニア州の多くの命を奪った、あの「銃」です。
 日本では一般市民が銃を手に入れることはできません。仮に手に入れるとすれば、不正な手段を用いて取得された銃を、高額な報酬を支払って入手する必要があります。無論、これは「例えば」の話であり、このような行為は非合法かつ危険ですから、やってはいけないのは言うまでもありません。
 こうした高額な銃売買が暴力団の資金源となっている可能性は否定できませんし、治安を脅かしているのも事実です。銃を誰もが購入できるようにすれば、少なくとも銃が暴力団の資金源となることはありませんし、不正な手段で密輸されることもなくなるでしょう。それでは、日本は銃の所持・取引を合法とすべきでしょうか。少なくとも私はすべきでないと考えます。
 さて、最近の日本では個人情報の取り扱いに厳しい制限が作られつつあり、名簿の漏えいや売買などは非合法です。仮に手に入れるとすれば、不正な手段で調査または漏えいされた個人情報を、高額な報酬を支払って入手する必要があります。それでは、こうした不正売買を防ぐために、個人情報保護の流れを断ち切り、誰もが他人の個人情報を容易に閲覧でき、個人情報の漏えいが横行する社会に戻すべきでしょうか。少なくとも私はすべきでないと考えます。
 個人情報保護法の何よりも大きな効能は、多くの人に個人情報の重要さを知らしめたことにあります。尼崎脱線事故では病院側が個人情報保護を盾に警察にさえ情報を渡さなかったという過剰反応があったようですが、こうした過剰反応は当然ながら問題です。しかし、相手が警察ではなくマスコミであるのなら、迷わずシャットアウトすべきです。警察に情報開示を要請されたような場合は保護の例外であるという規定の周知を図りつつ、それ以外の面ではむしろ今後とも保護を強化していくべきでしょう。
 マスコミが「個人情報保護法は過剰だ」と必死で叫んでいることを見ても、同法は効果を上げつつあるのです。また、「本人が同意すれば名簿などに記載できる」ことからしても、同法は基本的に「これまでは個人情報の漏えいや取引が野放図に認められてきたが、これからは個人情報に関する権利を本人が持つ」という内容ですから、少なくとも現時点において、これが匿名社会に結びつく恐れは全くなく、したがって個人情報保護法と匿名社会に因果関係は一切ありません。
 上記の通り、ここで個人情報保護の手を緩めるのは百害あって一利なしであり、保護の動きは今後とも推進していくべきです。それと同時に、今後とも社会全体の個人情報意識が高まることを願います。

 最近は帳簿に関する不正が目立ちます。加ト吉が循環取引を問題視され、今度は読売新聞社が所得を隠蔽したとして重加算税を請求されました。といっても、帳簿処理とは非常に分かりづらいものですから、これらについて少々解説を。
 まず加ト吉の循環取引の例ですが、まず連想したのが「手形」や「荷為替」に類する取引でした。加ト吉の例では倉庫に眠った商品を不正のタネにしたのではありますが、「商品・資産そのものではなくそれに関する権利を動かす」という点では似たところがありますので、権利を動かすことについて一応書いておきます。
 まず、帳簿上では権利も立派な資産とみなされます。例えば「他社に100万円を貸した」場合、これは「後で100万円(及び利子)を受け取る権利」があるとみなされ、帳簿上では受け取れる額を持っているとみなされます。この権利を文書化したものが「手形」であり、手形(イコール権利)を期日前に銀行に持参して売却したり(割引)、他人に現金や小切手を支払う代わりに渡したり(裏書)することは不透明な取引ではありません。
 それでは、循環取引とは一体何なのか。まずA社が何か適当な商品を仕入れ、倉庫で保管しておきます。そして、商品は倉庫に置いたまま、商品の権利をB社に売り渡します。B社はそれをC社に売る、といったことを繰り返し、最終的には商品の権利がA社に戻るようにします。その結果、商品の権利はそのまま手元に戻るのに、売り上げだけは増えている状態になります。これで売上高を不当に高く見せることができるというわけです。これを繰り返せば、不当に売上高をつり上げていくことができてしまいます。
 言うまでもありませんが、このような取引が公認されているようでは、売上高という数値そのものに信頼性がなくなります。会社側が好き放題に増やせてしまうのですから。実際、加ト吉の例では連日のように報道が行われた挙句に社長が辞任しましたし、場合によっては上場廃止などの処分になることもあります。
 もう1つ、読売新聞の例についてはどうでしょうか。同社の言い分は「輪転機を処分したはずが、連絡ミスで処分されていなかった」とのことで、追徴課税の原因は「所得隠し」です。ではなぜ「輪転機が処分されていなかった」のが「所得隠し」につながるのでしょうか。この点からして、一般の人にはピンと来ないでしょう。このように、会計処理は非常に分かりづらい分野であり、だからこそ(社長が逮捕された某社のように)不正に利用する輩が後を絶たないのです。
 まず、備品などには「除却」という手続きがあります。要するに「処分」することであると考えてください。これを行うと、例えば処分した備品に100万円の価値が残っていた場合、「100万円の備品を除却した」ということで、100万円の損失が生じたとみなされます。上記で「権利も資産とみなされる」ことを述べましたが、備品などの動産や不動産も当然ながら資産とみなされ、例えば建造物が倒壊したり、備品を廃棄した場合、これは損失とみなされます。
 ここからが重要なのですが、日本の法人税は外形標準課税ではなく、純利益に対して課税されます。それで、この純利益をどのように求めるかといえば、わざわざここで説明するまでもないでしょうが、収益から費用を引けば良いのです。この減算の後に残った金額に対して法人税が適用されます。
 そして、上記の「除却」ですが、これは勘定名を「除却損」といいまして、費用が発生したものとみなされます。すなわち、例えば100万円の備品を除却すると、費用が100万円増えるわけですから、結果として純利益が100万円少なくなることになります。純利益が少なくなるということは、法人税額も少なくなります。
 では、ここで問題です。「備品を除却した」と言いつつ、実はその備品を廃棄せずに取っておいてあったとしたら、一体どうなるでしょうか。当然、除却損が発生したなどという処理は大嘘になります。つまり、不当に費用を多く算出、すなわち純利益を少なく算出したことになり、その分の法人税を支払わなかったことになります。
 追徴課税についても簡単に述べておきましょう。一口に追徴課税といっても、重加算税を徴収される場合とされない場合があります。通常の申告漏れについては申告漏れ分の税額(と利息)を徴収されるのですが、重加算税がつく場合にはこれに加えてさらに多額の税を徴収されることになります。ではこの「重加算」とは何かといいますと、早い話が「罰金」です。悪質性が高く、脱税などの意図があったとみなされる場合のみ、重加算税が請求されるのです。
 読売の申告漏れ問題が単なるミスなのか、それとも別の意図があったのかは、私には分かりません。しかし、読売新聞社は社説で「100%減価償却」案を強く支持するなどしており、法人税を下げさせようという強烈なまでの意図が見えること、及び国税局が単なる追徴ではなく重加算税を用いたのは事実です。後の判断は皆様方にお任せいたしましょう。

 本日もまたもやAjaxなど扱っています。Ajax自体は大して面白い技術ではないものの、他のサーバーサイド言語の機能と組み合わせたり、JavaScriptの記述と組み合わせたりできる点は面白いです。
 さて、まずはAjaxでクッキーは使えるのでしょうか。無論、JavaScriptのクッキー関数を使えばクッキーを使うことはできるでしょうが、問題はヘッダでクッキーを宣言した場合です。また、Ajaxによるリクエストを受け取ったサーバーは、クッキーを読むことができるのでしょうか。Ajaxレスポンスのヘッダが無視されるのであれば、あるいはリクエスト時にクッキーが送られないのであれば、クッキーを使うことができません。レスポンスヘッダによるクッキーの設定が無視されるだけであれば、JavaScript側でクッキーを書き込んでやれば何とかなりますが、リクエストにクッキーのデータがない場合には厄介です。
 クッキーが使えれば、セッションなど様々な効果をつけることができます。しかし、クッキーが使えないようであれば、JavaScriptで引数でも使って強引にクッキーデータを送ってやらない限り、かなり機能が制限されます。
 何はともあれ、とにかく試してみることにしました。もし使えるようであれば、今後の開発に大いに利用させていただきますし、使えないのならそれはそれでまた考えます。
 そうして作ったのが以下のスクリプトです。
#!/usr/bin/perl

&param;

if($set{'mode'} eq ""){
	my $count = &getcookie("count");
	&setcookie("count" , $count + 1 , 60*60*24*7);
	&head; &form; &end;
}
if($set{'mode'} eq "ajax"){
	&ajax;
}

sub ajax{
	my $count = &getcookie("count");
	&setcookie("count" , $count + 1 , 60*60*24*7);

	print <<"EOF";
Content-type:text/html

count:$count
EOF
}

sub form{
	print <<"EOF";
<form onSubmit="ajax.ajaxRequest();return false">
<input type="submit" value="送信">
</form>

<div id="result">...</div>
EOF
}

sub head{
	print <<"EOF";
Content-type:text/html

<html>
<head>
<title>Ajax Cookie Test</title>
<script language="JavaScript">
function YAjax(){
	this.YAjax();
}
YAjax.prototype.YAjax = function(){
	this.createRequest();
	this.headers = new Array();
	this.headers.unshift(new RequestHeader("Content-type" , 
		"application/x-www-form-urlencoded"));
}
YAjax.prototype.getRequestHeaders = function(){
	return this.headers;
}
YAjax.prototype.setRequestHeaders = function(h){
	this.headers = h;
}

YAjax.prototype.createRequest = function(){
	try{
		this.ajax = new ActiveXObject("Microsoft.XMLHTTP");
	}catch(e){
		this.ajax = new XMLHttpRequest();
	}
}
YAjax.prototype.init = function(){
	if(this.ajax == null){
		this.createRequest();
		if(this.ajax == null){
			return false;
		}
	}else{
		this.ajax.abort();
	}

	if(this.ajax != null)
		this.prepareCallback();
	return true;
}
YAjax.prototype.isSucceed = function(){
	if(this.ajax.readyState == 4 && this.ajax.status == 200)
		return true;
	return false;
}
YAjax.prototype.getAjax = function(){
	return this.ajax;
}
YAjax.prototype.requestCallback = function(){
	if(this.isSucceed()){
		this.onResponse(this.ajax.responseText);
	}else if(this.ajax.readyState == 4){
		this.onError();
	}
}
YAjax.prototype.onResponse = function(text){
}
YAjax.prototype.onError = function(){
}
YAjax.prototype.prepareCallback = function(){
	if(this.ajax != null){
		var selfobj = this;
		this.ajax.onReadyStateChange = function(){
			selfobj.requestCallback();
		}
	}
}
YAjax.prototype.request = function(url , args , method , async){
	this.init();
	this.onRequest(url , args , method , async);
}
YAjax.prototype.onRequest = function(url , args , method , async){
	if(method == null || method == "")
		method = "GET";
	if(url == null)
		url = "";
	if(args == null)
		args = "";
	if(async == null)
		async = true;

	var urls = this.parseURL(url , args , method)

	var ajax = this.getAjax();
	ajax.open(method , urls[0] , async);

	for(var i = 0; i < this.headers.length; i++)
		ajax.setRequestHeader(this.headers[i].getName() , 
			this.headers[i].getValue());

	ajax.send(urls[1]);
}
YAjax.prototype.parseURL = function(url , args , method){
	var send_url = url;
	var send_args = "";

	if(method == "POST"){
		send_args = args;
	}else{
		if(send_url.charAt(send_url.length() - 1) != "?" 
			&& args != ""){
			send_url += "?";
		}
		send_url += args;
	}

	return new Array(send_url , send_args);
}

function RequestHeader(name , value){
	this.RequestHeader(name , value);
}
RequestHeader.prototype.RequestHeader = function(name , value){
	this.name = name;
	this.value = value;
}
RequestHeader.prototype.getName = function(){
	return this.name;
}
RequestHeader.prototype.getValue = function(){
	return this.value;
}

var ajax = new YAjax();
ajax.ajaxRequest = function(){
	this.request("?" , "mode=ajax" , "POST");
}
ajax.onResponse = function(res){
	document.getElementById("result").innerHTML = res;
}
</script>
</head>
<body>
EOF
}

sub end{
	print <<"EOF";
</body>
</html>
EOF
}

sub setcookie{
	my $cookie_name = $_[0];
	my $save = $_[1];
	my $cookie_time = $_[2];

	my @mon_data = ("Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" , 
		"Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec");
	my @week_data = ("Sun" , "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat");

	my($sec , $min , $hour , $day , $mon , $year , $week , undef) = 
		localtime(time + $cookie_time);

	my $expires = sprintf("%s, %02d\-%s\-%04d %02d:%02d:%02d GMT" , 
		$week_data[$week] , $day , $mon_data[$mon] , 
		$year + 1900 , $hour , $min , $sec);

	$save =~ s/(\W)/sprintf("%%%02X" , unpack("C" , $1))/eg;

	$cookie_name =~ s/(\W)/sprintf("%%%02X" , unpack("C" , $1))/eg;

	print "Set-Cookie: $cookie_name=$save; expires=$expires\n";
}

sub getcookie{
	my $cookie_name = $_[0];

	my $cookie_get = $ENV{'HTTP_COOKIE'};
	$cookie_get =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack("C" , hex($1))/eg;
	my @cookies = split(/; */ , $cookie_get);

	for(my $i = 0; $i < @cookies; $i++){
		my($name , $value);
		if($cookies[$i] =~ /=/){
			$name = $`;
			$value = $';
		}
		if($cookie_name eq $name){
			return $value;
		}
	}
	return undef;
}

sub param_parse{
	my $buffer = "";

	if($ENV{'REQUEST_METHOD'} eq "POST"){
		read(STDIN , $buffer , $ENV{'CONTENT_LENGTH'});
	}else{
		$buffer = $ENV{'QUERY_STRING'};
	}

	&param_form($buffer , $_[0]);
}

sub param_form{
	my @pairs = split(/&/ , $_[0]);
	my $set = $_[1];
	foreach(@pairs){
		my($name , $value) = split(/=/ , $_);
		$value =~ tr/+/ /;
		$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C" , hex($1))/eg;
		if($set->{$name} eq ""){
			$set->{$name} = $value;
		}else{
			$set->{$name} .= "," . $value;
		}
	}
}

sub param{
	&param_parse(\%set);
}
 やはりPerlは書いていて楽です。ただ、JavaScriptが絡んでくると、途端に厄介になってしまうのではありますが。とはいえ、私が単にJavaScript嫌いなだけで、言語の中に言語を絡めるテクニック自体は嫌いではありません。これを無粋などと称し、フレームワークに組み込んだ上で排除しようという動きもあるようですが、ネイティブランゲージが複数ある開発者に1つの言語しか扱わせない方が無粋でありましょう。
 PerlはJSPやPHPと違い、引数のパースやクッキー読み書き機能が標準で用意されていないため、自作のサブルーチンをいくつか定義しています。意味は大体見ての通りです。といっても、今回重要なのはそこではなく、ajaxサブルーチンなのですが。
 ajaxサブルーチンは引数がmode=ajaxの場合に呼ばれ、クッキーの値を1つ増加させる効果を持っています。mode=ajaxが呼ばれるのは、Ajaxリクエストを行った際です。すなわち、Ajaxでクッキーが使えるのなら、読み込むたびにクッキーの値が増えていくことになるのです。
 実際にやってみたところ、見事にクッキーの値が増えていくらしいことが分かりました。Ajaxではクッキーが使えると考えて間違いなさそうです。これで開発の幅が広がります。かなりありがたいです。とはいえ、Ajax通信時にクッキーなどが使えない通信機構を新しく用意するより、ブラウザそのものが持つ通信機構を使いまわした方が実装も楽で効率的ですから、不思議はありませんが。
 ついでにもう1つ試してみるとしましょう。果たしてAjaxではJavaScriptが使えるのでしょうか。Ajaxを使用し、次々とJavaScriptのコードを書き換えることができれば、かなり実装の幅が広がるはずです。
 先ほどのコードを改造して確かめてみることにしました。まず、headサブルーチンの最後の部分を次のように変更します。
function innerAlert(){
	alert("デフォルトの innerAlert です");
}
</script>
</head>
<body>
EOF
 Ajaxレスポンスで関数を上書きしてから呼び出した際、上書きした動作になれば、AjaxでJavaScriptを書けることになります。
 ただ、1度定義された関数を上書きするには、次のような方法ではいけません。
function innerAlert(){
	// 新しい実装
}
 JavaやC++ではオーバーライドしなければメソッドの定義を変更することはできません。これはオブジェクト指向の理念からしても当然ではあります。Perlでは匿名サブルーチンについては上書きできます。ではJavaScriptはどうかといいますと、JavaScriptにおける関数はすべて「関数が格納された変数」です。しかも「すべての関数はオブジェクト」という意味不明な特性まで備えています。
 つまり、あらゆる関数のオブジェクトは「new FunctionName(...)」で生成できてしまい、また「function innerAlert(){}」はinnerAlert変数に関数の中身が格納されたものとみなされます。極めて違和感がありますが、JavaScriptはそういう言語なのですから仕方ありません。
 で、肝心の上書きの方法ですが、
innerAlert = function(){
	// 新しい実装
}
 これです。innerAlertは変数ですから、新しく関数を代入してやれば良いわけです。
 これを踏まえて、ajaxサブルーチンを次のように書き換えます。
sub ajax{
	my $count = &getcookie("count");
	&setcookie("count" , $count + 1 , 60*60*24*7);

	print <<"EOF";
Content-type:text/html

<script language="JavaScript">
innerAlert = function(){
	alert("書き換えられた innerAlert です");
}
</script>
EOF
}
 これでHTMLが書き換わり、scriptタグが実行されさえすれば関数が書き換わることになります。このJavaScriptを呼び出せるようにするため、formサブルーチンも改変します。
sub form{
	print <<"EOF";
<form onSubmit="ajax.ajaxRequest();return false">
<input type="submit" value="送信">
</form>

<div id="result">...</div>
<b onClick="innerAlert()">メッセージ</b>
EOF
}
 適当ですが、テストですから良いでしょう。
 早速実行してみたところ、JavaScriptは一切書き換わらないことが分かりました。つまり、JavaScriptにJavaScriptを組み合わせたようなプログラムは書けないということです。おそらく、innerHTMLで書き換えたscriptタグをわざわざ再評価したりはしないのでしょう。できないものは仕方ありませんが、何とかならないものでしょうか。
 実際、AjaxからJavaScriptが実行できれば、かなり実装が広がるはずです。簡単にはあきらめきれません。最後の悪あがきにあれこれ考えてみました。

 そういえば最近、全く3Dライブラリの実装が進んでいないような。どう実装したものか見当もつかないわけですが。ところで、遠近法適用の際に自分の視点と同じZ深度にあるオブジェクトをどう表現するか、これは非常に悩んだものです。視点が数学で言うところの点、すなわち「場所だけを表し、幅も長さもない」ものである場合、大きさがないのですから、その点とZ深度が同じでありながら、その点と重ならない場所にある物体の位置は無限に離れたものとなってしまいます。これでは計算できるはずもなく。そういえば、ブラックホールにも「特異点」なる似たような性質の点があったような。質量無限大で計算が当てはまらない点なのだそうですが、似たようなものでしょうか。ブラックホールは光の速度を超える強烈な重力を持っており、入ってしまうと物質は分解されてしまうとか。物質といえば、物質に反物質をぶつけると巨大なエネルギーを出して消滅し、そのエネルギーはニュートリノによる欠損分を除けば質量分になるそうな。逆にエネルギーから物質と反物質を作り出せるとか。目に見えないほどの量で燃料何千リットルというエネルギーを供給できるのは良いものの、今のところ保存すらできない状態で、それがいつになるかは不明のようです。しかし、これがもし軍事利用されるようになれば地球は終わりでしょう。いわゆる「反物質の冬」です。ところで、冬といえば「麦わら帽子は冬に買え」という有名な格言がありますが、冬に麦わら帽子が陳列されているのを見たことがないような。かえって品揃えが悪くて高いのでは。そうでなくても経済学や経理には良く分からないことが多く、今でもそこかしこで不正経理が発覚する始末。発覚して責任を取るような会社はまだ良いとして、どこぞの会社のように不正経理と他人の迷惑を顧みぬ傍若無人な行動でのし上がった会社もあり、そのくせ逮捕されて裁判にかけられている某被告はそれを恥じるでもなく開き直り、能力など全くないくせに威張る・・・イバる・・・

 私は一体何年スクリプト言語を扱っているのでしょう。まさかevalの魔法を忘れていたとは。そういうわけで、早速試してみるとしましょう。
 まずajaxサブルーチンをこのように書き換えます。
sub ajax{
	my $count = &getcookie("count");
	&setcookie("count" , $count + 1 , 60*60*24*7);

	print <<"EOF";
Content-type:text/javascript

innerAlert = function(){
	alert("書き換えられた innerAlert です");
}
document.getElementById("result").innerHTML = "count:" + $count;
EOF
}
 見ての通り、レスポンスをJavaScriptコード化しています。
 同時に、headサブルーチンの最後の部分をこのように書き換えます。
ajax.onResponse = function(res){
	eval(res);
}

function innerAlert(){
	alert("デフォルトの innerAlert です");
}
</script>
</head>
<body>
EOF
 これでmode=ajax時のレスポンスはJavaScriptコードとみなされることでしょう。
 結論は言うまでもなく見事に動作してくれました。これでかなり色々なことができそうです。といっても、所詮Ajaxなどお遊び以上のことには使えませんし、そこまで色々なことをしてどうするのかは知りませんが。
 とにかく、そのうちこれで何か一品作るとしましょうか。
カテゴリ [開発魔法][社会問題][経済・知的財産] [トラックバック 0][コメント 0]

仲たがいする方々
2007/04/20(Fri)22:20:58
 長崎市長銃撃事件について、先に「これまでに発生した言論封殺テロでは、狙われたのが政府の意に反するような人であった場合には、政府は積極的な非難を行わなかった」と記述しましたが、まさにそれを地で行くかのような安倍コメントが発表されたことをご存知の方は多いでしょう。何と「真相究明を望む」意向を示すのみで、犯人に対する厳しい意見や言論封殺に対する憤りは述べられていないです。
 さすがに批判を浴びたようで、コメントを強めのものに差し替えたようですが、このあまりにもぞんざいな対応に対する私の意見は「やっぱり」です。安倍内閣の性質上、小泉政権時の政治家宅放火事件のようなぞんざいなコメントが出されるのではないかと予想していたら、案の定といいましょうか、的中してしまいました。予想はしていましたが、むなしいものです。
 この予想に至った経緯について少々説明しておきましょう。安倍内閣に限らず、政府は状況次第で地方自治体や住民と衝突してきました。公害や土地収用などもその1つですが、他にも原発、基地、平和などの点で主張がかち合うことは珍しくないようです。原発に関しては白羽の矢が立った地域の自治体や住民、基地については沖縄や他の移転候補地と対立することになります。
 では「平和」とは。小泉及び安倍内閣になってから、自衛隊のイラク派遣などがなし崩し的に進められ、憲法改正も現実味を帯びてきました。どうやら安部内閣は集団的自衛権を行使可能にし、ゆくゆくは憲法9条も変えたいと願っているようです。ところが、長崎や広島は原爆を落とされた場所であり、戦争の凄惨さを伝える建造物なども大量に残っています。市長などにも集団的自衛権や憲法9条改正に慎重な人が多いようです。平和と反戦を訴えて襲われた前の市長のように、その信念を口にしたり、行動で示したりする人も多いようです。
 こうしたことから、長崎市長の考えや行動は完全に安倍内閣の方針と衝突します。その結果が今回のコメントというわけです。とはいえ、いくら主張がかみ合わない相手であっても、何の落ち度もないのに突然射殺されたとなれば、ましてや暴力団追放行動を逆恨みされて殺されたとなれば、普通は厳しいコメントを出すものです。ところが、残念ながら小泉内閣と安倍内閣だけは違うのです。
 ここで想像していただきたいのは、ここに集団的自衛権の行使と憲法9条改正を強く訴える政治家がいたとして、この人が非合法組織から突然射殺されたという状況です。果たして安倍氏は「真相究明を望む」なる味気ないコメントだけで済ませようとするでしょうか。犯人に対する憤りをぶちまけ、テロは許さないと力説するのではないでしょうか。私が問題視するのはここなのです。

 HOYAとペンタックスの合併があさっての方向に。最初は友好的な対等合併を目指していたようですが、紆余曲折の末にHOYAがペンタックスへのTOBも検討すると言い出し、ペンタックス側は「そんな話は聞いていない」と大混乱。HOYAがTOBの機会をうかがう一方、ペンタックスはりそな銀行との関係を強化し、それを牽制しています。
 このところ、日本にも様々な合併あるいは合併未遂がありました。どこかと合併しなければ危ういことが最初から分かっている中堅企業に対し、複数の大手が合併を提案して合併後の計画を示し、中堅企業はその「マニフェスト」を比べた上で自ら合併先を決めた、というのが今のところ最もスマートな買収合戦の形でした。
 逆にあまりにこっけいなものとしては、売るに売れなくなった某社株を抱えたファンドが、お坊ちゃんが経営する企業に目をつけ、お坊ちゃん社長をおだてて操縦。ルールを無視した買収方法を伝授し、某社株を大量に買わせた裏で自分は売り抜け、という例がありました。結局、某社は「子会社が親会社の株を大量に握る」というスキにつけ込まれてこのような状況に追い込まれたのであり、蛮行を行ったお坊ちゃんやファンドのリーダーは今では被告の身です。因果応報といったところでしょうか。
 何とも野蛮なことに、とにかく「侵略戦争」を仕掛けて失敗した例といえば、かの「ティシュー王子」問題です。そういえば、日本で敵対的買収が功を奏した例はほとんどない気がします。これはおそらく日本に限ったことではなく、友好的に話を進められるのならその方が利益が大きいに決まっています。
 HOYAとペンタックスの場合、最初は友好的に話を進めていたのが、どうやら今では相互不信の状態にあるようです。友好的買収において、相互に油断ならないほどの不信感を生じるというのはかなり致命的です。かといって敵対的買収は失敗率も高く、成功したからといって良い結果を生むとは限りません。
 ちなみに、ペンタックスはカメラ事業にHOYAの技術を応用したいと考えているにもかかわらず、HOYAはペンタックスのカメラ事業を切り捨てて利益を強化しようとしているらしく、方針から違っています。目的が一致しない状況では、敵対的買収によって反発や士気の低下を招く可能性も高いことになります。
 こうなってしまったからには、再び努力して合併の流れを再構築するか、ダメなら合併をあきらめることが最善ではないでしょうか。
 ところで、楽天が再びTBSに手出しをしようとしているようです。楽天ももはや余計なことをしない方が自分のためなのでは。下手なことをすれば楽天の株(「株」といっても「株式」ではなく「お株」、つまり評判の方)が暴落するのは見えています。正直なところ、TBS株問題以降、私は楽天に良い印象を持っていません。これ以上問題を複雑にするようなら、所詮はライブドアと同類でしょう。
 TBSや同業者についても同じです。今後楽天が具体的な行動に出てくる可能性もありますが、その際に「ジャーナリズムを脅かす」「インターネットに比べて既存のマスメディアは優秀」などという意味不明な主張をひたすら並べ立てるのはやめるべきです。事実、ライブドアがニッポン放送に手を出した時の、新聞などの「ネット中傷」のひどいことといったら。本音を言わせていただけば、あの時ほどライブドアとフジサンケイグループの共倒れを願ったことはありません。
 また、敵対的買収防止策についての標準的な規定も定められるべきです。TBSは一応、敵対的買収防止策を作成していますが、これの有効性は極めて怪しいものです。もし楽天が本気でTBSを買収する気であれば、確実に司法での泥沼の争いになります。買収されそうな会社が買収防止策を発動しようとするたび、その是非が司法に持ち込まれるような状況は極めて問題です。防止策を盛り込んではみても、それが本当に行使できるかどうかは、実際に買収を仕掛けられるまで分からないのです。
 三角合併も解禁され、外資系企業やグリーンメーラーが大量に流入してくる可能性があるにもかかわらず、この状態はあまりにあいまい過ぎますし、危険でさえあります。TBSの「本当に役立つのか分からない買収防止策」を見るまでもなく、何とかすべきです。

 Ajaxとは何ぞや。「Asynchronous JavaScript + XML」なのですが、この略語には首をひねるばかりでした。略語にしてはあまりに下手すぎるのです。なぜ「JavaScript」の部分から2文字も取るのか全く不明ですし、「ja」では「Java」を連想しかねません。JavaとJavaScriptは完全なる別物ですから、このような勘違いの火種を残しておくのは問題です。
 ですから、普通に考えれば「AJX」か「AJSX」辺りが適当です。しかも、AjaxはもともとXMLを読み込む技術ですが、まじめにXMLを読み込んでいる人などごく少数です。JavaScriptで動作するHTTPソケットとして使われているのが現実です。すなわち、「Asynchronous JavaScript +」の次には「XML」ではなく「HTTP」や「Connection」、「Socket」などが来る方が自然なのです。略語は「AJH」「AJC」「AJS」のようになるでしょうか。
 さらに言えば、Ajaxでは非同期通信もできますが、同期通信もできます。そもそもスレッドなりを実装できる言語であれば、ソケットを使って非同期通信を作る程度のことは簡単ですから、「非同期」がことさら強調されるべきものとも考えられません。それなら「JavaScript HTTP Connector」(JHC)や「JavaScript HTTP Socket」(JHS)などとする方が適切なはずです。
 それなのになぜ「Ajax」という意味不明な名前になったのか。どうやら「Ajax」には意味があるようです。神話の固有名詞、軍艦の名前、サッカーチームの名前などに「Ajax」が存在するそうな。そして何といっても、米国には「Ajax」という洗剤があるらしいのです(ColgateのAjax紹介ページ)。
 実装の名づけに迷った挙句、とりあえず「ジョイ」とでもつけるようなノリなのでしょうか。ちなみに、米国にはカービィという掃除機があると聞いたことがあります。似たようなネタでは、マリオシリーズに登場する悪役の名前がどうしても決まらず、誰かが「クッパでも食べに行くか」と言ったことで「クッパ」に決まったとか(ちなみに英語版での名前は「Bowser」です)。
 とにかく、Ajaxとは何ともセンスのない名前と考えていたら、こういう裏があったのです。それで日本人には理解しがたいネーミングになっていたのですか。技術に洗剤の名前を付ける行為自体が理解しがたいことに変わりありませんが。ここは「神話から取った」ということにしておきましょう。
 ところで、Ajaxの登場によって「Comet」という技術が注目されています。HTTPは通常、リクエストを受け取ったら即座にレスポンスを返すものですが、それをあえて返さないようにしておき、任意のタイミングでリクエストを返すことで擬似的にリアルタイム通信を実現する技術です。Ajaxと組み合わせて使うことで、リアルタイムに近い作業が可能になります。
 で、「Comet」とは一体何なのでしょう。Ajaxには一応正式名称がありましたが、Cometに関しては正式名称を聞いたことがありません。正式名称がないなら、「Comet」と名づける意味もありません。なぜ「Comet」なのでしょうか。
 結論から言えば、これはアメリカンジョークでした。Cometといえば夜空を見上げたくなるような名前ですが、実際にはそういうロマンチックな経緯で名づけられたわけではないようです。それというのも、米国には「Comet Cleanser」という洗剤があるのです(Prestige BrandsのComet紹介ページ)。AjaxとCometは競争相手になります。
 なるほど、これがAjaxとCometのせんざい能力というものですか。何でも良いですが、技術の世界にこういう意味不明なことを持ち込む必要はないのでは。それ以前に、わざわざ略語に意味のある言葉を当てないで欲しいというのが本音です。紛らわしくなる一方です。もし意味のある言葉を当てるなら、Perl(病的折衷主義ガラクタ出力機)程度のセンスが欲しいところです。その上、読みは「パール」ですが、表記が「Pearl」ではないため、混乱することもありません。もしくはJavaのように略語でなく正式名称自体を単語とするか。
 それで、Ajaxとセットで語られがちなCometですが、実はセットである必要などどこにもありません(セットにしたものは以前にこちらの記事で作成済みです)。Ajax単体、またはComet単体で使っても何ら問題ないのです。とはいえ、Cometはブラウザそのもので使うには少々難のある技術です。ではどうすべきかといえば、アプリケーションを書いて使えば良いのです。
 無論、アプリケーションで非同期通信を行いたければ、ソケットを使うのが最も良いのですが、HTTPサーバーを使う必要があったり、ポートが開いていないなど、それができない状況も少なくありません。そうした場合にはCometの出番です。
 それではJSPとJavaアプリケーションを書いてみるとしましょう。JSPはフォームになっており、文字データを送信することができます。Javaアプリケーションはタスクトレイに常駐し、JSPでデータが送信されたらそれを検出してバルーンで通知します。
 まずはJSPから。
// comet.jsp
<%@page import="java.io.* , java.util.*" 
contentType="text/html;charset=Shift_JIS" %>

<%!
private CometData comet;
private ArrayList<String> datas;

class CometData{
	private String data;
	public String getData(){
		return data;
	}
	public void setData(String d){
		data = d;
	}
}

public void jspInit(){
	super.jspInit();
	comet = new CometData();
	datas = new ArrayList<String>();
}
%>

<%
request.setCharacterEncoding("Shift_JIS");

String mode = request.getParameter("mode");

if("check".equals(mode)){
	// Comet による擬似リアルタイムリクエスト
	String msg = "";
	try{
		synchronized(comet){
			while(msg.isEmpty()){
				comet.wait();	// 待機
				msg = comet.getData();
			}
		}
	}catch(InterruptedException e){
	}

	response.setContentType("text/plain;charset=Shift_JIS");
	out.print(msg);
}else{
	if("add".equals(mode)){
		String data = request.getParameter("data");
		if(data != null && !data.isEmpty()){
			if(data.length() > 50){
				data = data.substring(0 , 50);
			}
			synchronized(this){
				datas.add(data);
			}
			// データ更新を通知
			synchronized(comet){
				comet.setData(data);
				comet.notifyAll();
			}
		}
	}else if("remove".equals(mode)){
		boolean flag = false;
		synchronized(this){
			if(datas.size() > 0){
				flag = true;
				datas.clear();
			}
		}
		// データ削除を通知
		if(flag){
			synchronized(comet){
				comet.setData(
					"データがクリアされました。");
				comet.notifyAll();
			}
		}
	}
%>

<html>
<head>
<title>Comet Application</title>
</head>
<body>

<b>Comet Application</b><br>
 以下のフォームにデータを入力してください。データは50文字までです。

<form method="POST" action="?">
<input type="hidden" name="mode" value="add">
データ : <input type="text" name="data" size="40" maxlength="50">
<input type="submit" value="データの登録">
</form>

<form method="POST" action="?">
<input type="hidden" name="mode" value="remove">
<input type="submit" value="全データの削除">
</form>

<hr>

<%
synchronized(this){
	for(int i = datas.size() - 1; i >= 0; i--){
		out.println(datas.get(i) + "<hr>");
	}
}
%>

<div align="center"><small>yamicha.com Comet Application</small></div>

</body>
</html>

<%
}
%>
 ?mode=checkにリクエストがあったら、まずcomet.wait()で制御を止めます。データに変化が発生した際にはcomet.notifyAll()を呼び出し、待機スレッドを全部再開させます。
 comet変数には通知すべきデータが格納されるようになっており、comet.wait()を抜けたらすぐにString型の変数にデータを代入しています。これはsynchronized(comet)ブロック内で行われており、データの追加などcometが変更される処理もまたsynchronized(comet)ブロック内でのみ行われていますので、少なくともcomet.wait()から復帰し、cometに保持されたデータを取得するまでの間に、cometのデータが他のスレッドから書き換えられることはありません。
 このように、JSPでも少々面倒なマルチスレッドプログラミングですが、Javaアプリケーション側ではもう少し複雑さが増しています。必要なファイルは次の通りです。
Main.java
Looking.java
IconChanger.java
icon.gif - 普段のタスクトレイアイコン
icon_i.gif - 通知があった時のアイコン
 アイコンは好きなように作成して構いませんが、私は次のようなものを用いました。

icon.gif
icon_i.gif

 普段はicon.gifが用いられるのですが、通知後は10秒間だけicon_i.gifに変わります。とはいっても「10秒後にアイコンを戻す」などというメソッドは存在しませんから、IconChangerが10秒後にアイコンを元に戻す処理を受け持ちます。
 それでは実装と参りましょう。起点となるMain.javaは非常に単純です。
// Main.java
public class Main extends Thread{
	public static void main(String args[]) throws Exception{
		Looking look = new Looking();
		look.looking();
	}
}
 処理をすべてLooking.javaに任せていることが分かります。そして、実質的にLooking.javaがほとんどの処理を受け持っています。
// Looking.java
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;

public class Looking{
	private volatile boolean loop;

	private Image icon;
	private Image icon_i;
	private HttpURLConnection connection;

	private SystemTray systray;
	private TrayIcon tray;
	private IconChanger change;
	public Looking() throws Exception{
		Toolkit tool = Toolkit.getDefaultToolkit();
		icon = tool.getImage("icon.gif");
		icon_i = tool.getImage("icon_i.gif");

		loop = true;
		change = null;

		// トレイの構築
		systray = SystemTray.getSystemTray();

		PopupMenu popup = new PopupMenu();
		MenuItem m = new MenuItem("閉じる");
		m.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				synchronized(this){
					loop = false;
					connection.disconnect();
				}
			}
		});

		popup.add(m);

		tray = new TrayIcon(icon , "Comet Application" , popup);
		systray.add(tray);
	}
	public void looking() throws Exception{
		// URL は環境に応じて変更
		URL url = new URL(
			"http://www.localyamicha.com/comet.jsp?mode=check");

		while(true){
			synchronized(this){
				if(!loop)
					break;

				connection = (HttpURLConnection)url.
					openConnection();
				connection.setReadTimeout(0);

				connection.connect();
			}

			try{
				InputStream is = connection.getInputStream();
				String data = "";

				int read = 0;
				byte b[] = new byte[256];

				while((read = is.read(b)) != -1){
					data += new String(b , 0 , 
						read , "Shift_JIS");
				}

				if(is != null)
					is.close();

				// 改行を削除
				int trim = 0;
				while(trim < data.length()){
					if(data.charAt(trim) != '\n' &&
						data.charAt(trim) != '\r')
						break;
					trim ++;
				}
				data = data.substring(trim , data.length());

				tray.displayMessage("データの変更" , 
					"データに変更がありました:\n" + 
					data , TrayIcon.MessageType.INFO);
				tray.setImage(icon_i);

				if(change != null)
					change.setInvalid();
				change = new IconChanger(tray , icon , 10*1000);
				change.start();
			}catch(IOException e){
			}
		}

		if(change != null)
			change.setInvalid();

		systray.remove(tray);
	}
}
 IconChanger.javaは次の通り。当然ながらThreadの継承クラスです。
import java.awt.Image;
import java.awt.TrayIcon;

public class IconChanger extends Thread{
	private TrayIcon tray;
	private Image newicon;
	private long time;
	private boolean valid;
	public IconChanger(TrayIcon trayicon , Image icon , long t){
		tray = trayicon;
		newicon = icon;
		time = t;
		valid = true;
	}
	synchronized public void setInvalid(){
		valid = false;
		interrupt();
	}
	synchronized public void run(){
		try{
			wait(time);
		}catch(InterruptedException e){
		}

		if(valid)
			tray.setImage(newicon);
	}
}
 Cometをアプリケーションから使ってやろうというこのプログラムですが、書いている時には色々な問題に見舞われました。
 その中でも困ったのが、HttpURLConnectionから取得したInputStreamの仕様です。SocketのInputStreamはread()によるブロック中に別スレッドからclose()されるとIOExceptionを投げてくるのですが、HttpURLConnectionでは何とread()のブロックが解けるまでclose()メソッドがブロックされてしまうのです。
 これが何を意味するか。つまり、通信待機中にリスナからプログラムを終了させる通知(上記プログラムではPopupMenuの「閉じる」)が来た場合、InputStreamを強引にclose()してIOExceptionを発生させ、read()によるブロックから制御を戻してプログラムを終了させるということができないのです。もちろん、リソースの解放を考えなくて良いのであれば、メインスレッドAからスレッドBを作り、BからさらにCを作り、Cに通信処理を行わせておいて、BはC.join()で待機、リスナから終了の通知が来たらBに対してinterrupt()で割り込み、join()を強制的に解除して終了させれば良いのですが、これではInputStreamが閉じられません。閉じないとどうなるのかは分かりませんが、少なくとも良いことは起こらないでしょう。
 仕方がないので、InputStraemのread()のブロックに割り込むのはあきらめ、HttpURLConnectionのgetInputStream()に割り込むことにしました。JSPでは、outに対して何も出力していない状態であれば、out.flush()がなされるまで実際にデータが送られることはないようですので、JSPが実際にデータを送り始めるまではgetInputStream()でブロックされた状態が続くことになります。JSPが何らかのデータを送り始めると、getInputStream()からInputStreamが返り、読み込み操作を実行できるようになります。
 アプリケーション終了の通知が送られてきたら、HttpURLConnectionのdisconnect()で割り込みます。するとgetInputStream()がSocketException(extends IOException)を投げてくるため、ブロックから抜け出すことができます。
 この動作を正常に行うために、プログラムではsynchronized()ロックを使うようにしています。Looking.javaでsynchronizedを使っているのは以下の2箇所です。
// 1.入れ子クラス内
PopupMenu popup = new PopupMenu();
MenuItem m = new MenuItem("閉じる");
m.addActionListener(new ActionListener(){
	public void actionPerformed(ActionEvent e){
		synchronized(this){
			loop = false;
			connection.disconnect();
		}
	}
});

popup.add(m);
// ...

// 2.looking() 内
public void looking() throws Exception{
	// URL は環境に応じて変更
	URL url = new URL(
		"http://www.localyamicha.com/comet.jsp?mode=check");

	while(true){
		synchronized(this){
			if(!loop)
				break;

			connection = (HttpURLConnection)url.openConnection();
			connection.setReadTimeout(0);
			connection.connect();
		}

	try{
		InputStream is = connection.getInputStream();
		String data = "";

		// ...
 特に2番目の
while(true){
	synchronized(this){
		if(!loop)
			break;
 の部分、「while(loop)」にすれば良いではないかと指摘されそうですが、もちろん理由があります。
 loopをsynchronized内で評価し、そのsynchronized内でconnectionに新しいHttpURLConnectionを割り当て、接続を行っていますが、このような実装にしなかった場合、次のようなことが発生しかねないのです。

1.looking()実行中のスレッドがloopを評価、trueのため続行
2.actionPerformed()のスレッドがloopをfalseにし、connection.disconnect()を実行
3.looking()のスレッドがconnectionに新しい接続を代入し、接続を開始。disconnect()された接続はあわれガベージコレクタのお世話に
4.looking()のスレッドがgetInputStream()でブロックし、次のメッセージが送られてくるか、再び「閉じる」動作が実行されるまで、終了することができない

 上記実装では同じsynchronizedブロックでloopを評価した上、connectionを生成していますから、このsynchronizedブロックに突入する以前にloopがfalseにされていればループを抜けますし、synchronizedを抜けた後でloopがfalseになり、connection.disconnect()されたとしても、その後のgetInputStream()ですぐに例外を投げてくれます。マルチスレッドプログラミングはスリリングといいましょうか。
 こうして作ったからには、実際に使ってみましょう。まず「java Main」でプログラムを実行し、comet.jspで適当なメッセージを送ってみます。さて、送ったメッセージが通知されたでしょうか。


 通知を受けた画面。何やら本当にしゃべっているように見えるのですが。

 このアプリケーションですが、例えばチャットの入室者を監視するプログラムなどに応用できそうです。がんばればPerlなどでも作れないことはありませんし、単なるHTTPですのでソケットやポートなどの面倒なことを考える必要もありません。
 それにしてもバルーン表示は便利です。以前にC++で書いた時には、MSN Messenger風にログインを表示しようとしてウィンドウの扱いに四苦八苦したのですが、Javaならバルーンがこれほど簡単に使えてしまう上、タスクトレイさえサポートしていればどのOSでも動くのですから。
 とにかくCometは色々な使い方があるということで。「CometはWeb 2.0において重要な技術である」などという宗教団体の勧誘のようなことは言いませんが、「それなりに面白い」ものではあります。
カテゴリ [開発魔法][社会問題][経済・知的財産] [トラックバック 0][コメント 0]

貿易自由協定
2007/04/04(Wed)21:06:53
 東国原知事が「タミフルを飲んだから異常な言動をするかもしれない」と称して少々過激な発言をするユーモアなどもありましたが、このほどタミフル耐性ウイルスが人から人に感染したことを示すデータが米国の専門雑誌に発表されました。
 もともとウイルスは突然変異を起こしやすい性質を持っており、新薬をかいくぐるようにして耐性ウイルスが出現することがたびたびあります。ですから、タミフルに耐性を持つウイルスの登場は別に不思議ではありませんし、十分予想できたことです。それどころか、実際にはこの耐性ウイルスがそれなりに蔓延している可能性が高いと考えられます。
 これまで日本では、すさまじいまでにタミフルが乱用されてきました。従って、異常行動のような副作用の件数も諸外国に比べて圧倒的に多くなりますし、何よりウイルスが耐性を持つのには絶好の環境です。こうしたことから、日本のインフルエンザは外国より高い割合で耐性を持っていると考えるべきでしょう。
 いくらグローバル化が進んだとはいっても、日本は島国です。地続きが多い外国とは違い、ウイルスの出入りはかなり少なくなります。ご丁寧にも、そういうビニールハウス内に耐性ウイルスが生じやすい環境を用意しているのが現在の日本です。従って、様々な国の中でも日本は高いリスクを背負っていることが分かります。
 とにかく恐ろしいのは、こうして耐性を得たウイルスが、タミフルが効かなければ命にかかわる患者に感染してしまうことです。タミフルを必要以上に乱用したことにより、死ななくても良い患者が死ぬことになるのです。タミフルは確かに強力で優れた薬なのですが、異常行動、内臓障害死、耐性ウイルスの出現といったリスクを背負ってまで健康な人に対しても乱用すべきかといいますと、そのような道理は一切ありません。
 タミフル耐性ウイルスは健康な人にとってはどうということはありませんが、弱者にとっては命にかかわります。最悪のケースとして、鳥インフルエンザがタミフルに耐性を持てば、人類は新ウイルスへの手立てを失ってしまいます。日本人がタミフルを乱用することで利益を得るのは中外製薬とロシュ社だけです。それでもなお、日本人はタミフルを乱用すべきでしょうか。それとも、必要な人にピンポイントで使うようにすべきでしょうか。
 確かにインフルエンザの症状は辛いでしょう。早く職場なりに復帰したいというニーズも分からなくはありません。しかし、健常者なら感染しても命に別状のないインフルエンザを1日程度早く治癒するために、弱者にとって危険な耐性ウイルス出現のリスクを高め、さらには耐性鳥インフルエンザのリスクまで高めるなど、身勝手というものです。自分さえ良ければ弱者などどうなっても良いというのでしょうか。
 結論は分かりきっていますが、「タミフルは効率良く使いましょう」ということで。健常者に対して乱用するなどの非効率的な使い方はやめるべきです。

 米韓FTAが話題になっています。日本も積極姿勢に転じるべきとの声があれば、あせる必要はないとの声もあり。では実際のところ、一体どうすれば良いのでしょうか。
 無論、あまりにのんびりしすぎるとタイミングを逃して大変なことになりますが、急ぎすぎて中途半端な交渉に終わるのも困ります。ましてや、日本はこれから人口が減少することになりますし、それを見越した諸外国も「自国の人材の受け入れ」を条件の1つとして提示してくると考えられ、単に輸入関係の契約に留まらない難しさがあります。
 しかし、それよりさらに困るのが、日本はFTA交渉がこの上ないほど下手という事実です。日本は食糧とエネルギーのほとんどを外国に頼っており、さらに経済規模も大きく、外国にとっては交渉しがいのある相手のはずなのですが、とにかく上手くまとまらないのです。期待を持って始まったはずのFTA交渉が散々時間を食った末に結局決裂したり、成立しても大山鳴動ねずみ一匹。これがいつものパターンです。
 日本の交渉力や決断力が低いのもあるのでしょうが、関係者がとにかく自国産業の保護に熱心である点も大きな原因の1つです。FTAは通常、税の軽減や輸入量の増加、人材の受け入れなどの条件を相互に出し合い、自分に有利な条件を認めてもらう代わりに相手の条件を飲んで成立するものなのですが、自分が条件を飲まないのに相手が条件を飲んでくれるはずもありません。結果、交渉が決裂したり、ほとんど意味のないような条件でFTAが締結されてしまうのです。
 日本と諸外国のFTA交渉の際には、日本が相手国に工業製品の輸入条件改善を要求し、逆に相手国は日本に農作物などの輸入条件改善、あるいは人材の受け入れを要求してくることが多いです。これは日本の性質からしても妥当な交渉ではあるのですが、「下手に安価な農作物を輸入しては国内の農業に影響を与える」というのが交渉を決裂あるいは縮小させた関係者の言い分です。言いたいことは分かりますが、これでは誰とも永久に交渉が成立しません。
 国産の農作物は信頼性が高いですから、安い輸入作物が入ったからといって日本産が滅亡するとは考えられませんし、それを受け入れる代わりに日本の主要産業の輸出が有利になれば明らかにプラス収支のはずなのですが、これがまた場合によっては利権政治家が絡んでいたりします。これでは成立するものも成立しなくなって当然です。
 こうしたことから、農作物にはやたら高い税率がつけられている場合が多いです。例えば日本の数少ない主要農作物である米ですが、税関の関税表によると402円/kgとのこと。輸入米を販売しようとすると、相当安価な米でも大変な額になってしまうわけです。ここまで露骨なことをしなくても、日本人の多くは少々高くても日本米を食べる気がしなくもありませんが、ともかく交渉相手国が下手に「米の輸入条件の緩和」などの条件をつけてきたら、蜂の巣をつついたような騒ぎになった末に決裂するのがオチです。他の農作物の場合にはここまで極端ではないにしても、決裂や規模縮小が起こるのも分かるというものです。
 FTAに対する懸念は、とにかく「日本はFTA交渉が非常に下手である」の一点に集約されます。特定の品目について、普通の規模の国が1国2国FTAを結んでいる程度ならまだ良いのですが、日本以外の主要輸出国がほとんどFTAを結んでしまうと大変です。日本の製品は通常の税、他国の製品は交渉で定められた安価な税となれば、同価格の同等品であるにもかかわらず、日本の製品のみ値段が高いということが発生しかねません。日本はとにかく輸出重視の国ですから、このような状態になるようでは致命的です。
 結局のところ、米韓のFTAを引き合いに出して自国のFTAを論じるより、まずは交渉力を何とかしてもらわなくては困ります。ただでさえ日本の交渉はスローペースなのですから、のんびりしていては出遅れるばかりですし、だからといってあわてて交渉を進めようにも、こういう状態では交渉は成立しません。日本がFTAで出遅れるのにはそれなりの理由があるのであって、これを改善しない限り結果は同じです。

 以前に扱ったJMSとMessage Driven Bean。非同期処理を行わせるには結構便利そうなのですが、さらに何とも便利なことにメッセージを受け取るキューを限定することができます。最初はメッセージに特定のIDか何かを持たせて区別するのかと考えていたのですが、実際にチュートリアルなどを調べてみたところ、それよりさらに柔軟な方法でメッセージを識別するらしいことが分かりました。これは試してみなければ。
 まずはJMSの設定から。先にAppServerをglassfishに変えたことで、以前のJMSの設定はすべてなくなっていますので、再びJMSのファクトリやリソースを作らなければなりません。
 といっても、以前に行ったことですから簡単なものです。まずAdminコンソールの「Resources/JMS Resources/Connection Factories」を開き、コネクションファクトリを作成します。今回はQueueを使いますので、ファクトリは「QueueConnectionFactory」とし、JNDI名は「jms/QueueConnectionFactory」としました。プロパティ値のuserName及びpasswordは両方とも「admin」に設定します。
 次に「Resource/JMS Resources/Destination Resources」を開き、JNDI名を「jms/Queue」、プロパティ値のnameを「queue」とでもして手早くDestination Resourceを作成しようとしたのですが、何とリソース登録ボタンが存在しません。入力フォームとプロパティのリスト、さらにプロパティ項目追加用のボタンなら存在するのですが、リソースを登録するボタンがどこにも存在しないのです。言うまでもありませんが、ページを開きなおしても同じです。全くもってボタンらしきものは見えません。
 明らかにバグなのでしょうが、これでは困ります。以前に行った作業だからとタカをくくった挙句、うっかりミスで失敗するのならまだしも、まさかボタンがないとは。しかし、ここで立ち止まっているわけにもいきません。Adminコンソールが使えなくても、コマンドラインが使えるはずです。
 散々探し回った末に見つけたのがこちらのブログ記事でした。これで何とかなりそうです。それでは早速コマンドを打ち込んでみるとしましょう。

asadmin create-jms-resource --restype javax.jms.Queue --property imqDestinationName=queue jms/Queue

 これでどうやら成功のようです。念のためにAdminコンソールの「JNDI Browser」で確認してみましたが、確かに「jms/Queue」が登録されています。ただ、私の環境固有の問題である可能性が高そうですが、上記のコマンドを使ってリソースを登録したところ、なぜか先ほど「Connection Factories」で登録した「jms/QueueConnectionFactory」がJNDIツリーに表示されなくなり、一旦削除して登録しなおすハメになりました。JNDIを確かめる際には、念のためにリソースとファクトリの両方が存在するかを確かめておくと良さそうです。
 何とかJMSが使えるようになったところで、次に参ります。先にJMSメッセージを受け取るキューを制御できることを述べましたが、それでは一体どのように制御するのでしょうか。まず、あらゆるJMSメッセージ(BytesMessage、MapMessageなど)のスーパーインタフェースとしてMessageがあるのですが、このインタフェースにはsetStringProperty()やsetIntProperty()といった様々なプリミティブ型もしくは文字列型を登録するメソッドが定義されています。
 では、これらのメソッドは一体どのように使うのでしょうか。JMSデータには「メッセージヘッダ」「メッセージプロパティ」「本文」の3レイヤーが存在するらしいのですが、これらのメソッドによって設定されるのは「メッセージプロパティ」のデータのようです。一応getStringProperty()のようにgetメソッドはあるのですが、「プロパティをJMSクライアント間のメッセージングに利用してはいけない」とのこと。メッセージのやり取りはあくまで本文で行えということなのでしょうが、それならなおさらこれらのメソッドの使い道が見出せません。わざわざ全プリミティブ型と文字列型のsetメソッドを用意して、一体何がやりたいのでしょう。
 実はこのプロパティこそが必要なメッセージを識別するためのデータなのです。しかし、どうしてそれだけのために様々な型の設定メソッドが必要なのでしょうか。これがJMSのメッセージ選別機能の恐ろしいところです。
 例えば、以下のようにプロパティを設定したとしましょう。
// msg : Message の子クラスのインスタンス
msg.setStringProperty("mode" , "Test");
msg.setIntProperty("value" , 50);
 このメッセージを識別するにはどうすれば良いのでしょうか。条件に該当する例を1つ示すなら、何と以下のような文字列になるのです。
"mode = 'Test' AND value > 10 AND value < 60"
 もはやSQLです。これならかなり柔軟な条件指定が可能でしょう。EJB-QLやら、上記のJMS条件指定やらを見るたびに、SQLを習得しておいて良かったと考える私なのでありました。
 それでは、このメッセージ条件機構を使用して、適当なEnterprise Applicationでも書いてみましょうか。JMSを使うわけですから、当然Message Driven Beanは含めるとして、後はデータ保持用のEntity、JMS送信やデータ登録・読み込み用のStateless、Web層のJSPとでもしましょうか。
/equipment
 /equip_jar
  /com
   /yamicha
    /equipment
     @Remote EquipmentRegister
     @Stateless EquipmentRegisterBean.java
     @MessageDriven EquipmentDriven.java
     @MessageDriven SupplyDriven.java
     @Entity Equipment.java
     @Entity Supply.java
  /META-INF
   persistence.xml
 /equip_war
  /WEB-INF
   web.xml
  equipment.jsp
 /META-INF
  application.xml
 ここでの「Equipment」とは装備品ではなく備品の意味です。税法上、備品とみなされるのは20万円以上とされますので、20万円を境に強引に備品と消耗品に分けてしまおうというサンプルです。融通もへったくれもなく、いかなる事情も完全無視し、20万円基準で分けてしまいます。具体的には、EquipmentDrivenは備品のみ、SupplyDrivenは消耗品のみを受信するようにします。
 ちなみに、EquipmentDrivenとSupplyDriven、EquipmentとSupplyはアノテーションの一部が多少違う程度であり、ほとんど同じものと考えて差し支えありません。本当はこういう書き方をしてはいけないのですが、そこはサンプルですから。
 とにかく、いつもながらXMLから。つくづく代わり映えしないのではありますが。
// META-INF\application.xml
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/javaee">
	<description>Equipment EJB</description>
	<module>
		<web>
			<web-uri>equipweb.war</web-uri>
			<context-root>equipweb</context-root>
		</web>
	</module>

	<module>
		<ejb>equipejb.jar</ejb>
	</module>
</application>

// equip_jar\META-INF\persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" 
 version="1.0">
<persistence-unit name="persist" transaction-type="JTA">
 <provider>
oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider<
;/provider>
 <jta-data-source>jdbc/MySQL</jta-data-source>
 <properties>
  <property name="toplink.jdbc.driver" value="com.mysql.jdbc.Driver" />
  <property name="toplink.ddl-generation" value="create-tables" />
  <property name="toplink.target-database" value="MySQL4" />
 </properties>
</persistence-unit>
</persistence>

// equip_war\WEB-INF\web.xml
<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
	<display-name>Equipment Application</display-name>
	<description>Equipment Application</description>
</web-app>
 本当はXMLなど書かなくて済むに越したことはありませんが、最低限上記の3つのみはどうしても必要なのです。
 それではクラスの実装と参りましょう。今回はテーブルのリレーがないため、Entityは極めて簡潔です。Entity Beanはリレーが絡むと途端に考えることが多くなりますから。カスケードといい、フェッチタイプといい、どちらを所有側にすべきかといい。
// equip_jar\com\yamicha\equipment\RegisterEquipment.java
package com.yamicha.equipment;

import javax.ejb.*;
import java.util.List;

@Remote public interface EquipmentRegister{
	void registerEquipment(long dated , String name , 
		int price , String note) throws Exception;
	List<Equipment> getEquipments();
	List<Supply> getSupplies();
}

// equip_jar\com\yamicha\equipment\RegisterEquipmentBean.java
package com.yamicha.equipment;

import javax.jms.*;
import javax.annotation.*;
import javax.ejb.*;
import javax.persistence.*;
import java.util.List;

@Stateless(name="EquipmentRegister" , mappedName="ejb/EquipmentRegister") 
	public class EquipmentRegisterBean 
		implements EquipmentRegister , java.io.Serializable{
	@PersistenceContext(unitName="persist") private EntityManager em;
	@Resource(mappedName="jms/QueueConnectionFactory") 
		private QueueConnectionFactory factory;
	@Resource(mappedName="jms/Queue") private Queue queue;

	public void registerEquipment(long dated , String name , 
		int price , String note) 
		throws Exception{
		QueueConnection connect = factory.createQueueConnection();
		QueueSession session = connect.createQueueSession(
			false , Session.AUTO_ACKNOWLEDGE);
		QueueSender sender = session.createSender(queue);

		connect.start();

		MapMessage msg = session.createMapMessage();
		msg.setLong("dated" , dated);
		msg.setString("name" , name);
		msg.setInt("price" , price);
		msg.setString("note" , note);

		msg.setIntProperty("price" , price);
		msg.setStringProperty("type" , "Equipment");

		sender.send(msg);
		sender.close();
	}
	public List<Equipment> getEquipments(){
		return (List<Equipment>)em.createQuery(
			"SELECT e FROM Equipment e ORDER BY e.dated DESC"
			).getResultList();
	}
	public List<Supply> getSupplies(){
		return (List<Supply>)em.createQuery(
			"SELECT s FROM Supply s ORDER BY s.dated DESC"
			).getResultList();
	}
}

// equip_jar\com\yamicha\equipment\EquimentDriven.java
package com.yamicha.equipment;

import javax.jms.*;
import javax.ejb.*;
import javax.annotation.*;
import javax.persistence.*;
import java.util.Date;

// price が 200000 以上の場合は Equipment で処理
@MessageDriven(mappedName="jms/Queue" , activationConfig={
	@ActivationConfigProperty(propertyName="messageSelector" , 
	propertyValue="type = 'Equipment' AND price >= 200000")})
	public class EquipmentDriven implements MessageListener{
	@PersistenceContext(unitName="persist") private EntityManager em;

	public void onMessage(Message msg){
		try{
			if(msg instanceof MapMessage){
				MapMessage map = (MapMessage)msg;

				Equipment e = new Equipment(
					new Date(map.getLong("dated")) , 
					map.getString("name") , 
					map.getInt("price") , 
					map.getString("note")
				);

				em.persist(e);
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

// equip_jar\com\yamicha\equipment\SupplyDriven.java
package com.yamicha.equipment;

import javax.jms.*;
import javax.ejb.*;
import javax.annotation.*;
import javax.persistence.*;
import java.util.Date;

// 200000 以下の場合は Supply で処理
@MessageDriven(mappedName="jms/Queue" , activationConfig={
	@ActivationConfigProperty(propertyName="messageSelector" , 
	propertyValue="type = 'Equipment' AND price < 200000")})
	public class SupplyDriven implements MessageListener{
	@PersistenceContext(unitName="persist") private EntityManager em;

	public void onMessage(Message msg){
		try{
			if(msg instanceof MapMessage){
				MapMessage map = (MapMessage)msg;

				Supply s = new Supply(
					new Date(map.getLong("dated")) , 
					map.getString("name") , 
					map.getInt("price") , 
					map.getString("note")
				);

				em.persist(s);
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

// equip_jar\com\yamicha\equipment\Equipment.java
package com.yamicha.equipment;

import javax.persistence.*;
import java.util.Date;

@Entity @Table(name="equipment_entity_equipment") 
	public class Equipment implements java.io.Serializable{
	private int number;
	private Date dated;
	private String name;
	private int price;
	private String note;
	public Equipment(){
	}
	public Equipment(Date dated , String name , 
		int price , String note){
		this.dated = dated;
		this.name = name;
		this.price = price;
		this.note = note;
	}

	@Id @GeneratedValue @Column(name="number") 
		public int getNumber(){
		return number;
	}
	@Temporal(TemporalType.DATE) @Column(name="dated") 
		public Date getDated(){
		return dated;
	}
	@Column(name="name") public String getName(){
		return name;
	}
	@Column(name="price") public int getPrice(){
		return price;
	}
	@Column(name="note") public String getNote(){
		return note;
	}

	private void setNumber(int n){
		number = n;
	}
	public void setDated(Date d){
		dated = d;
	}
	public void setName(String n){
		name = n;
	}
	public void setPrice(int p){
		price = p;
	}
	public void setNote(String n){
		note = n;
	}
}

// equip_jar\com\yamicha\equipment\Supply.java
package com.yamicha.equipment;

import javax.persistence.*;
import java.util.Date;

@Entity @Table(name="equipment_entity_supply") 
	public class Supply implements java.io.Serializable{
	private int number;
	private Date dated;
	private String name;
	private int price;
	private String note;
	public Supply(){
	}
	public Supply(Date dated , String name , 
		int price , String note){
		this.dated = dated;
		this.name = name;
		this.price = price;
		this.note = note;
	}

	@Id @GeneratedValue @Column(name="number") 
		public int getNumber(){
		return number;
	}
	@Temporal(TemporalType.DATE) @Column(name="dated") 
		public Date getDated(){
		return dated;
	}
	@Column(name="name") public String getName(){
		return name;
	}
	@Column(name="price") public int getPrice(){
		return price;
	}
	@Column(name="note") public String getNote(){
		return note;
	}

	private void setNumber(int n){
		number = n;
	}
	public void setDated(Date d){
		dated = d;
	}
	public void setName(String n){
		name = n;
	}
	public void setPrice(int p){
		price = p;
	}
	public void setNote(String n){
		note = n;
	}
}
 これらのEJBモジュールが完成したら、これらを処理するJSPを書きます。WebService編ではEJBを最初にデプロイし、その後にwsimportを使ってからWebモジュールをデプロイしていましたが、今回は@WebServiceがついていないのを見ての通り、WebServiceは一切関係ありませんから、Enterprise Applicationとしてまとめてデプロイできます(何もしなくてもWeb側でEJBクラスをインポートできるため、この方が楽です)。
// equip_web\equipment.jsp
<%@ page import="javax.servlet.http.* , javax.annotation.* , javax.ejb.* , 
javax.naming.* , java.rmi.* , javax.rmi.* , java.io.* , java.util.* , 
com.yamicha.equipment.*" 
contentType="text/html; charset=Shift_JIS" %>

<%!
private EquipmentRegister getEquipmentRegister() throws Exception{
	Context ct = new InitialContext();
	Object ejb = ct.lookup("ejb/EquipmentRegister");
	return (EquipmentRegister)PortableRemoteObject.narrow(
		ejb , EquipmentRegister.class);
}
%>

<html>
<head>
<title>Equipment Interface</title>
</head>
<body>
<b>備品データ管理インタフェース</b><br>
[<a href="?mode=register">新規登録</a>] 
[<a href="?mode=equipment">備品台帳</a>] 
[<a href="?mode=supply">消耗品台帳</a>]<br>
<br>

<%
request.setCharacterEncoding("Shift_JIS");

String mode = request.getParameter("mode");
if(mode == null){
	out.println("メニューより操作を実行してください。");
}else if("register".equals(mode)){
	Calendar c = Calendar.getInstance();
	int year = c.get(Calendar.YEAR);
	int month = c.get(Calendar.MONTH);
	int day = c.get(Calendar.DAY_OF_MONTH);
%>
<b>登録</b>
<form method="POST" action="?">

<table border="1">
<tr>
<td>取引日</td>
<td>
<input type="text" name="year" size="6" value="<%=year%>">年
<input type="text" name="month" size="4" value="<%=month%>">月
<input type="text" name="day" size="4" value="<%=day%>">日
</td>
</tr>

<tr>
<td>取引品目</td>
<td><input type="text" name="name" size="40"></td>
</tr>

<tr>
<td>金額</td>
<td><input type="text" name="price" size="10"></td>
</tr>

<tr>
<td>摘要</td>
<td><input type="text" name="note" size="60"></td>
</tr>
</table>

<input type="submit" value="登録">
<input type="hidden" name="mode" value="send">
</form>
<%
}else if("send".equals(mode)){
	int year = 0;
	int month = 0;
	int day = 0;
	int price = 0;
	String name = request.getParameter("name");
	String note = request.getParameter("note");

	boolean success = false;
	try{
		year = Integer.parseInt(request.getParameter("year"));
		month = Integer.parseInt(request.getParameter("month"));
		day = Integer.parseInt(request.getParameter("day"));
		price = Integer.parseInt(request.getParameter("price"));

		success = true;
	}catch(NumberFormatException e){
		out.println("フォームの値が不正です。");
	}

	if(success){
		EquipmentRegister er = getEquipmentRegister();

		Calendar c = Calendar.getInstance();
		c.set(year , month , day , 0 , 0 , 0);

		er.registerEquipment(c.getTimeInMillis() , 
			name , price , note);

		out.println("データを登録しました。");
	}
}else if("equipment".equals(mode)){
	EquipmentRegister er = getEquipmentRegister();

%>
<b>備品台帳</b>
<table border="1">
<tr>
<td bgcolor="#CCDDFF">番号</td>
<td bgcolor="#CCDDFF">取引日</td>
<td bgcolor="#CCDDFF">備品名</td>
<td bgcolor="#CCDDFF">金額</td>
<td bgcolor="#CCDDFF">摘要</td>
</tr>
<%
	List<Equipment> equipments = er.getEquipments();
	for(Equipment e : equipments){
		Calendar c = Calendar.getInstance();
		c.setTime(e.getDated());

		out.println("<tr>");
		out.println("<td>" + e.getNumber() + "</td>");
		out.println("<td>" + c.get(Calendar.YEAR) + "/" + 
			c.get(Calendar.MONTH) + "/" + 
			c.get(Calendar.DAY_OF_MONTH) + "</td>");
		out.println("<td>" + e.getName() + "</td>");
		out.println("<td>" + e.getPrice() + "</td>");
		out.println("<td>" + e.getNote() + "</td>");
		out.println("</tr>");
	}
	out.println("</table>");
}else if("supply".equals(mode)){
	EquipmentRegister er = getEquipmentRegister();

%>
<b>消耗品台帳</b>
<table border="1">
<tr>
<td bgcolor="#CCDDFF">番号</td>
<td bgcolor="#CCDDFF">取引日</td>
<td bgcolor="#CCDDFF">備品名</td>
<td bgcolor="#CCDDFF">金額</td>
<td bgcolor="#CCDDFF">摘要</td>
</tr>
<%
	List<Supply> supplies = er.getSupplies();
	for(Supply s : supplies){
		Calendar c = Calendar.getInstance();
		c.setTime(s.getDated());

		out.println("<tr>");
		out.println("<td>" + s.getNumber() + "</td>");
		out.println("<td>" + c.get(Calendar.YEAR) + "/" + 
			c.get(Calendar.MONTH) + "/" + 
			c.get(Calendar.DAY_OF_MONTH) + "</td>");
		out.println("<td>" + s.getName() + "</td>");
		out.println("<td>" + s.getPrice() + "</td>");
		out.println("<td>" + s.getNote() + "</td>");
		out.println("</tr>");
	}
	out.println("</table>");
}

%>
</body>
</html>
 後はこれらを「Enterprise Application」としてディレクトリデプロイし(あるいはEAR化してデプロイする場合も同様)、「AppServer_Servlet_URI/equipweb/equipment.jsp」を開くだけです。登録画面から色々登録し、備品または消耗品台帳を見てみると、値段によってそれぞれデータが振り分けられて登録されているのが分かります。これはJMSの機能によるものです。
 実際、DBでも分けて登録されています。
// バカ高いコーヒーメーカーを導入させたのは某魔道士とか
SELECT dated , price , name FROM equipment_entity_equipment;

dated	price	name
2007-04-04	320000	応接セット6人掛け
2007-04-01	580000	騎士装備品セット16XL(ブルー)
2007-03-26	460000	イリス IR3200-DUAL ブレードサーバー
2007-03-20	280000	イリス 豆ひき付き業務用ハイエンドコーヒーメーカー

// どうやらブレードサーバーとラックを同時に購入したらしい
SELECT dated , price , name FROM equipment_entity_supply;

dated	price	name
2007-04-04	2800	イリス 騎士ハサミ
2007-03-26	74000	ブレードサーバーラック
2007-03-24	150	イリス鉛筆HB 1ダース
2007-03-23	7800	イリス 8ポート全二重10/100Baseハブ

// 「16XL」とはどれほど大きいのか
SELECT * FROM 
(SELECT dated , price , name FROM equipment_entity_equipment UNION 
SELECT dated , price , name FROM equipment_entity_supply) e 
WHERE dated >= '2007-04-01' ORDER BY price DESC;

dated	price	name
2007-04-01	580000	騎士装備品セット16XL(ブルー)
2007-04-04	320000	応接セット6人掛け
2007-04-04	2800	イリス 騎士ハサミ
 恐るべしJMS。この程度のサンプルでも結構楽しめるのですから、上手くやれば色々書けそうです。
 ちなみに、今回はMessage Driven Beanでメッセージを選別してみましたが、当然ながら普通のQueueReceiverでもメッセージ選別が可能です。
// 簡単なサンプル

// factory : QueueConnectionFactory のインスタンス
// JNDI ルックアップ、またはインジェクトしておく

// queue : Queue のインスタンス
// こちらもルックアップかインジェクトしておく

QueueConnection connect = factory.createQueueConnection();
QueueSession session = connect.createQueueSession(
	false , Session.AUTO_ACKNOWLEDGE);
QueueReceiver receiver = session.createReceiver(queue , 
	"type = 'Blog' AND logs > 500");
 どうやら文法は「SQL-92のサブセット」と位置づけられているらしく、LIKEやBETWEENまで使えてしまいます。JMSでLIKEが使えそうな状況というのもすぐには連想しがたいですが、とにかく色々できることは事実です。特にアノテーションに条件を定める場合は条件文字列が定数になってしまいますから、こういう柔軟な検索ができる機構はありがたいところです。
カテゴリ [開発魔法][社会問題][経済・知的財産] [トラックバック 0][コメント 0]

ゆすり屋排除の原則
2007/03/31(Sat)23:01:16
 「メディア・リテラシー」などと甘い言葉を並べている状況ではなく、もはや「テレビの情報はほぼウソ」と考えた方が良いのでしょう。あるあるの次は「恐怖の食卓」なる番組のインチキ報道をフジテレビが謝罪。問題となった番組の放送日は昨年9月末、今から半年前ですか。
 何でも「子どもが高脂質、高たんぱくの食事を続けるとADHDになる」という放送を行ったそうですが、これは同疾患について少しでも知っている人なら見た瞬間にあきれ果てるような内容です。私の記憶しているところによれば、ADHDは「生まれつきの脳の微細な損傷」が原因となって起こる先天的な症状であり、基本的に学力には問題なく、適切な指導があれば一般学級で普通に学校生活が可能です。比率としては1クラスに1人はいるとされ、女児よりも男児に多く、高齢出産や喫煙がリスクを高めるとされます。
 このテレビの説を信じるなら、どうやら高脂質、高たんぱくの食事を続けることで先天的な症状を発症するようです。常識的に考えて、タイムスリップでもできない限りあり得ません。いわば「子どもにこうした食事をさせると、その子は生まれつきの奇形児になります」と言っているも同等であり、このようなものは完全なるデタラメです。
 そういえば似たようなことを主張している人間が他にもいました。ゲーム脳などというインチキを言いふらす自称脳科学者です。何でも、ゲームをやると自閉症になるのだとか。やはり噴飯ものですが、一応説明しておきますと、自閉症は先天的な疾患とされており、ゲームが原因で後天的に自閉症になることは考えられません。
 同番組ではさらに「ADHDが少年犯罪の一因」などという間違った説明を行い、危機感をあおる始末。そのようなデータは未だに見たことがなく、何を根拠にそのようなことを言っているのか理解に苦しみます。ADHDは周囲の理解と適切な指導で状況を改善できるのですが、この番組を教師なりADHD児の同級生の親なりが見ていて、ADHD児の親が適切な指導を要望する際に「子どもがADHDである」ことを明かせば、いわれのない偏見が生じることは見えています。このようなねつ造報道は最低以下の最低といえましょう。
 やはり、政府は一刻も早く「ねつ造報道、過熱報道を理由とする、放送免許取り消しも含めた罰則」を導入しなければなりません。情報の受け側である国民は、ねつ造や過熱報道の自主規制と終焉を長らく待ち続けました。しかし、実際には何かが変わるどころか、次第に悪質になっているような状況です。挙句の果てに、ニセ情報で健康被害を生じさせたり、遺族の精神をズタズタにしたり、大嘘によって偏見を助長したりといった行為を繰り返し、見かねてとがめた人に対しては「報道の自由の侵害だ」。これも自分でまいた種です。

 最近は、歴史を自分の主観で都合の良いように作り変える「歴史ねつ造主義」などという意味不明なものまで登場し、教科書検定もなかなか大変な状況になっているようですが、政府の検閲削除についての問題は後日として、経済記事をいくつか。
 まずエディオンとビックカメラの統合がお流れになったそうです。かなりの大型案件ですので注目していましたが、合併にはこうしたこともつきものです。玩具業界合併のさきがけとして注目された「セガバンダイ」も結局は幻となり、合併が単純な足し算のようにいかないことはAOLタイムワーナーやダイムラー・クライスラーが証明しています。
 合併話がまとまったは良いものの、いざ合併となるとためらいが生じたり、相手の悪い面が見えたりすることもあるのでしょう。合併で業界が様変わりすると大騒ぎした末にドタキャンとなれば、これまた衝撃は大きいものです。極端な話、マルハニチロすらドタキャンの可能性がゼロではないのですから。
 最近は合併が多いですが、グリーンメーラーに対するホワイトナイトのような特殊な場合を除き、大抵は合併までに余裕を持たせて期間を設定する場合が多く、本当に合併されるかは実際に合併する時まで分かりません。ただ、双方の選択肢を大幅に狭める敵対的買収をむやみに仕掛けるより、お流れになっても合併交渉を行う方が良いに決まっていますが。
 ところで敵対的買収ですが、サッポロの敵対的買収防止策が株主によって承認されました。これを否決していれば、グリーンメーラーが短期的利益をもたらす可能性があったにもかかわらず、防止策が無事に承認されたのです。日本のモラルが少しずつながら上がってきていることが分かります。グリーンメーラーを市場から叩き出すための第一歩を踏み出された株主の方々に敬意を表します。
 まず、スティールや村上のようなグリーンメーラーの暗躍は、企業にとって何ら良い影響をもたらしません。村上ファンドに狙われた企業は長期的利益を損なっている場合が多いという結果も出ています。ターゲットにされた企業にしてみれば災難な話であり、この影響は利害関係者(社員、株主、消費者、ひいては多くの国民)全体に少しずつ及びます。ただし、グリーンメーラーがプレミアありの価格でTOBを行ったり、それに対してホワイトナイトが出てきたり、または短期的に株価を上昇させるよう圧力をかければ、ターゲット企業の株主は売り抜けて利益を得られます。
 ここで、グリーンメーラーを受け入れるか拒絶するかの二者択一によってそれぞれ得られる利益をゲーム理論で勘定してみます。受け入れた場合は株主が利益20で他の利害関係者が-3、拒絶した場合は株主の利益が0で利害関係者も0であるとしましょう。グリーンメーラー・ファンドは受け入れの場合に50、拒絶の場合に-3であるとします。すると、受け入れた場合は「株主20,他-3,ファンド50」、拒絶した場合は「株主0,他0,ファンド-3」となり、株主の支配戦略は「受け入れ」となります。ところが、今回はグリーンメーラーを拒絶する決定がなされました。なぜでしょうか。
 現在、日本にはこうした買収防止条項が株主に承認された例がほとんどなく、サッポロという知名度の高い企業での株主承認の動向は、確実に他の企業にも影響を与えます。そして、サッポロの株主はサッポロ問題の場合こそ株主ですが、他の企業が同様の状況に置かれた場合は「他の利害関係者」になります。
 少々極端な例ですが、サッポロの株主総会の前例によって他企業10件の問題の行方も連動して決まるとしましょう。サッポロの株主も他企業においては「その他の利害関係者」ですから、承認した場合は「株主-10,他-30,ファンド500」となり、ファンドが利益を手にします。逆に、ここで拒絶する前例を作った場合、「株主0,他0,ファンド-30」となり、グリーンメーラーの暴挙を認めるより利益が大きいことが分かります。
 平たく言えば「グリーンメーラーを叩き出すのが日本のため」ということです。これをしなかった場合、株主は一時の利益を得ることができますが、後には結局自分も含めた日本国民全員が被害を被ることになり、日本のためにも自分のためにもならないのです。「グリーンメーラーをのさばらせるのは日本のためにならない」という意識はぜひとも広がって欲しいところです。

 最近はSunのブログやドキュメントに良く助けられますが、WebServiceでJava型とXMLの変換を担当するJAXBに関しては、日本人技術者のカワグチ コウスケ氏が担当されているらしく、情報収集中にはその関係で氏の名前を良く見かけます。このグローバル化の時代、世界の人間が寄り集まることなど全く珍しくなく、しかもプログラム言語は万国共通言語です。従って「日本人が世界で技術を引っ張っている。日本人よ、大志を抱け」などと時代錯誤なことは言いませんが、こういう関係からか日本語の情報が割と多くて助かります。
 中国の方のブログも存在しました。Sunの日本人のブログは日本語または英語で書かれている場合が多く、これは分かるのですが、この中国の方のブログは日本語で説明が書かれているではありませんか。仲間の技術者に翻訳を依頼したのでもなければ、各国技術者のるつぼであるSunに所属しているなら英語も使えるでしょうし、3ヶ国語の上にプログラム言語ですか。やはり本場の技術者は違います。
 その後色々と調べてみましたが、やはり参照無限ループはWebServiceの問題として認識されているようです。常識的に考えて、Entity Beanを受け渡すだけでこのような問題が発生する以上、普通はリリース前の段階で潰すものではと考えるのですが、あちらにも色々事情があるのでしょう。SOAPはベンダ共通のため、自分勝手なデータは付加できませんし。
 そこで探し回った挙句に見つけたのがこちら。Sunのサイトにおいてあるくせに「Unofficial JAXB Guide」という少々怪しげなタイトルですが、役に立ちます。この方法によってEntityをマップすることもできるようです。
 要約しますと、まず@OneToManyまたは@OneToOneの場合、参照が循環するとエラーが出てしまいますので、片方(@OneToManyの場合は@ManyToOne側)に「@XmlTransient」をつけると良いようです。このアノテーションはフィールドまたはJavaBeanメソッドをXMLに反映させない効果があり、結果としてループが解消されます。
 JAXBの対象とし、XMLによる直列化のルート要素に位置するクラスには「@XmlRootElement」を付加し、後は必要に応じてフィールドやJavaBeanメソッドをアノテートするようです。
@XmlRootElement public class Dishonest{
	private String name;
	private List<Victim> victims = new ArrayList<Victim>();
	public String getName(){
		return name;
	}
	public List<Victim> getVictims(){
		return victims;
	}
	public void setName(String n){
		name = n;
	}
	public void setVictims(List<Victim> v){
		victims = v;
	}
	public void add(Victim v){
		victims.add(v);
		v.setDishonest(this);
	}
}
public class Victim{
	private String name;
	private Dishonest dishonest;
	public Victim(){
	}
	public Victim(String n){
		name = n;
	}
	public String getName(){
		return name;
	}
	public Dishonest getDishonest(){
		return dishonest;
	}
	public void setName(String n){
		name = n;
	}
	public void setDishonest(Dishonest d){
		dishonest = d;
	}
}

Dishonest livedoor = new Dishonest();
livedoor.setName("ライブドア");
livedoor.add(new Victim("ライブドア株主"));
livedoor.add(new Victim("催眠術にかかった一部の堀江支持者"));

Dishonest crystal = new Dishonest();
crystal.setName("クリスタル");
crystal.add(new Victim("過労死した社員"));
crystal.add(new Victim("法令破りをさせられた人々"));
crystal.add(new Victim("ピンハネされた人々"));

Dishonest canon = new Dishonest();
canon.add(new Victim("偽装請負行為に巻き込まれた人々"));
canon.add(new Victim("無賃残業合法化制度に巻き込まれる可能性のある人々"));
 これでDishonestクラスのインスタンスを返せば、自動的にVictimもついてきます。@Transientされているため、無限ループにも陥りません。
 ただし、ここで注意したいのは、@Transientは無限ループを抑制するアノテーションではなく、XML上からそのフィールドを完全に消し去ってしまうアノテーションである点です。wsimportでクラスを自動生成し、Dishonestのインスタンスを入手したとしても、@Transientされたフィールドは使えません。
// d : Dishonest Instance
List<Victim> victims = d.getVictims();
for(Victim v : victims){
	System.out.println(v.getName());
	System.out.println(v.getDishonest());	// Error
	// Victim.dishonest は存在しない
}
 つまり、せっかく@OneToManyや@OneToOneでプログラムを組んだとしても、@Transientされた側から相手インスタンスを得ることはできないのです。例えば上記の場合、悪徳企業のインスタンスがあれば、その悪徳企業にやられた被害者たちは分かるのですが、被害者のインスタンスから被害者がどの悪徳企業にやられたかを調べることはできません。
 ではManyToManyの場合はどうなるのでしょう。ManyToManyは互いが互いの参照で構築されている点で@OneToManyとは根本から違っており、仮に@ManyToManyの片側が1つしかデータを持たない場合であっても@OneToManyではなく@ManyToManyにしておくことは考えられます。@ManyToManyには大本のデータを変更すれば全データに反映される特徴があり、この点が@OneToManyとは異なっています。で、これですが、先のサイトによりますと「TBD」だそうです。では、この「TBD」とは一体何なのでしょう。
 TBDとは「To Be Determined」の略称であり直訳すると今後決定するのような意味になりこの文言の意味もほとんど直訳の意味と同等であるのだが未だ仕様策定中であったりそのうち定めることを表すビジネス用語として用いられ仕様書にこの文言がある場合はまだ仕様が定められていないことを表している
 ああ、そうですか。つまり、今のところ仕様は定まっていないことになります。一体どうしろとおっしゃいますか。EJB 3.0の時代になって1年経つのですが。とにかく決まっていないことはどうしようもありませんから保留するとしましょう。
 もう1つ、@XmlIDと@XmlIDREFについても触れておきます。これはXMLで参照を表現するための手法で、これを使えばXMLでは表現が厄介な参照を扱うことができます。これを活用することで、@Transientなしの@OneToManyのようなことが可能です。原理はSQLのPRIMARY KEYと似たり寄ったりであり、@XmlIDはユニークIDを、@XmlIDREFは@XmlIDアノテートしたフィールドを持つクラス型を注釈します。
 何とも便利そうと言うなかれ、私が試したところによれば、これには以下の落とし穴があります。

・@XmlID/@XmlIDREFはユニークIDを使って参照先を解決するため、参照される側にユニークIDのフィールドまたはJavaBeanメソッドが必要である
・ドキュメントには「@XmlIDREF注釈されたフィールドが型である場合はその型に、コレクションの場合はコレクション要素の型に@XmlID注釈されたフィールドが必要」と書いてあるが、コレクションに@XmlIDREFを使うとエラーが発生する。普通のフィールドになら使える
・@XmlID注釈する対象はString型に限られる。intなどは使ってはいけない
・@XmlIDREF注釈されたフィールドは、wsimport先ではなぜかjava.lang.Object型になってしまう(WSDLの情報では参照先が分からないためか)。使用時にはキャストを余儀なくされる
ルートエレメントクラスのフィールドに@XmlIDREFを付加し、そのフィールド型のクラスに@XmlIDをつけることによって参照を表現することはできない

 最後の項目が特に重要です。つまり、上記のクラスでいうところの以下のような記述はダメです。
@XmlRootElement public class Dishonest{
	// ...
	@XmlID public String getId(){
	}
	public String getName(){
		return name;
	}
	public List<Victim> getVictims(){
		return victims;
	}
	// ...
}
public class Victim{
	// ...
	public String getName(){
		return name;
	}
	@XmlIDREF public Dishonest getDishonest(){
		return dishonest;
	}
	// ...
}
 ただし、ラップクラスを作れば使えるようになります。
@XmlRootElement public class Wrap{
	private List<Victim> victims;
	public Wrap(){
		victims = new ArrayList<Victim>();
	}
	public List<Victim> getVictims(){
		return victims;
	}
	public void setVitcims(List<Victim> v){
		victims = v;
	}
}
public class Dishonest{
	// ...
}
public class Victim{
	// ...
}

Victim v1 = new Victim("弱者全般");
Victim v2 = new Victim("自殺者");
Victim v3 = new Victim("開発者全般");
Victim v4 = new Victim("私(管理者)");

Dishonest politics = new Dishonest();
politics.setName("安倍政治");
politics.add(new Victim(v1));
politics.add(new Victim(v2));

Dishonest development = new Dishonest();
development.setName("開発");
development.add(new Victim(v3));
development.add(new Victim(v4));

Wrap w = new Wrap();
w.getVictims().add(v1);
w.getVictims().add(v2);
w.getVictims().add(v3);
w.getVictims().add(v4);
 言うまでもありませんが、Victim.dishonet.victimsには元のVictimが含まれています。便利なものです。しかし、WebServiceのためにわざわざ別のクラスやメソッドを書いたりしなければならないのはどうかと。
 もう少し何とかならないものでしょうか。ManyToManyを含めて。
カテゴリ [開発魔法][社会問題][経済・知的財産] [トラックバック 0][コメント 0]

<-前のページ [4] [5] [6] [7] [8] [9] [10] [11] [12] 次のページ->
- Blog by yamicha.com -