bokusui について

ソフトウェアハウスでのPG・SEから始まり、10年近く勤めた金融系企業の社内SEを数年前にやめ、フリーランス時代を経たのち法人成りしました。システム開発の全工程をこじんまりとやり続けています。

画面エラーのログ出力を整理

今作っているJavaEEアプリですが、画面エラー発生時のログ出力を検討しました。ま、アプリケーションサーバであるWebLogicが勝手にログを出してくれますが、エラー以外にも勝手に出るので量が多くて見るのが辛いし、アプリとしては別途log4j2でロギングする方向にしているので、画面エラー発生時もlog4j出力に統一するという要件になります。WebLogicでlog4jとログを連携させるとかFilter使うとかいろいろやり方はありそうでしたが、画面個々に手を入れずに最も簡単なやり方は無いかなと考えたところ、画面エラー発生時はエラーページに遷移するようにしておいて、そのエラーページでログ出力させるという形にしてみます。

web.xml抜粋(今の所・・・)

  <error-page>
    <exception-type>javax.servlet.ServletException</exception-type>
    <location>/errors/index.xhtml</location>
  </error-page>
  <error-page>
    <exception-type>javax.ejb.EJBException</exception-type>
    <location>/errors/index.xhtml</location>
  </error-page>
  <error-page>
    <error-code>403</error-code>
    <location>/errors/403.xhtml</location>
  </error-page>

エラーページ用JSF管理Bean

@ManagedBean(name="errorPage")
public class ErrorHander {
	protected static Logger logger=LogManager.getLogger();
	private String message;
	
	/** エラーメッセージ出力 */
	public String getMessage() {
        FacesContext context = FacesContext.getCurrentInstance();
        ExternalContext externalContext = context.getExternalContext();
        HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();

		String errorURI = 
				(String) request.getAttribute("javax.servlet.error.request_uri");

		message = (String)request.getAttribute("javax.servlet.error.message");
		Throwable throwable = (Throwable) 
				request.getAttribute("javax.servlet.error.exception");

		logger.error("View Error URL={}", errorURI, throwable);
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
}

最後にweb.xmlで指定した/errors/index.xhtmlで、エラー内容は見せないように下記を忍ばせておけば、管理Beanのメソッドが実行されるという形になります。

<h:outputLabel id="execMsg" value="#{errorPage.message}" style="visibility:hidden;"/>

TeraTermマクロでStack overflow発生

だいぶ前に作ったTeraTermマクロのUNIXサーバ運用作業自動化ツールでエラーが出たというので見に行くと、Stack overflowというマクロのエラーダイアログが。
要件的には、psコマンドで特定のJVMプロセス終了を持って次に進む、20回チェックして終わってないならダイアログを表示し、その前の処理に戻るという内容ですが、たまたま今回の作業中に長時間動作するプロセスが実行中となっており、psを11回実行したところで停止した状態になってました。
これまで何回も使われているマクロの自動化ツールですが、この事象は今回初めて。
ちょっとググるとすぐに下記が出たのでマクロスクリプトを見直したら、ループ中にgotoで飛ぶというダメな作りを痛感。。
https://osdn.jp/ticket/browse.php?group_id=1412&tid=8024
goto否定主義者では無いし、多重ループだと解りにくくなるので、素直にループの外にgotoを出して対応。

timeout=3

:BEFORE_PROC
~前処理の詳細は割愛~

ifdefined OUTPUTBUF
if result = 0 then
   strdim OUTPUTBUF 500
endif

CHK_COUNT_OVER = 20
CHK_COUNT_NUM = 0
:CHK_PROCESS_ASTART
OUTPUTCNT = 0

flushrecv
setsync 1
CMD = "ps -ef | grep -e JOB0010 -e JOB0020 -e JOB0030 -e JOB0040 -e JOB0050 -e JOB0060 "
strconcat CMD "-e JOB0070 -e JOB0080 | grep -v grep"
sendln CMD

recvln
while result = 1
   recvln
   if result = 0 then
      break
   endif
   OUTPUTBUF[OUTPUTCNT] = inputstr
   OUTPUTCNT = OUTPUTCNT + 1
endwhile
setsync 0

if OUTPUTCNT > 0 then
   CHKSTR = '/bin/java'
   for ii 0 OUTPUTCNT - 1
      strscan OUTPUTBUF[ii] CHKSTR
      if result > 0 then
         CHK_COUNT_NUM = CHK_COUNT_NUM + 1
         if CHK_COUNT_NUM >= CHK_COUNT_OVER then
            messagebox 'プロセスが実行中で作業を続行出来ません。'#13'状態を確認してくだい(OKで前処理から続行します)' OUTPUTBUF[ii]
            goto BEFORE_PROC
         else
            pause 3
            goto CHK_PROCESS_ASTART
         endif
      endif
   next
endif

上記の33行目以降を下記に変更

CHKSTATUS = 1

if OUTPUTCNT > 0 then
   CHKSTR = '/bin/java'
   for ii 0 OUTPUTCNT - 1
      strscan OUTPUTBUF[ii] CHKSTR
      if result > 0 then
         CHK_COUNT_NUM = CHK_COUNT_NUM + 1
         if CHK_COUNT_NUM >= CHK_COUNT_OVER then
            CHKSTATUS = 2
            messagebox 'プロセスが実行中で作業を続行出来ません。'#13'状態を確認してくだい(OKで前処理から続行します)' OUTPUTBUF[ii]
         else
            CHKSTATUS = 3
            pause 3
         endif
      endif
   next
endif

if CHKSTATUS = 2 then
   goto BEFORE_PROC
elseif CHKSTATUS = 3 then
   goto CHK_PROCESS_ASTART
endif

e-Taxを使って感じたこと

源泉徴収手続きの期限が迫ってきたので、e-Taxでやってみました。と、言っても仕事のタイミング的に昨年中は特に払うものは無いのですが、無いなら無いとしての手続きをしなくてはなりません。。
 ルート証明書をインストールし、Webから開始届出の手続きをすると、利用者識別番号が表示されるので、その番号でログインして利用者情報の登録をしたら、各種申告の入力をするだけです。筆者の場合は、特例の申請をしているので、「給与所得・退職所得等の所得税徴収高計算書(納期特例分)」で入力して終わりです。
 ちょっと違和感を覚えたのが、開始届出を画面から入力している時点で殆どの利用者の属性情報を入力しているはずなのに、またログイン後に利用者情報を入力しなければならないという事。2重入力しなければならない理由は何なのか、職業上いろいろ想像してしまいます。ま、殆どの場合システム構成とかデータ設計上の問題でそうなっているだけだと思いますが。。
 他にも、拡張子がdoだからStrutsを使っているっぽいとか、Javaアプレットを使う必要性ってホントにあるの?ブラウザはともかく、端末環境に依存することはやっちゃダメだよね、とか、画面デザインの野暮ったい感じは開発ベンダーに丸投げしたのでは?とか、画面上の文言表記だけでなくユーザー目線で設計された感じが全くしない、とか、既存の用紙や手続フローを置き換える以外のシステム化コンセプトを少しも考えなかったんだろうな、とか、そもそも肥大化・複雑化しすぎたスキームの再設計や膨大な種類の取扱用紙の再整理をせずに表面だけシステム化したところで解りやすくなることはない、など、いろいろ示唆に富んだシステムですね。
 イータ君ってイタキャラって事かな?

要件はちゃんと確認しないと・・・VFSで作り直し

ちょっと前に書いたログファイルをダウンロードする機能ですが、実機となるLINUX上にデプロイすると動きませんでした。。。サーバへ接続はできているようなのですが瞬時に切断されているようです。原因切り分けの為、作業中に使っているユーザーIDで試してみると特に問題無く動いてます。TeraTermから指定のIDでログインしてみると即切断されたようになり、WinSCPでSFTPログインすると問題無くアクセスできます。
 で、要件を思い出すと、「SFTPはOK」ではなく、「SFTPのみOK」であった事に気が付きました。与えられたユーザーIDでは、SSH接続ができないセキュリティ設定になっていたという事なんです。(sshd_configでSFTP専用にされてました)
 要件上はたった2文字の違いですが、実装上は明らかに別物であり、SSHで接続するような実装ではNGという事になります。
 となるとGamynedのようにSSHを前提にしたライブラリでなく、SFTPのみでサーバへアクセスできるライブラリを探し、ApacheCommonsのVFSに落ち着きました。なお、VSFにはJschが必要みたいです。
 login,logoutはなさそうなんで、ManagedBeanの呼び出しメソッド引数にID、パスワード、ホスト名を追加して、SFTP処理クラスが殆どの変更箇所となります。とりあえず、ファイル受信は出来ましたので別途整理します。

  <dependencies>
	<dependency>
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-vfs2</artifactId>
		<version>2.0</version>
	</dependency>
	<dependency>
		<groupId>com.jcraft</groupId>
		<artifactId>jsch</artifactId>
		<version>0.1.53</version>
	</dependency>
	<dependency>
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-lang3</artifactId>
		<version>3.4</version>
	</dependency>
  </dependencies>
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemManager;
import org.apache.commons.vfs2.FileSystemOptions;
import org.apache.commons.vfs2.FileType;
import org.apache.commons.vfs2.VFS;
import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder;

/**
 * SFTPファイルアクセスクラス by apache commons vfs
 * */
@Stateless
public class SFTPFileAccess {
	
	public SFTPFileAccess(){
	}

	/**
	 * リモートホスト上の指定ディレクトリのファイル一覧を取得する
	 * @param String RemoteDir ディレクトリ名
	 * @param List<LogFile> セットするファイル一覧
	 * @param String User ユーザーID
	 * @param String Password パスワード
	 * @param String HostName ホスト名
	 * @throws IOException
	 * */
	public void getSFTPList(String RemoteDir, List<LogFile> FileList, String User, String Password, String HostName) throws IOException {

		FileSystemOptions opts = new FileSystemOptions();
		SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(opts, "no");
		FileSystemManager fsManager = VFS.getManager();
		
    	String URL = "sftp://"+URLEncoder.encode(User, "UTF-8");
    	
    	URL = URL +":" + URLEncoder.encode(Password, "UTF-8");
    	URL = URL +"@" + URLEncoder.encode(HostName, "UTF-8");
    	URL = URL + RemoteDir;
    	
		FileObject localFileObject=fsManager.resolveFile(URL);
		
		//Directoryチェック
		if ( !localFileObject.getType().equals(FileType.FOLDER) ){
			return;
		}
		//ファイル存在チェック
		if( ! localFileObject.getType().hasChildren() ){
			return;
		}

		FileObject[] children = localFileObject.getChildren();
        for (int i = 0; i < children.length; i++) {
        	if(StringUtils.isEmpty(children[ i ].getName().getBaseName())){
	        	LogFile file = new LogFile();
	        	file.setFileName(children[ i ].getName().getBaseName());
	        	file.setDirectory(RemoteDir);
	        	file.setFileSize(children[ i ].getContent().getSize());
	        	Date tm = new Date(children[ i ].getContent().getLastModifiedTime());
	        	file.setUpDateTime(DateFormatUtils.format(tm, "yyyy/MM/dd HH:mm:ss"));
	        	FileList.add(file);
        	}
        }
    }
    /**
     * SFTPファイル受信
     * @param remoteFile 受信元ファイル
     * @param stream ストリーム
     * @param String User ユーザーID
     * @param String Password パスワード
     * @param String HostName ホスト名
     * */
    public void getFile( String remoteFile, OutputStream stream, String User, String Password, String HostName) throws IOException{
		FileSystemOptions opts = new FileSystemOptions();

		SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(opts, "no");
		FileSystemManager fsManager = VFS.getManager();
        
		String URL = "sftp://"+URLEncoder.encode(User, "UTF-8");
    	URL = URL +":" + URLEncoder.encode(Password, "UTF-8");
    	URL = URL +"@" + URLEncoder.encode(HostName, "UTF-8");
    	URL = URL + remoteFile;
    	
		FileObject localFileObject = fsManager.resolveFile(URL);
		
		if(!localFileObject.exists()){
			throw new IOException(); 
		}
		
		InputStream inputStream = localFileObject.getContent().getInputStream();

		BufferedOutputStream bos = new BufferedOutputStream(stream);
        
        byte[] buffer = new byte[1024];
        int readLength = 0;
        
        while( (readLength = inputStream.read(buffer)) != -1){
          bos.write(buffer, 0, readLength);
        }
        // close
        inputStream.close();
        bos.close();
    }
}

源泉徴収の知らせがきた

税務署から「源泉所得税及び復興所得税の納付期限のお知らせ」というハガキが来ていました。
9月末に「給与支払事務所等の開設申請書」の届けを出してから、それ以降、いつ手続きすればよいのかわからなかったんですが、ハガキに納付期限が1月20日と書いてありました。となると、「源泉所得税の納期の特例の承認に関する申請書」によって半年に一度手続きすればよいはずなので、1月と7月がそのタイミングなんですね。ようやく解りました。

bootstrapのdatepickerを使ってみる

前回、今作成中のWebLogicアプリにbootstrapを使う事にしたので、次は日付入力にdatepickerを使ってみることにしました。datepickerって、使う方も入力しやすいし、作る方も余計な日付チェック処理を作らなくて済むし、素晴らしいコンポーネントだと思います。昔はテキスト入力エリアを年/月/日と3個繋げて、日付妥当性やメンドクサイうるう年チェックとかしてたんですよ・・・
しかも、ダウンロードURLではオプション設定の書き方も教えてくれてます。
bootstrap-datepicker
JSF2画面xhtmlの該当箇所は下記ですが、そのままだとBean側が更新されないので、f:ajaxで通知してます。

<h:outputStylesheet name="bootstrap/css/bootstrap-datepicker.min.css" />
<h:outputScript name="bootstrap/js/bootstrap-datepicker.min.js" />
<h:outputScript name="bootstrap/js/bootstrap-datepicker.ja.min.js" />
<script type="text/javascript">
$(function() {
  $('.datepicker').datepicker({
    language: 'ja',
    format: 'yyyy/mm/dd',
    autoclose: true,
    todayHighlight: true
  });
});
</script>
・・・中略
          <h:inputText value="#{bean.inDate}" class="from-control datepicker" >
             <f:ajax  execute="@this"/>
          </h:inputText>
・・・以下省略

画面メニューにbootstrapのnavbarを使ってみる

今作っているWebアプリですが、ログインユーザー権限によりメニューのリンク表示を切り替える要件の実装について、一緒に作ってるメンバーに聞いてみたら、bootstrapのnavbarがかっこいいとの事で使ってみました。
各画面で指定するテンプレートxhtmlにbootstrapのnavbarをセットする形です。なお、bootstrapのバージョンは少し古いですが2.3.2を使ってます。
bootstrapをダウンロードしたら、Webソース内のresourcesにダウンロードした各ディレクトリを入れ、メニューを表示する各画面xhtmlでは、下記のようにテンプレートを指定します。

<ui:composition template="../templates/menu.xhtml">

テンプレートとして指定したmenu.xhtmlですが、その中の「SessionManager」はログイン情報を保持しているSessionScopedBeanで、ログイン時にログイン日時をlogintimeに、各権限の値をroleCdにセットし、xhtml側ではui:fragmentのrenderedで表示を切り替えます。

<h:outputStylesheet name="bootstrap/css/bootstrap.css" />
<h:outputStylesheet name="bootstrap/css/bootstrap-responsive.css" />
<h:outputScript name="bootstrap/js/bootstrap.js" />
<h:body>
  <nav class="navbar navbar-inverse navbar-default">
    <div class="navbar-inner">
    <div id="container">
      <h:form>
        <ul class="nav navbar-nav">
          <ui:fragment rendered="#{SessionManager.roleCd == 1}">
            <li><a href="../sales/page1.xhtml">ファイル照会</a></li>
            <li><a href="../sales/page2.xhtml">状態確認</a></li>
            <li><a href="../common/pwchange.xhtml">パスワード更新</a></li>
          </ui:fragment>
          <ui:fragment rendered="#{SessionManager.roleCd == 2}">
            <li><a href="../usermanage/page1.xhtml">ユーザー管理</a></li>
            <li><a href="../usermanage/page2.xhtml">ユーザー一覧</a></li>
            <li><a href="../common/pwchange.xhtml">パスワード更新</a></li>
          </ui:fragment>
・・・他の設定は割愛
          <ui:insert name="navlinks" ></ui:insert>
        </ul>
        <h:commandButton class="btn btn-primary btn-lg pull-right" value="ログアウト" action="#{SessionManager.logout}" />
        <h:outputText class="span3 pull-right" style="color: #ffffff;" value="#{SessionManager.logintime}"/>
        </h:form>
      <ui:insert name="navinfo"/>
    </div>
    </div>
  </nav>

ログインした権限に従い下記のようにメニューを表示されます。
navbar

税務講習会に行く

新規起業者向けの税務講習会をやりますと前にハガキが来ていたので、今日、松戸法人会に行ってきました。
講習会の構成は法人税、消費税の概要と源泉徴収の3点。税務署の人が講師で、参加者は15人くらいでしょうか、9時半から2時間程度で終わりました。
税務署の人に直接聞いて確認したところ、前に保留していた役員報酬に関する届け出は、定額であれば特に税務署に届け出の必要は無く、普通に帳簿を付けて申告するだけ、あとは源泉徴収で行えばいいとの事。なので、法人税は基本的に決算期まで特に何もいらないし、消費税も2年後からだし売上的にも当面考えなくてよさそう。。まずは、源泉徴収ですが、「源泉所得税の納期の特例の承認に関する申請書」を出しているので、半期に一度、今はE-Taxで申告や引落の手続きができるみたいなので、起業に関してはだいぶ整理が付いた気がします。
源泉徴収って、サラリーマン時代は会社がやってくれていたので、ほとんど内容を知りませんでしたが、計算の仕方とかみてると国の方もいろいろと所得再分配とか考えてこうしているのでしょうが、もうちょっとシンプルにならないものでしょうかね?

P-COMマクロでホスト作業を自動化

 ホスト(=メインフレーム)ってまだ結構使っているところが多いように思います。
筆者が知る限り(3270系しか知りませんが)、エミュレータにPersonal Communications(P-COM)かFalconのどちらかを使っていて、どちらもマクロ機能があり、主に画面操作を自動化することが出来ます。
で、ちょっと前に、とあるホストの運用作業を自動化したいという話があったので、P-COMのホスト・アクセス・クラス・ライブラリーを使ったVBスクリプトを作ってみました。P-COMのホスト・アクセス・クラス・ライブラリーについては下記が参考になります。
http://www-01.ibm.com/support/knowledgecenter/SSEQ5Y_5.9.0/com.ibm.pcomm.doc/books/html/host_access08.htm?lang=ja
基本的にP-COM起動やホスト接続、終了以外は、画面の座標軸(縦と横)に対して文字を入力したり、指定場所に表示されているのが特定の文字かをチェックするという繰り返しになると思いますので、VBSではそれらをサブルーチンにしてます。
また、スクリプトの引数として、ログインIDとパスワード及び、事前に用意が必要な接続プロファイル(拡張子がwsのやつ)を指定する形になってます。

'2秒*30でタイムアウト判定
Const DEF_MAXCOUNT = 30

Dim oConnMGR, oECLPS, oECLOIA
Dim oParam, HUserID, HPassWd, ProFile
Dim i, idx

Set oParam = WScript.Arguments

For idx = 0 To oParam.Count - 1 
   Select Case idx
   Case 0
      HUserID = UCase( oParam(idx) )
   Case 1
      HPassWd = UCase( oParam(idx) )
   Case 2
      ProFile = oParam(idx)
   End Select
Next
'''''引数エラー処理割愛

'PCOMオブジェクトの作成
Set oConnMGR = CreateObject("PCOMM.autECLConnMgr")
Set oECLPS = CreateObject("PCOMM.autECLPS")
Set oECLOIA = CreateObject("PCOMM.autECLOIA")

'接続開始
Host_Start

If Check_Host("DISP3270") = false Then
	WScript.Sleep(10000)
	If Check_Host("DISP3270") = false Then
		Host_End
		WScript.Quit(-11)
	End If
End If

'(13/01) ログイン [LOGON ユーザーID] + CTL
Host_Input 13, 1, "LOGON " & HUserID , "[enter]", 1

'(8/20) パスワード [      ] + CTL 2回
Host_Input 8, 20, HPassWd , "[enter]", 2

Dim iPutLine, InputCmd

'画面遷移チェック (4,59) ユーザーIDが表示されるのをチェック
If Host_OutputChk(4, 59, Len(HUserID), HUserID) = False Then
	'エラー
	Host_Input 0, 0, "", "[pf3]", 2
End If

・・・以下省略

'終了
'F3 2回で後処理へ戻る
Host_Input 0, 0, "", "[pf3]", 2

'(5/27) 印刷せずにデータセットを削除「2」を選択 [2] + CTL
Host_Input 4, 27, "2", "[enter]", 1

'(2/01) ログオフ [LOGOFF] + CTL
Host_Input 2, 1, "LOGOFF", "[enter]", 1

'切断
Host_End

'ここでスクリプト終了
WScript.Quit(0)

'以下サブルーチン
'1 ホスト接続開始
Sub Host_Start
	
	oConnMGR.autECLConnList.Refresh
	
	'事前に取得したコネクションの数だけループして接続を停止する
	For i = 1 To oConnMGR.autECLConnList.Count   
		oConnMGR.autECLConnList(i).StopCommunication()
		WScript.Sleep 5000
		oConnMGR.StopConnection oConnMGR.autECLConnList(i).Name, "saveprofile=no"
		WScript.Sleep 5000
	Next

	Dim strCmd
	strCmd = "profile=" & ProFile & " connname=A"
	oConnMGR.StartConnection strCmd 
End Sub

'2 ホスト接続終了
Sub Host_End
	
	oConnMGR.autECLConnList.Refresh
	If oConnMGR.autECLConnList.Count > 0 Then
		oConnMGR.StopConnection "A", "saveprofile=no"
		WScript.Sleep 3000
	End If
End Sub

'3 ホストセッションの取得(接続タイプ)
Function Check_Host(ConnType)

	For i = 0 To DEF_MAXCOUNT
		oConnMGR.autECLConnList.Refresh
		If oConnMGR.autECLConnList.Count > 0 Then
			Exit For
		End If
		WScript.Sleep 2000
	Next

	For i = 1 To oConnMGR.autECLConnList.Count   '取得したコネクションの数だけループ
	    If oConnMGR.autECLConnList(i).ConnType = ConnType Then
	        '該当セッションのハンドルからセッションオブジェクトをセット
	        oECLPS.SetConnectionByHandle (oConnMGR.autECLConnList(i).Handle)
	        oECLOIA.SetConnectionByHandle (oConnMGR.autECLConnList(i).Handle)
	        Exit For
	    End If
	Next
	Check_Host = True
	If i > oConnMGR.autECLConnList.Count Then
		Check_Host = False
	Else
		For i = 0 To DEF_MAXCOUNT
			WScript.Sleep 2000
			'処理完了待ち
			If oECLOIA.InputInhibited = 0 Then
				If Host_OutputChk(12, 16, 5, "*****") = True Then
					Exit For
				End If
			End If
		Next
		'起動確認タイムアウト
		If i >= DEF_MAXCOUNT Then
			Host_End
			WScript.Quit(-2)
		End If
	End If

End Function

'4 ホスト画面への入力(Y座標、X座標、文字列、キー入力、キー入力回数)
Sub Host_Input(argY, argX, argStr, argKeyStr, argKeyCnt)
	Dim iKey
	
	If argY <> 0 And argX <> 0 Then
		oECLPS.SetCursorPos argY, argX
		oECLPS.SendKeys argStr
	End If
	
	'エンターキー
	If argKeyStr <> "" Then
		For iKey = 1 To argKeyCnt
			oECLPS.SendKeys argKeyStr
			For i = 0 To DEF_MAXCOUNT * 10
				WScript.Sleep 200
				'処理完了待ち
				If oECLOIA.InputInhibited = 0 Then
					Exit For
				End If
			Next
			If i >= DEF_MAXCOUNT * 10 Then
				Host_End
				WScript.Quit(-2)
			End If
		Next
	End If

End Sub

'5 画面表示内容のチェック(Y座標、X座標、文字列の長さ、文字列 一致したらtrueを返す
Function Host_OutputChk(argY, argX, argLen, argStr)

	Dim strBuff
	
	strBuff = oECLPS.GetTextRect(argY, argX, argY, argX + argLen-1)
	If Trim(strBuff) = Trim(argStr) Then
		Host_OutputChk = True
	Else
		Host_OutputChk = False
	End If

End Function

やっかいなのは、あくまで画面操作なので接続とか応答に時間が掛かったり、いろいろ問題が出てきます。
ちょっと雑にSleepをガンガンかましていますが、お察しください。。

JSF2カスタムコンバータでデコードしつつdataTableの背景を変える

今作っているWEBアプリで、画面に一覧を表示して、一覧表示内の項目値によって背景色を変えたいという要件がありました。
で、JSF2のカスタムコンバータを使って、コード値を文言にデコードしつつ、裏でCSSクラスを変えることで背景色が動的に変わる形にしてみました。コンバータは一つで、どのコード体系でデコードするかは、attributeでコンバータに情報を渡す構成になってます、とりあえずは。。

CSS抜粋

.listTable th {
	font-weight: normal;
	color: #ffffff;
	background-color: #339933;
	text-align: center;
	padding: 1px;
	border-right-style: solid;
	border-bottom-style: solid;
	border-right-color: #CCCCCC;
	border-bottom-color: #CCCCCC;
	font-size: 10pt;
	white-space: nowrap;
}

.listTable td {
	padding: 1px;
	background-color: #ffffff;
	border-right-style: solid;
	border-bottom-style: solid;
	border-right-color: #CCCCCC;
	border-bottom-color: #CCCCCC;
	font-size: 11pt;
}

/* 警告表示 */
.cError {
	background-color: #ff0000;
	color: #ffffff;
 	display:block;
}
/* 一部異常表示 */
.cCaution {
	background-color: #ffff00;
 	display:block;
}

/* 対象外表示 */
.cNoTaget {
 	background-color: #a4a4a4;
 	display:block;
}

/* 通常表示 */
.cDefault {
	background-color: #ffffff;
 	display:block;
}

xhtmlのdataTable部分抜粋
※wordsはwords.propertiesのファイル側に定義している項目タイトルの文言です

    <h:dataTable id="Info" var="list" value="#{listView.itemlist}" styleClass="listTable">
      <h:column>
        <f:facet name="header" >#{words.Label_coName}</f:facet>
        <h:outputText value="#{list.coName}"/>
      </h:column>
      <h:column>
        <f:facet name="header" >#{words.Label_coCode}</f:facet>
        <h:outputText value="#{list.coCode}"/>
      </h:column>
      <h:column>
        <f:facet name="header" >#{words.Label_status1}</f:facet>
        <h:outputText value="#{list.status1}">
          <f:converter converterId="statusConverter"/>
          <f:attribute name="ConvertType" value="1"/>
        </h:outputText>
      </h:column>
      <h:column>
        <f:facet name="header" >#{words.Label_status2}</f:facet>
        <h:outputText value="#{list.status2}">
          <f:converter converterId="statusConverter"/>
          <f:attribute name="ConvertType" value="2"/>
        </h:outputText>
      </h:column>
以下省略

    </h:dataTable>

カスタムコンバータ

@FacesConverter(value="statusConverter")
public class StatusConverter implements Converter {

	private static final String DEFVALUE = "X";

	private static final String DEFAULT = "cDefault";
	private static final String NOTARGET = "cNoTaget";
	private static final String CAUTION = "cCaution";
	private static final String ERROR = "cError";
	
	// ConvertType 1 OKNG表示
	private static final Map<String, String> OkNgState;
	static {
		HashMap<String, String> map = new HashMap<String, String>();
		map.put(DEFVALUE, "-");
		map.put("1", "OK");
		map.put("2", "NG");
		OkNgState = Collections.unmodifiableMap(map);
	}
	// ConvertType 2 現在状況表示
	private static final Map<String, String> NowState;
	static {
		HashMap<String, String> map = new HashMap<String, String>();
		map.put(DEFVALUE, "-");
		map.put("0", "処理前");
		map.put("1", "仕掛中");
		map.put("2", "処理済");
		NowState = Collections.unmodifiableMap(map);
	}
	// ConvertType 3 結果表示
	private static final Map<String, String> ResStatus;
	static {
		HashMap<String, String> map = new HashMap<String, String>();
		map.put(DEFVALUE, "-");
		map.put("0", "正常");
		map.put("1", "異常");
		map.put("2", "警告");
		ResStatus = Collections.unmodifiableMap(map);
	}
	//警告表示対象
	private static final Map<String, String> Caution;
	static {
		HashMap<String, String> map = new HashMap<String, String>();
		map.put("異常", ERROR);
		map.put("警告", CAUTION);
		map.put("-", NOTARGET);
		Caution = Collections.unmodifiableMap(map);
	}

	@Override
	public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2) {
		String DispValue="";
		String InnerValue = (String) arg2;
		String StyleClass = DEFAULT;
		
		Map<String,String> ConvertMap = null;
		Map<String,Object> attrMap = arg1.getAttributes();
		
		//f:attribute より変換タイプを取得
		String type = (String)attrMap.get("ConvertType");
		if (! StringUtils.isNumeric(type) ){
			return "";
		}

		switch (Integer.valueOf(type)){
			case 1:
				ConvertMap = OkNgState;
				break;
			case 2:
				ConvertMap = NowState;
				break;
			case 3:
				ConvertMap = ResStatus;
				break;
			default:
				DispValue = InnerValue;
				ConvertMap = null;
				break;
				
		}
		//デコード
		if(ConvertMap != null){
			DispValue = ConvertMap.get(InnerValue);
			//初期値
			if (DispValue == null ){
				DispValue = ConvertMap.get(DEFVALUE);
			}
		}
		//スタイルクラス指定
		if(Caution.get(DispValue) != null){
				StyleClass = Caution.get(DispValue);
			}
		}
		//色変
		if (arg1.getClass() == HtmlOutputText.class){
			HtmlOutputText ui = (HtmlOutputText) arg1;
			ui.setStyleClass(StyleClass);
		}
		return DispValue;
	}
	//こっちは関係無し
	@Override
	public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2) {
		return null;
	}
}

ManagedBean抜粋
※一覧表示対象のitemInfoの実装は割愛します。プロパティとゲッタ・セッタがあるだけです。。

@ManagedBean(name="listView")
@ViewScoped
public class ListView {
	private List<itemInfo> itemlist;

	public List<itemInfo> getitemlist() {
		//ここでitemlistのデータをセットしてますが省略します
		return itemlist;
	}
以下、省略

こんな感じで表示できそうなので、別途整理して実装します。
上記例では色が変わるのは行でなくセル単位になります。なお、dataTableの場合、その中のoutputTextはdivに置き換えらえるようなので、コンバータではtdのスタイルを変えているのではなくdivが対象になってます。それをCSSでカバーしているのですが、CSSは得意でないので。。