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

フランク・ゲーリー展を見て感じたこと

 以前から、建築業界とシステム業界というのは共通点が結構あるな~と考えていました。作る対象物は違えど設計、構築といった仕事の進め方もそうだし、大手ベンダー=ゼネコン、多重請負とかの業界構造もです。で、今ミッドタウンの21-21DESIGNSIGHTでやっているフランク・ゲーリー展に行ってみようと思った訳です。
 正直、その斬新なデザインは賛否両論あるかと思いますが、筆者が驚いたのは、昔は模型を作っては壊してを繰り返して設計していたのが、今ではデザインコンセプトを模型で確認した後はコンピューターで3Dモデリングし、決められた予算範囲内でデザインを妥協することなく最適な資材構成となるようシミュレーションを繰り返して決定しているという事です。それにより、建築資材の数や搬入時期までを決める事で施工時のコストまでも最適化し、複数の施工業者が見積もっても誤差は0.5%とか。スゲエ!と思ってしまいました。ようするに、それを実現できるソフトウェアを開発した人であるということなんです。建築業界はまったく解らないですが、こういうやり方は一般的なのでしょうかね?国立競技場とかこの人に任せればよかったのにと思いますけど。。
 で、建築とシステム開発の違いについて、フランク・ゲーリー展を見た後に筆者が感じた所ですが、建築では、住宅からビルやホールとか構築対象物の規模が解りやすいという事と、建築での施工業者なる位置付けの仕事がシステム開発に存在しているか?という事です。
 システムの規模って、誰が見ても大規模なものもあれば、それ程でもないものってかなりあるように思いますが、その規模の判断が人それぞれな気がします。大抵大手ベンダーに依頼すれば大規模と見なされていませんかね?クリティカルなシステムだって、例えば5種類のデータしか扱わないのであればそれは対した複雑さは無いはずなんです。また、扱うデータ量が膨大だとしても実装されたシステムを多数のサーバに配置するだけなので、インフラ含めアプリもちゃんと設計されていれば開発自体のコストは何も変わりません。
 あと、施工業者という点では、所謂上流下流といった分け方をしている場合、プログラマーは施工業者みたいなものだという考え方を持っている人もいると思いますが、それを当てはめた方がよいケースは極少数でしかないと筆者は考えます。どちらかというとプログラマーは施工業者というより建築資材供給者の位置づけに近いと思いますが、システムでは1つの設計=1つのプログラムみたいなものです。同じものを量産するという概念がありません。すごく似ている機能が多数存在する事もありますが、それを一つまたは最小限にできないなら設計に問題があるはず。であれば、何を作るべきかを把握しているであろう設計者自身がプログラムを書く方が品質が高く、また分業する事によるコミュニケーションコストも最小化できるはずです。
 システム開発に施工業者が存在していないと考えの元では、建築家フランク・ゲーリーにとってのデザイン活動範囲は、システム開発全ての活動範囲と一致しており、構築対象物は違えど、システム開発におけるプログラミングはデザインという活動範囲に含まれているという事になります。施工業者的であるのは多数のサーバで構成された場合におけるインフラ側の仕事だけではないでしょうか?
 筆者の立場では、今は一人か極少数で開発をしているので、これは注文住宅を自身で設計・施工している工務店の大工みたいなものですかね。こういう戸建レベルの規模の開発案件って結構存在しているはずなんですが、欲しているものの実際の規模と受注側が考えているその規模のズレを注文側が感じるとる事を出来るかという所になります。でも、大規模と言われた物を小規模であると主張するのは結構タイヘンなんですよね。発言に対し全てのリスクを引き受ける気合い!がなければ、なかなか言える事では無いかもしれません。別の方法として、一つのシステムを戸建てレベルの集合体と分割することも出来ますけど、構築後の保守フェーズまで考慮するとこちらもタイヘンかなあ。。

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でエラーになりちょっとハマりました。。

レルムの代わりにJASPICで

 今、企業内で少人数が使用するWEBアプリケーションをWebLogicで動かす前提で作ってますが、ユーザー認証についてどうしようかと要件を確認したところ、少人数使用のアプリなのでLDAP無し、WebLogicに登録・管理したくないのでWeblogicのレルムも使わないでとのこと。となると従来のDBにユーザーテーブル作ってユーザー管理画面とかも作る感じになります。
 ユーザーには数パターンあるうちの1つの権限を付与すれば使える画面と使えない画面が決まるので、基本的にURLで制御できる範囲です。で、いろいろ調べましたが、下記を参考にして、JASPIC(Java Authentication Service Provider Interface for Containers)というJavaEE7仕様に含まれているものを使ってみる事にしました。
http://arjan-tijms.omnifaces.org/2012/11/implementing-container-authentication.html
ここのSETP5にあるServerAuthModule実装のvalidateRequestに認証処理を書き込む形のようです。
下記のサンプルソースも参考になりました。
https://github.com/erik-wramner/YubikeyAuth/tree/master/yubi-jaspic-example

但し、WebLogicで動かす場合は、web.xmlにだけ設定すればよいだけでなく、weblogic.xmlにも同じような設定が必要みたいです。

web.xml

  <security-role>
    <description>管理者</description>
    <role-name>Managers</role-name>
  </security-role>
  <security-role>
    <description>利用者</description>
    <role-name>Users</role-name>
  </security-role>
  
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>manager page</web-resource-name>
      <url-pattern>/manage/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>Managers</role-name>
    </auth-constraint>
  </security-constraint>

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>user page</web-resource-name>
      <url-pattern>/user/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>Users</role-name>
    </auth-constraint>
  </security-constraint>

weblogic.xml

    <wls:security-role-assignment>
      <wls:role-name>Managers</wls:role-name>
      <wls:principal-name>Managers</wls:principal-name>
    </wls:security-role-assignment>
    
    <wls:security-role-assignment>
      <wls:role-name>Users</wls:role-name>
      <wls:principal-name>Users</wls:principal-name>
    </wls:security-role-assignment>

デプロイするとそのインスタンス全体に影響し、開発環境では管理サーバで動かしているので管理コンソールに入れなくなります。。

カテゴリー: Java

フレームワークの性格

 ここ数か月、筆者はとあるシステムベンダーが構築したWebアプリケーションのフレームワークで作成された既存システムのソースを流用して、新規のWebサービスを作ってました。この既存システムは、新規開発から既に10年近く経過し、その間いろいろと変更・追加があったようです。フレームワーク自体はstruts1+Spring+ibatisという構成の上にさらにベンダー独自の実装で構成されたものです。そのベンダーさんに使用許諾を頂きましたが、特にリファレンスとかは無いのでソースを読みながらフレームワークを理解しながらの開発となりました。
 結果、3か月近く開発し、作ったアプリは現在ビジネス要件を満たし動く状態になってきましたが、こういう経験の無い枠組みの中で作るのは思ったよりも結構時間が掛かってしまうものだなあという印象です。一から作れと言われたらそりゃもっともっと時間が掛かるでしょうが。。
 最初、やはり時間の経過でしょうか、今さらこの構成?という感じでしたが、現在は良くできてるなあという思いもあります。ベンダーが作ったフレームワークって知ってる人からすると、結構時間が掛かる調べものの時間がないので、慣れてる人からすれば、ホントに工数を節減できたり、正確な工数を見積もったりできているんだとは思います。ただ、気持ち的には今さらstruts1系?とかここでの経験は他では使えないなどいろいろな感情が絡んでしまい、また、ソースの切り貼りみたいな作業も多く、モチベーションが低下してしまったのは否定できません。Javaの場合は流行り廃りがあるとはいえ、このようなフレームワークは、利用している方からするとシステム更新しない限りずっと使い続けるしかないのはしょうがない事ですが。
 時間の経過と言えば、最大なのはメインフレーム、いわゆるホストと呼んでいるものも一種のフレームワークと呼んでいいと思います。筆者の場合、最初がオープン系畑だったので、前職の時にホスト(zOS)と関わった時は、最初は違和感ありましたが、しばらくして良くできているなあと感じてきました。主にCobolソースとJCLの整然としたリソース構成は、雑然としたオープン系と比較するときれいに見えてきます。80桁×24行しかない画面もむしろ潔いよいです。。
 とはいえ、ホストのような自由度の無いガチガチなタイプがいいか、ちょっとぐちゃぐちゃ感はあるけど自由度のあるタイプがいいのかというと、もちろん筆者は後者の方が断然好きです。

TeraTermマクロでプロセスをKILL

UNIXサーバ作業の自動化として、特定のプロセスをKILLするマクロを書きました。
前提として
・停止対象のプロセスはJavaの常駐JOBが3つ
・プロセス停止前にチェック処理が必要
このチェック処理が複雑なので、マクロにしました。
下記は、サーバにログオンされた状態以降の抜粋です。チェック処理と最後のログオフは省略してます。
実際のツールはHTAで作業メニュー画面があり、画面操作でマクロを起動する構成にしました。

strdim OUTPUTBUF 500

; KILLする対象のプロセスがPSコマンドで表示される名前
strdim CHK_PROCESS_NAMES 3
CHK_PROCESS_NAMES[0] = 'jp.aaa.bbb.jobA'
CHK_PROCESS_NAMES[1] = 'jp.aaa.bbb.jobB'
CHK_PROCESS_NAMES[2] = 'jp.aaa.bbb.jobC'

; プロセスIDをゲットできたかのフラグ
CHK_PROCESS_RES = 0

timeout = 5

for iii 0 2
   
   CHK_PROCESS_NAME = CHK_PROCESS_NAMES[iii]
   ;; プロセスID取得subコール
   call GET_PROCESSID
   if CHK_PROCESS_RES <> 1 then
      messagebox '停止対象のプロセスが起動していません。'#13'状態を確認してくだい(OKで次プロセスの停止に進みます)' CHK_PROCESS_NAME
   else
      strlen CHK_PROCESS_ID
      if result = 0 then
         messagebox '停止対象のプロセスIDが取得出来ませんでした。'#13'状態を確認してくだい(OKで続行します)' CHK_PROCESS_NAME
      else
         ;; KILL前のチェック処理(省略・・)

         ;; プロセスKILL
         CMD = 'kill -9 '
         strconcat CMD CHK_PROCESS_ID
         sendln CMD
         wait '#' '$'

         ;; プロセスID取得subコール(Killしたのに残っていたら警告)
         call GET_PROCESSID
         if CHK_PROCESS_RES <> 2 then
            messagebox 'プロセスが停止できていません。'#13'状態を確認してくだい(OKで続行します)' CHK_PROCESS_NAME
         endif
      endif
   endif
next

;;マクロ終了
end

;;; sub プロセス状態確認 start
:GET_PROCESSID
CHK_PROCESS_RES = 2
OUTPUTCNT = 0
flushrecv

setsync 1
CMD = 'ps -ef | grep '
strconcat CMD CHK_PROCESS_NAME
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
   ;; Javaプロセスか確認(Grepでない)
   CHKSTR = '/bin/java'
   for i 0 OUTPUTCNT - 1
      strscan OUTPUTBUF[i] CHKSTR
      if result > 0 then
         ;; プロセスIDセット
         strcopy OUTPUTBUF[i] 10 8 CHK_PROCESS_ID
         CHK_PROCESS_RES = 1
         break
      endif
   next
endif

return
;;; sub プロセス状態確認 end

問題は発見するまでの方が時間がかかる

最近あまり読まなくなりましたが、以前はよくソフトウェア関連書を読んでいました。
関連書と言っても、テクニカルなものでなく、トム・デマルコとかワインバーグといった、辛口でウイットに富んだエッセイみたいなタイプの方です。
特にワインバーグの「ライト、ついてますか」がすごく好きです。これはソフトウェア関連書に分類されているので、大きい本屋のコンピューター関連書の陳列棚に置いていたりいなかったりですが、中身はまったくコンピューターと関係なく、サブタイトル「問題発見の人間学」の通り、真の問題を発見する事の重要性について面白い寓話で説明してくれている内容です。どっちかというとビジネス書とか自己啓発本の方に置いておけば、既に30年近く前の本とはいえ、結構売れるんじゃないかなと思うんですが、その手の本と比べるとちょっと値段が高いかな。。
仕事の打合せに出ていて、たまに問題が行ったり来たり跳ねたり飛んだりしてるなと感じる時によく思い出す本です。

税務署リトライ

先月提出をしなかった書類を税務署に出してきました。

・青色申告の承認申請書
・給与支払事務所等の開設申請書
・源泉所得税の納期の特例の承認に関する申請書

税務署ではパラッとチェックして一瞬で終わりました。

青色申告については、個人事業者の経験があるので、税制上の優遇があって有利だというのは分かるのですが、それ以外については、なんだかよくわからないので内容を整理してみます。

・給与支払事務所等の開設申請書
文字通り給与を支払うという行為が発生するにあたって、所得税の源泉徴収を行う事務所を開設しますという事で、この事務所宛に源泉所得税の納付用紙が送られてくるとの事。役員給与という形になるので一人法人でも必要になるとの事。
でも、後で気が付いたのですが、1ヶ月前に法人設立届出書を提出してしばらくした後に税務署から「源泉徴収のしかた」という冊子と納付書が届いていました。。税務署フライングしてません?この書類出さなくてもよかったのかな。。?

・源泉所得税の納期の特例の承認に関する申請書
これは本来毎月支払わなくてはならない源泉所得税を半年ごとにしてもらう為との事。
毎月払うなんて経理とか総務の人が居ない限り無理なんで、出さなくてはいけない申請書です。

一人法人として残りの手続きは年金周りと思いますが、もうちょっと情報収集してからにします。

人月の制約

ソフトウェア関係の書籍としてはあまりにも有名な「人月の神話」の冒頭に、プログラムをプログラミング製品(汎用性を持つデザイン、文書化、テスト等)にすると元の最低3倍のコストがかかり、プログラミングシステム(コンポーネントとしての使用に耐えうるようにするという事か)となるには、こちらも3倍かかるので、これら双方を兼ね備えたプログラミングシステム製品は結果9倍以上になるという記述があります。
で、現在ここでいうプログラミングシステム製品って何があるんだろうと考えますが、OSやJavaのようなプラットフォームしか思い当りませんね。
筆者のような日本にいる「システムエンジニア」と言われる人々は何を作っているのかと考えますと、企業内で使用されるシステムとかWEBサービスとかの構築がメインなんじゃないでしょうか?これらは、上記でいうところのプログラミング製品というものにも当てはまらないように思われます。文書化されてはいても、殆どの場合それを使う為では無く、どう作るか、またはどう作ったかという設計書としての文書だし、テストにしても到底あり得ないケースまで考慮してテストやったり、その為の実装したりってあまりやらないでしょ。
という事で、筆者の場合は特に個々の機能について見積もりをしない前提である為、「人月の神話」の世界にあるような大規模なシステム製品を作っている訳ではなく、参考になる所もあるけど、当てはまらない事が多いように感じました。
結局のところ、どこまでやるかがそのコストになるので、どこまで整理されたシステム構成にするか、どこまで完璧にテストするか、どこまで大多数の人が読める文書を作るかといった判断は、全て予定されたスケジュールと用意されたコストによってある程度決まってしまいます。結果的に「人月の神話」では無く、「人月の制約」の中で必要な機能を全てちゃんと用意した上で上記のような要素に対応するしかないのです。でも、それは全然悪い事では無いと思います。特に筆者のような仕事は、製品を作っているのではなく、エンジニアリングサービスを提供しているからです。
他者が書いた1システム全体のソースを読んでいると、そのような状況が何となく想像されますね。メンバー構成とか役割分担の仕方とか、テストで特に不具合が多かった箇所とか、アーキテクト的な全体を構成する立場の人がいたかとか。一番難しいのは全体の構成でしょうね。人によって読みやすいコードって多分違うので、コンセプトを理解した上でないと、良し悪しの判断さえ出来ないでしょう。