JSF2でbootstrapのdatetimepickerを使ってみる

 前回、画面の日付入力にdatepickerを使ってみましたが、
bootstrapのdatepickerを使ってみる
今度は時間の入力も必要な画面を追加する事になり、datetimepickerを使ってみる事にしました。
 使用したのは下記です。日本語も対応されています。
http://www.malot.fr/bootstrap-datetimepicker
 ダウンロードしたJSとCSSをdatepickerの時と同じようにコンテンツディレクトリ配下のresource内に配置。
resource

xhtmlは下記のようになりました。aInfoという管理BeanにdispFromDateとdispEndDateの2つの文字列日付プロパティがあり、ボタン押下でgetListを呼び出し結果をresultListにdataTableで表示するという内容です。

<script type="text/javascript">
$(function() {
	  $('.datetimepicker').datetimepicker({
		  format: 'yyyy/mm/dd hh:ii:ss',
	      autoclose: true,
	      todayBtn: true,
	      pickerPosition: "bottom-left",
		  language: 'ja'
	  });
	});
</script>
・・・中略

<h:inputText id="fromDate" value="#{aInfo.dispFromDate}" class="datetimepicker from-control span2" >
  <f:ajax  execute="@this"/>
</h:inputText>
<h:inputText id="toDate" value="#{aInfo.dispEndDate}" class="datetimepicker from-control span2" >
  <f:ajax  execute="@this"/>
</h:inputText>
・・・中略

<h:commandButton id="listget" action="#{aInfo.getList}" value="表示" class="btn btn-default safebutton">
  <f:ajax execute="fromDate toDate" render="resultList"/>
</h:commandButton>

・・・中略
<h:dataTable id="resultList" var="list" value="#{aInfo.resultList}" class="table table-striped" >
・・・以下略

なお、日付フォーマットは今回の要件的に秒も対象にしていますが、datetimepickerのUI自体は分までが対応範囲なので秒については直入力という整理です。

createQueryとcreateNativeQueryは結構違う

 現在、JavaEEで作っているアプリケーションに画面を追加する事になり、それまではJPAのJPQLを使ってのcreateQueryで済んでいたのですが、今回はGROUP BYとかUNIONとかでSQLを作る必要があり、JPQLでは無理そうなのでcreateNativeQueryで実装する事にしました。
 で、最初はcreateQueryをcreateNativeQueryに置き換えてSQL文を噛ませばいいかなと思っていたのですが、どうやら結構違うらしい。。
 一つ目の違いは、createQueryならテーブル定義に沿って作ったEntityに直接入れる事が出来ますが、ちょっと複雑なSQLだと問い合わせ結果に一致したEntityを作る事自体が難しい。
 二つ目はバインド変数の違い。createQueryならバインド変数は[:PARAM]のように文字として指定して下記のように変数をセットして結果を取得できますが、

List<LoginUser> result = em.createQuery(
  "SELECT c FROM LoginUser c WHERE c.USER_TYPE < '5' AND c.STATUS_FLG < :PARAM ORDER BY c.USER_ID")
  .setParameter("PARAM", "9")
  .getResultList();

createNativeQueryでは[?]でないとダメという解りにくい相違点。。
結果的に下記のようになりました。

String SQL="SELECT KEY,SUBKEY,SUM(AMOUNT) FROM TABLE_A WHERE REGDATE = ? AND (? IS NULL OR SUBKEY = ? ) " +
		"UNION SELECT KEY,SUBKEY,SUM(AMOUNT) FROM TABLE_B WHERE REGDATE = ? AND (? IS NULL OR SUBKEY = ? ) ";

Query q = em.createNativeQuery(SQL);
for (int i = 0 ; i < 6 ; i = i + 3){
	q.setParameter(i+1, StringUtils.remove(StringUtils.remove(getDate(),"/"),":"));
	q.setParameter(i+2, getSubKey());
	q.setParameter(i+3, getSubKey());
}
List<Summary> list = new ArrayList<Summary> ();

List<Object[]> results = q.getResultList();
for (Object[] it : results) {
	Summary sum = new Summary();
	sum.setKey((String)it[0]);
	sum.setSubKey((String)it[1]);
	BigDecimal bd = (BigDecimal)it[2];
	sum.setCount((int)bd.intValue());
	list.add(sum);
}

※上記のSummaryは上記クエリ結果をセットする為だけのクラスです。

なお、数値はBigDecimalで扱うようです。
バインド変数の仕様違いにはちょっとハマりました。。

PayaraをCENTOS7で動かしてみる

 せっかくJavaEE7の仕事しているので、他にも何か作ろうかなと思い、自宅のVMにCENTOSを入れてPayaraを動かすことにしました。weblogicは高いし、glassfishは将来性が。。Payaraもglassfishなんですけど、将来性があるかと思い、こちらからMulti-Language Web Profileをダウンロード
http://www.payara.fish/all_downloads

なお自宅のVMはGIGABYTEのBRIX。
http://www.gigabyte.jp/products/product-page.aspx?pid=4581#ov
1年半くらい前にSSD250Gとメモリ8G*2を突っ込み合計8万くらいで作ったもので、VMware ESXiを入れてます。先日、Windows Server 2016 Technical Preview 4も仮想に入れてみました。入れただけですが。。これは今年9月くらいには使えなくなるみたいです。

 久しぶりにCENTOSをググってみるとバージョンが7になっています。4GあるDVDISOをダウンロードし、仮想マシンを作成してDVDISOからインストール。インストールウィザードだけで殆ど設定は完了。
centos7
次にTeraTeamからCENTOSに入ってJavaインストール

 yum search jdk java でちょうどいいのを探して、
 yum install java-1.8.0-openjdk.x86_64

次はダウンロードしたPayaraを、/optに解凍します。

で早速、Payaraを解凍したディレクトリpayara41/glassfish/binにあるstartservを実行。

動いたらしいので、Payaraの管理コンソールを表示してみます。 http://サーバIP:4848
あれれ、繋がらない?TeraTeamからサーバに入って、

ps -ef | grep java
 ちゃんとプロセスは動いています。

次はyum nmapでインストール後、ポートチェック。
 nmap localhost → 4848/tcp open appserv-http
 OKだけど、なんでだろう??

ネットワーク系問題の匂いがぷんぷんする中、原因をググっていくと、下記を発見
https://blog-kazuhisya.rhcloud.com/2014/06/15/getting-started-with-rhel7/

 firewall-cmd –permanent –add-port=4848/tcp
 やっとコンソールを表示できました。。が、リモートからだとデフォルトではコンソールにログイン出来ない模様。。

TeraTeamに戻って上記のbinで、
./asadmin enable-secure-admin
./asadmin change-admin-password
であとは再起動
./stopserv
./startserv &
やっと入れました。。
payara
で、何作ろうか?

JSF2 Ajaxのエラーハンドリング

前回、画面エラー発生時のログ出力を整理しましたが、数秒毎に最新の状態にリフレッシュ表示する要件があり、Ajaxで実装してました。AjaxエラーのハンドリングはAjax側でハンドリングしなくてはなりません。
ネットで探してみたらOracleのサイトにjsf.ajax.addOnErrorというのが見つかりました。
https://docs.oracle.com/cd/E17802_01/j2ee/javaee/javaserverfaces/2.0/docs/js-api/symbols/jsf.ajax.html

JavaScriptのsetIntervalでフォームを5秒毎にリフレッシュさせているjsf.ajax.requestの箇所にエラーハンドリングを追加します。エラーが発生したらalertで問題発生をダイアログで表示させてますが、clearIntervalをしてもイベントが残っているみたいで、Alartのダイアログがしつこく出続けます。。仕方ないので1回だけ表示するように無理やり制御。

<script type="text/javascript">
var dispTimer1;
var errFlg;

errFlg = false;
var handleError = function handleError(data){
	clearInterval(dispTimer1);
	if ( errFlg == false){
		errFlg = true;
 		alert("情報の読み込みに失敗しました");
		location.reload();
	}
}

$(document).ready(function(){
  dispTimer1 = setInterval(function(){
    jsf.ajax.addOnError(handleError);
	jsf.ajax.request('mainform' , null,
            {execute: 'mainform',render:"mainform"}
    );
  }, 5000);
});
</script>

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

今作っている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;"/>

要件はちゃんと確認しないと・・・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();
    }
}

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

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は得意でないので。。

ganymed ssh2を使ってダウンロード画面を作る

今作っているWebアプリケーションでサーバのログファイルをダウンロードしたいという要件があり、そこでganymedを使ってみることにしました。アプリケーションをデプロイしているサーバとは別に複数存在するサーバ上にあるログファイルを画面から選択してクリックでダウンロードという流れです。
 なお、サーバとログファイルが格納されているディレクトリ情報は別途DB等から取る形にして、下記はSFTPでファイルを取得する箇所の抜粋です。

POM.xml ganymed指定

    <dependency>
	  <groupId>ch.ethz.ganymed</groupId>
	  <artifactId>ganymed-ssh2</artifactId>
	  <version>262</version>
     </dependency>

画面に表示するログファイル情報

public class LogFile {
	
	/** ファイル名 */
	private String fileName;
	
	/** 更新日時 */
	private String upDateTime;
	
	/** ディレクトリ */
	private String directory;
	
	/** サイズ */
	private long fileSize;
	
	/** ホスト名 */
	private String hostName;

以下、ゲッタとセッタは割愛

SFTP処理
※サーバは一般的なID・パスワード認証です。

import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.SFTPv3Client;
import ch.ethz.ssh2.SFTPv3DirectoryEntry;
import ch.ethz.ssh2.SFTPv3FileHandle;

/**
 * SFTPファイルアクセスクラス by Ganymed SSH-2
 * */
@Stateless
public class SFTPFileAccess {
	private final int PORT = 22;
	private SFTPv3Client sftp = null;
	private Connection conn = null;
	
	/**
	 * リモートホストへログインします
	 * @param HostName リモートホスト名
	 * @param User ログインユーザー名
	 * @param Password ログインパスワード
	 * @throws IOException 
	 * 
	 * */
	public boolean loginHost(String HostName, String User, String Password) throws IOException{
		conn = new Connection(HostName, PORT);

		conn.connect();
        if (!conn.authenticateWithPassword(User, Password)) {
        	//エラー
        	return false;
        }
        return true;
	}
	
	/**
	 * リモートホスト接続をクローズします
	 * @throws IOException 
	 * 
	 * */
	public void logOutHost() throws IOException{
		if(conn != null){
			conn.close();
		}
        return;
	}

	/**
	 * リモートホスト上の指定ディレクトリのファイル一覧を取得する
	 * @param String RemoteDir ディレクトリ名
	 * @param List<LogFile> セットするファイル一覧
	 * @throws IOException
	 * */
	public void getSFTPList(String RemoteDir, List<LogFile> FileList) throws IOException {
		if(conn == null){
			return;
		}
        sftp = new SFTPv3Client(conn);
        
		List<SFTPv3DirectoryEntry> DirList = sftp.ls(RemoteDir);
		if ( FileList == null ){
			FileList = new ArrayList<LogFile>();
		}
        for (SFTPv3DirectoryEntry Entry : DirList ){
        	//ディレクトリ以外
        	if (!Entry.longEntry.startsWith("d") ){
	        	LogFile file = new LogFile();
	        	file.setDirectory(RemoteDir);
	        	file.setFileName(Entry.filename);
	        	file.setHostName(conn.getHostname());
	        	file.setFileSize(Entry.attributes.size);
                //ファイル更新時間
	        	Date tm = new Date(Entry.attributes.mtime * 1000L);
	        	file.setUpDateTime(DateUtils.format(tm, DateUtils.FORMAT_YYYY_MM_DD_HH_MM_SS));
	        	FileList.add(file);
	    		
        	}
        }
        return;
	}

    /**
     * SFTPファイル受信
     * @param remoteFile 受信元ファイル
     * @param stream 出力ストリーム
     * 
     * */
    public void getFile( String remoteFile, OutputStream stream) throws IOException{
		if(conn == null){
			return;
		}
    	if(sftp == null){
            sftp = new SFTPv3Client(conn);
    	}
        long remoteFileSize = sftp.stat(remoteFile).size;
        // open remote file with read only
        SFTPv3FileHandle sftpFileHandle = sftp.openFileRO(remoteFile);
        
        BufferedOutputStream bos = new BufferedOutputStream(stream);
        		
        byte[] buffer = new byte[1024];
        long offset = 0;
        int readLength = 0;
        while( (readLength = sftp.read(sftpFileHandle, offset, buffer, 0, buffer.length)) != -1){
          bos.write(buffer, 0, readLength);
          offset += readLength;
        }
        // flush & Close
        bos.flush();
        bos.close();
        sftp.closeFile(sftpFileHandle);
        
        // compare
        if (offset == remoteFileSize) {
        } else {
        	System.out.println("failed: file size is different from remote. remote:" + 
              remoteFileSize+", local:"+offset);
        }
        return;
    }
}

画面処理
※HttpServletResponseのOutputStreamに直ファイルを突っ込むので、一時ファイルを置いたりしません。

/**
 * ログダウンロード画面
 * */
@ManagedBean(name="LogDownload")
@SessionScoped
public class LogDownload implements Serializable {
	private static final long serialVersionUID = 1L;

	protected static Logger logger = LogManager.getLogger();
	
    @EJB
    private SFTPFileAccess ftp;
    
    private String selectedServer;

    private List<LogFile> fileList;
    
	public LogDownload() {
		fileList = new ArrayList<LogFile>();
	}

	public List<LogFile> getFileList() {
		return fileList;
	}

	public void setFileList(List<LogFile> fileList) {
		this.fileList = fileList;
	}

	public String getSelectedServer() {
		return this.selectedServer;
	}

	public void setSelectedServer(String selectedServer) {
		this.selectedServer = selectedServer;
	}

	/**
	 * ディレクトリ選択時にファイル一覧を取得
	 * */
	public void getList(){
		try {
			Map<String,String> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
			String fileDir = params.get("fileDir");

			ftp.loginHost(selectedServer, dao.getServerLoginUser(selectedServer), dao.getServerLoginPasswd(selectedServer));
			
			fileList.clear();
			ftp.getSFTPList(fileDir,fileList);
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * ファイルダウンロード
	 * */
	public void getFile(){
		Map<String,String> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
		String fileName = params.get("fileName");
		String fileDir = params.get("fileDir");
	  
		try {
			HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance() .getExternalContext().getResponse();
			response.setContentType("application/octet-stream");
			response.setHeader("Content-Disposition", "attachment;filename=" + fileName);

			OutputStream a = response.getOutputStream();
			ftp.getFile(fileDir +"/" + fileName , a);
			FacesContext.getCurrentInstance().responseComplete();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

xhtml
※ディレクトリリストの箇所は、上記ソース上割愛しています。
ディレクトリリストの左に置いたボタンクリックで、ファイル一覧を取得し、ファイル一覧のファイル名クリックでダウンロードします。

    <h:form id="listForm">
      <h:dataTable var="dirlist" value="#{LogDownload.directoryList}" rowIndexVar="rowIndex" class="table">
        <h:column>
          <f:facet name="header" >#{words.Label_LogList}</f:facet>
          <h:commandLink value="#{dirlist.INFO_VALUE}" actionListener="#{LogDownload.downLoad}">
            <f:attribute name="directory" value="#{dirlist.INFO_VALUE}"/>
          </h:commandLink>
        </h:column>
        <h:column>
          <h:commandButton id="listget" action="#{LogDownload.getList}" value="#{words.Label_FileListGet}">
            <f:param name="fileDir" value="#{dirlist.INFO_VALUE}" />
            <f:ajax render=":listForm:loglist"/>
          </h:commandButton>
        </h:column>
      </h:dataTable>
    
      <h:dataTable id="loglist" var="list" value="#{LogDownload.fileList}" styleClass="listTable">
        <h:column><f:facet name="header" >ファイル名</f:facet>
        <h:commandLink id="filelistName" value="#{list.fileName}" action="#{LogDownload.getFile}">
        <f:param name="fileName" value="#{list.fileName}" />
        <f:param name="fileDir" value="#{list.directory}" />
        </h:commandLink>
      </h:column>
      <h:column><f:facet name="header">ファイルサイズ</f:facet>
        <h:outputText value="#{list.fileSize}">
          <f:convertNumber groupingUsed="true" />
        </h:outputText>
      </h:column>
      <h:column><f:facet name="header" >ファイル更新日時</f:facet>
        <h:outputText value="#{list.upDateTime}"/>
      </h:column>
    </h:dataTable>    
  </h:form>

エラーハンドリングやサーバの切断、ファイルサイズの数値右寄せ等、細かい箇所がまだ出来ていませんが、とりあえず動きそうです。
最初、ログファイル情報のプロパティ名が先頭大文字とかだと、JSFでエラーになりちょっとハマりました。。