レガシーなTomcatアプリケーションを修正した


JSP(JavaServer Pages)も今じゃすっかりレガシー扱いで、新規案件でお目にかかることは大規模システムを除くと殆ど無くなった。
今回、古いWebアプリケーションの補修・改修案件で、十何年かぶりにJSPを触る機会があったので、基礎用語の解説も交えながら作業ログを残す。

はじめに

サーバ環境

Tomcatをマイクロソフト社のパブリッククラウドAzureで動かしている。サーバ構成は、前回の記事(アプリケーションサーバをごっそり載せ替えた話)を参照。StrutsやSpringなどのフレームワークは使っていなくて、JSPだけで構築されている。

作業上の制約

  • あるのは本番サーバのみ。テストサーバは無い。
  • Javaのソースコードも無い。あるのはwarファイルのみ。
  • 当然、ドキュメントも無い。

改修内容

ある条件を満たしたときに、特定のボタンを表示しないようにするだけだ。

JavaのWebアプリケーション(J2EE)におけるMVCモデルは下表の通り。

レイヤー J2EEでの呼び名
Model層 Bean
View層 JSP
Controller層 Servlet

今回は表示を変えるだけなので、手を入れるのはJSPだけで良く、Javaのソースコードが無いことは大きな障壁とはならない。ただし「ある条件を満たすかどうか」はビジネスロジックの範疇となりBeanが管理しているため、Beanの解析は必要になる。

解析と修正の進め方

Beanって何だっけ?

Beanとは、教科書通りに説明するなら「JavaBeansEJB(Enterprise JavaBeans)の仕様に則って作られた再利用可能なJavaプログラム」のことである。
簡単に言うと、プロパティ(属性)を持つただのJavaクラスだ。
JSPからBeanを呼び出すにはuseBeanというJSPタグを使う。このタグでBeanのインスタンスを生成、つまりBeanオブジェクトを作り、スクリプトレットから利用したり、setProperty/getPropertyタグでBeanのプロパティにアクセスする。

useBeanの使用例
<jsp:useBean id="[オブジェクト名]" class="[クラス名]"/>

Beanの解析には Java Decompiler を使った。クラスファイルやjarファイルから、Javaのソースコードを生成する逆コンパイラである。

解析を進めると、クラス名bean.ClientCheckinBeanで所望のメソッドが定義されていると分かる。

JSPの修正

クラス名が分かったので、該当のJSPファイルから、このクラスを呼び出している箇所を検索してみる。

JSP
1: <%@ page contentType = "text/html;charset=Shift_JIS" %>
2: <% response.setContentType("text/html;charset=Shift_JIS"); %>
3: <% request.setCharacterEncoding("Shift_JIS"); %>
4: <%@ include file = "Nocache.inc" %>
5: <%@ page import = "common.Sanitizer" %>
6: <%@ page import = "java.util.concurrent.CopyOnWriteArrayList" %>
7: <%@ page import = "java.util.Calendar" %>
8: <jsp:useBean id="CHECKIN_DATA" scope="session" class="bean.ClientCheckinBean" />
9: <jsp:useBean id="DATA" scope="session" class="java.util.concurrent.CopyOnWriteArrayList" />

8行目で CHECKIN_DATA というsessionスコープのオブジェクトに入れていることが分かる。
試しに、このクラスのgetClientcdメソッドの戻り値を「あぶりだし」で画面に表示してみる。

<font color="white"><!-- 文字の色を白にする -->
    <%= CHECKIN_DATA.getClientcd() %>
</font>

この俗に言うあぶりだしとは、背景色と文字色を同じにすることで、そのままでは読めないがカーソルで選択し反転させると文字が浮かび上がる効果を利用したものだ。本番サーバなので、他人に気づかれずに動作を確認したい。

想定通りの値を確認できたので、条件を満たすとボタンを表示しないよう修正。

<input type="button" name="btn1" value=" 出勤 " onClick="checkinAction('1')">
<input type="button" name="btn2" value=" 退勤 " onClick="checkinAction('2')">
<% if (CHECKIN_DATA.getClientcd().intValue() == 123) { %>  <!-- 追加 -->
    <input type="button" name="btn3" value="公用外出" onClick="checkinAction('3')">
    <input type="button" name="btn4" value="公用戻り" onClick="checkinAction('4')">
<% } %>  <!-- 追加 -->

が! あるボタンのクリック時に発火されるJavaScriptのイベントでエラーになってしまった。
F12キーでChromeのデベロッパーツールを起動すると、次のエラーメッセージがコンソールに出力されている。

Uncaught TypeError: Cannot set property 'disabled' of undefined
    at disableForm (ClientCheckinServlet:54)
    at checkinAction (ClientCheckinServlet:41)
    at HTMLInputElement.onclick (ClientCheckinServlet:329)
disableForm @ ClientCheckinServlet:54
checkinAction @ ClientCheckinServlet:41
onclick @ ClientCheckinServlet:329

JSPによってボタンを減らされたHTMLがブラウザに返却され、JavaScriptがそのボタンの要素を参照しようとして「未定義だ」とエラーになっている。
trycatchでエラーを無視しても良いのだが、ここは丁寧に、ボタンが存在するか判定してから要素を参照するようにした。

JavaScript
document.checkin.btn1.disabled = true;
document.checkin.btn2.disabled = true;
if (document.checkin.btn3) {  // 追加
    document.checkin.btn3.disabled = true;
    document.checkin.btn4.disabled = true;
}

jQueryなら違う書き方もできるだろうが、なにせ古いWebアプリケーションなので生のJavaScriptで対応した。