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で扱うようです。
バインド変数の仕様違いにはちょっとハマりました。。

以外と知られていないが便利なHTA

 Windows用で画面一つだけとかの簡単なツールを作る場合、HTA(HTML Applications)をよく使います。かなり昔からありますが、かなりマイナーな存在のようです。筆者もその存在に気が付いたのは実は3年くらい前です。。小さい規模でしか使う事はまずないので、HTAを使うような開発を外部へ発注するような事がなかったからでしょうか?
 基本的にIE上で動作しているので、MicrosoftがIEを辞めたら終わりかも知れませんが、テキストエディタのみで作れるし、VBScriptとJavaScriptの組み合わせで大抵の事は実現できます。特に助かるのが、企業内で使う端末って勝手にソフトをインストール出来なかったり、会社によっていろんな制約があったりしますが、HTAなら端末環境上の影響がなく、Windows端末でさえあればOKという点です。少数で使うちょっとした画面を作るという前提では結構便利だと思います。

Coherenceと1年付き合ってみた

 OracleのインメモリーグリッドCoherenceを使った開発に携わり1年立ちました。途中の数ヶ月は別の事もやったりで比較的まったりやっていましたが、この辺でちょっと整理。別に筆者が選定した訳ではありませんが、今携わっている仕事でCoherenceを使う事になったのは、Webサービスでミッションクリティカルかつ、応答レスポンスの最小化という要件を満たす為でした。構成はこんな感じです。

構成

 上記のWebLogicにデプロイされているフロントアプリケーションと、別途バックにJavaVMで動作させているプロセスの両方にCoherenceのキャッシュデータが配置されます。フロントは静的かつアクセス頻度が高いマスタ系データをレプリケーションキャッシュとして配置、バック側は主にトランザクション系データを分散キャッシュとして配置することで、分散かつ他のプロセスにバックアップを取ってくれます。コンフィグ次第で他サーバとかにバックアップを取るようにも出来るようです。またバックキャッシュではキャッシュストアといって、キャッシュに無ければDBに取りに行ったり、キャッシュに入ったものをDBへ入れたりする機能が動作します。
 Coherenceに関してコードを書くところは、キャッシュに格納するデータとなるエンティティモデル、上記のキャッシュストア、その他必要に応じてイベント処理やビジネスロジック側の為にDAO的なものを用意するくらいです。

 まず、エンティティモデルとしてPortableObjectを実装します。こんな感じです。

package jp.co.esoro.cache.EDM;

import java.io.IOException;

import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
import com.tangosol.io.pof.PortableObject;

/** 取引履歴 */
public class TrnRequest implements PortableObject{

	/** 取引年月日 */
	private String trnDate;
	/** 所属先ID */
	private String companyID;
	/** 取引番号 */
	private String trnID;
	/** 取消フラグ */
	private String canselFlg;
	/** ユーザーID */
	private String userID;
	/** 金額 */
	private long amount;
	/** 取引区分 */
	private String requestType;
	
	public String getTrnDate() {
		return trnDate;
	}
	public void setTrnDate(String trnDate) {
		this.trnDate = trnDate;
	}
	public String getCompanyID() {
		return companyID;
	}
	public void setCompanyID(String companyID) {
		this.companyID = companyID;
	}
	public String getTrnID() {
		return trnID;
	}
	public void setTrnID(String trnID) {
		this.trnID = trnID;
	}
	public String getCanselFlg() {
		return canselFlg;
	}
	public void setCanselFlg(String canselFlg) {
		this.canselFlg = canselFlg;
	}
	public String getUserID() {
		return userID;
	}
	public void setUserID(String userID) {
		this.userID = userID;
	}
	public long getAmount() {
		return amount;
	}
	public void setAmount(long amount) {
		this.amount = amount;
	}
	public String getRequestType() {
		return requestType;
	}
	public void setRequestType(String requestType) {
		this.requestType = requestType;
	}
	public String getId() {
		return trnDate + companyID + trnID;
	}

	@Override
	public void readExternal(PofReader arg0) throws IOException {
		setTrnDate(arg0.readString(1));
		setCompanyID(arg0.readString(2));
		setTrnID(arg0.readString(3));
		setCanselFlg(arg0.readString(4));
		setUserID(arg0.readString(5));
		setAmount(arg0.readLong(6));
		setRequestType(arg0.readString(7));
	}
	@Override
	public void writeExternal(PofWriter arg0) throws IOException {
		arg0.writeString(0, getId());
		arg0.writeString(1, getTrnDate());
		arg0.writeString(2, getCompanyID());
		arg0.writeString(3, getTrnID());
		arg0.writeString(4, getCanselFlg());
		arg0.writeString(5, getUserID());
		arg0.writeLong(6, getAmount());
		arg0.writeString(7, getRequestType());
	}
}

 上記の例ではキー項目が取引年月日と所属先IDと取引番号の3つですが、KeyValueなので1項目のキーとしてgetIdというメソッドを入れてます。
 
 次にバックキャッシュ側で動作するCacheStoreを実装します。こんな感じ。

package jp.co.esoro.cache.Cachestore;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;

import jp.co.esoro.cache.EDM.TrnRequest;

import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceFactory;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.tangosol.net.cache.CacheStore;

/**
 * 履歴テーブルキャッシュストア
 * */
public class ReqCacheStore implements CacheStore {
	protected static Logger logger;
	private Connection con = null;
	PreparedStatement storePs = null;
	PoolDataSource pds;
	
	private static String sql_TrnRequest = "MERGE INTO TrnRequest H "
			+ "USING (SELECT ? trnDate, ? companyID, ? trnID FROM DUAL) U "
			+ "ON (H.trnDate = U.trnDate "
			+ "AND H.companyID = U.companyID "
			+ "AND H.trnID = U.trnID) "
			+ "WHEN MATCHED THEN "
			+ "UPDATE SET canselFlg=? "
			+ "WHEN NOT MATCHED THEN "
			+ "INSERT (trnDate,companyID,trnID,canselFlg,userID,amount,requestType) "
			+ " VALUES (?,?,?,?,?,?,?)";
	
	public ReqCacheStore(String cacheName) {
		super();
		logger = LogManager.getLogger();
		try {
			pds = PoolDataSourceFactory.getPoolDataSource();
			pds.setConnectionPoolName(cacheName);
			pds.setConnectionFactoryClassName(   
			        "oracle.jdbc.pool.OracleDataSource");
			pds.setValidateConnectionOnBorrow(true);

			pds.setURL("****************");
			pds.setUser("user");
			pds.setPassword("password");
			pds.setInitialPoolSize(1);
			pds.setMinPoolSize(1);
			pds.setMaxPoolSize(20);
			
			con = pds.getConnection();
			storePs = con.prepareStatement(sql_TrnRequest);
		} catch (SQLException e) {
			logger.error(e.getMessage());
		}
	}

	@Override
	public Object load(Object arg0) {
		//get時キャッシュに無い場合DB等から読込みが必要な場合記述
		return null;
	}

	@SuppressWarnings("rawtypes")
	@Override
	public Map loadAll(Collection arg0) {
		//get時キャッシュに無い場合DB等から読込みが必要な場合記述
		return null;
	}
	
	@Override
	public void erase(Object arg0) {
		//削除が必要な場合記述
	}
	
	@SuppressWarnings("rawtypes")
	@Override
	public void eraseAll(Collection arg0) {
		//削除が必要な場合記述
	}
	
	/**
	 * DB書込み
	 * */
	@Override
	public void store(Object arg0, Object arg1) {
		try {
			TrnRequest trn = (TrnRequest)arg1;
			
			storePs.setString(1, trn.getTrnDate());
			storePs.setString(2, trn.getCompanyID());
			storePs.setString(3, trn.getTrnID());
			storePs.setString(4, trn.getCanselFlg());
			storePs.setString(5, trn.getTrnDate());
			storePs.setString(6, trn.getCompanyID());
			storePs.setString(7, trn.getTrnID());
			storePs.setString(8, trn.getCanselFlg());
			storePs.setString(8, trn.getUserID());
			storePs.setLong(10, trn.getAmount());
			storePs.setString(11, trn.getRequestType());
			
			storePs.executeUpdate();
			
		} catch (SQLException e) {
			logger.error(e.getMessage());
		}
	}

	@SuppressWarnings("rawtypes")
	@Override
	public void storeAll(Map arg0) {		
		for (Object entry :arg0.entrySet()){
			store( (Object)((Entry<?, ?>) entry).getKey(),(Object)((Entry<?, ?>) entry).getValue());
		}
	}
}

上記はDB書き込みのみの実装例です。

 この先はコンフィグを3つ書きます。凝った事をしなければフロント用もバック用も同じものでOKです。これらはJVM起動時オプションで指定します。クラスパス内ならファイル名だけ、外に置いてもフルパスで指定すればOKです。

 まず、キャッシュのクラスタ設定ですが、基本的にクラスタ名だけ書いておけば後は勝手に各プロセスが連携してくれます。
 起動オプションは、-Dtangosol.coherence.override=tangosol-coherence-override.xml

<?xml version="1.0" encoding="UTF-8"?>
<coherence xmlns="http://xmlns.oracle.com/coherence/coherence-operational-config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-operational-config http://xmlns.oracle.com/coherence/coherence-operational-config/1.2/coherence-operational-config.xsd">
    <!--coherence-version:12.1.3-->
    <cluster-config>
        <member-identity>
            <cluster-name system-property="tangosol.coherence.cluster">cohe-cluster1</cluster-name>
        </member-identity>
        <multicast-listener>
          <address system-property="tangosol.coherence.clusteraddress">224.0.0.1</address>  
          <port system-property="tangosol.coherence.clusterport">11131</port>
        </multicast-listener> 
    </cluster-config>
</coherence>

 次にPortableObjectとして実装したものをPOFコンフィグに書いておきます。番号は1000以上で適当に並べます。
 起動オプションは、-Dtangosol.pof.config=pof.xml

<?xml version="1.0"?>
<pof-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://xmlns.oracle.com/coherence/coherence-pof-config"
    xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-pof-config http://xmlns.oracle.com/coherence/coherence-pof-config/1.2/coherence-pof-config.xsd">
	<user-type-list>
	<!-- include all "standard" Coherence POF user types -->
	<include>coherence-pof-config.xml</include>
	<user-type>
		<type-id>1001</type-id>
		<class-name>jp.co.esoro.cache.EDM.MstUser</class-name>
	</user-type>
	<user-type>
		<type-id>1002</type-id>
		<class-name>jp.co.esoro.cache.EDM.TrnRequest</class-name>
	</user-type>
	</user-type-list>
</pof-config>

 最後に各キャッシュ構成として、キャッシュのタイプや構成、作成したキャッシュストア等を書きます。
 起動オプションは、-Dtangosol.coherence.cacheconfig=cache-config.xml

<?xml version="1.0"?>
<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config http://xmlns.oracle.com/coherence/coherence-cache-config/1.2/coherence-cache-config.xsd">
  <defaults>
    <serializer>pof</serializer>
    <socket-provider system-property="tangosol.coherence.socketprovider"/>
  </defaults>
  <caching-scheme-mapping>
    <cache-mapping>
      <cache-name>userMst</cache-name>
      <scheme-name>master-scheme</scheme-name>
    </cache-mapping>
    <cache-mapping>
      <cache-name>RequestTrn</cache-name>
      <scheme-name>trn-scheme</scheme-name>
    </cache-mapping>
  </caching-scheme-mapping>
  <caching-schemes>
    <replicated-scheme>
      <scheme-name>master-scheme</scheme-name>
      <service-name>master-service</service-name>
      <backing-map-scheme>
      	<local-scheme></local-scheme>
      </backing-map-scheme>
      <autostart>true</autostart>
    </replicated-scheme>
    
    <distributed-scheme>
      <scheme-name>trn-scheme</scheme-name>
      <service-name>trn-service</service-name>
      <thread-count>3</thread-count>
      <backing-map-scheme>
        <read-write-backing-map-scheme>
          <internal-cache-scheme>
      	    <local-scheme>
      	      <eviction-policy>LRU</eviction-policy>
      	      <high-units>1000000</high-units>
      	    </local-scheme>
          </internal-cache-scheme>
          <cachestore-scheme>
            <class-scheme>
              <class-name>jp.co.esoro.cache.Cachestore.ReqCacheStore</class-name>
                 <init-params>
                <init-param>
                  <param-type>java.lang.String</param-type>
                  <param-value>{cache-name}</param-value>
                </init-param>
              </init-params>
            </class-scheme>
          </cachestore-scheme>
          <write-delay>5s</write-delay>
          <write-requeue-threshold>1</write-requeue-threshold>
        </read-write-backing-map-scheme>
      </backing-map-scheme>
      <autostart>true</autostart>
    </distributed-scheme>
    
    <proxy-scheme>
      <scheme-name>proxy-scheme</scheme-name>
      <service-name>proxy-service</service-name>
      <thread-count>10</thread-count>
      <acceptor-config>
        <tcp-acceptor>
          <local-address>
            <address>192.168.111.113</address>
            <port>9099</port>
          </local-address>
        </tcp-acceptor>
      </acceptor-config>
      <proxy-config>
        <cache-service-proxy>
          <enabled>true</enabled>
        </cache-service-proxy>
        <invocation-service-proxy>
          <enabled>true</enabled>
        </invocation-service-proxy>
      </proxy-config>
      <autostart>true</autostart>
    </proxy-scheme>
  </caching-schemes>
</cache-config>

 上記ですとuserMstはレプリケーションキャッシュ、RequestTrnは分散キャッシュでOutOfMemory対策として1プロセス最大100万件まで、処理負荷分散の為にスレッドを3つ、キャッシュストアは応答レスポンスを意識してキャッシュ書き込み5秒後に非同期で動作するという内容です。最後のProxyはクラスタ構成プロセス外からのアクセス(*Extends)がある場合の受信口を用意している形になってます。なお、複数プロセスを動作させる場合は、PORTを個々に指定します。

カテゴリー: Java

JSF2単純リダイレクト

JSF2でコンテンツルートのページから別ページへリダイレクトさせるのに何がいいかなと調べたところ、meta refreshタグを使って飛ばすのが一番簡単そうでした。具体的にはコンテンツルートにログイン画面を置くことが出来ない場合に、ログイン画面に飛ばすという要件への対応になります。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<meta http-equiv="refresh" content="0; URL=#{request.contextPath}/login/index.xhtml"/>
</h:head>
</html>
カテゴリー: Java

作らないで済むなら作らない方がいい

 社内ネットワーク内で使う研修用の動画サイト作れないかな?と相談されたので、最初は作る前提でいろいろ調べていました。
 Javascript系のvideo.jsやMediaElement.jsとかで単純に動画を表示する所まではやってみましたが、動画のアップロードやメディア管理、認証等まで考えると結構な時間が掛かかりそうだと実感。。
 で、結局の所、WordPressを立ち上げてプラグインを入れる方がよいと伝えました。
 下手に作ると使う側にもこちらにもコストが掛かるし、使う側も何か問題があったら何も出来ずに問い合わせしてくるしかありません。WordPressなら情報量が豊富なので使う側も多少の勉強が必要とはいえググれば大抵の対処が可能だし、WordPressを理解していく過程で、別要件もこれで実現できそうだというのも結構ありそうです。
 作らなくて済むなら作らない方がいいというケースは、オープンソースのライブラリとかを使用するのも同じです。しかし、その見極めが結構難しくて、「定番」みたいなものなら別ですが、情報量が少ないものは使うと決めるまでに調べたり試してみたりの時間が掛かってしまうものです。

データの意味

 先日、セミナーにちょっと行ってきました。
アプリケーションセキュリティに関するセミナーでしたが、印象に残ったのが、扱うデータの意味を理解して対策を立てるのは、どんなセキュリティ対策のツールを使っても扱うのが相当困難だという点でした。
 企業内で扱うデータの意味を知っているのは、基本的に企業内SEもしくはその位置付けにある人達です。何を知っているかと言えば、具体的にはテーブルとカラム、データ項目、インターフェース設計なんだと思います。前職で一時的にホストを担当した時は、VSAMのレコードレイアウト図として紙でしか資料が残っていなかったので、エクセルのテーブル設計書に落として、把握している範囲で項目の備考欄を埋めていきました。
 備考欄に殆ど何も書かれていないテーブル設計書をたまに見かけますが、項目の備考欄こそデータの意味を記載する箇所ではないかと思います。項目名だけでデータの意味を想定するのは間違いが生まれる元ですので、どういう意味の項目であるか、どういう値が入りえるのか、他とどういう関係を持っているか、出来る限り備考欄を埋める事が、データ設計をする人としてのそのデータを扱う他者への優しさではないでしょうかね?

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
で、何作ろうか?

VFS2でのFTP送信を追加

今作っているアプリケーションの要件に他サーバへ日次でFTP送信するというのがあり、シェルでいいかと思っていたのですが、既に別要件でVFS2を使っていたので、ついでにFTP送信もVFS2でやる事にしました。で、前に作ったクラスに下記を追加。

    /**
     * FTPファイルPut
     * @param 送信元DIR
     * @param 送信元ファイル
	 * @param サーバログインユーザー
	 * @param サーバログインパスワード
	 * @param サーバホスト名
	 * @param 送信先DIR
	 * @param 送信先ファイル名
     * @throws IOException 
     * 
     * */
    public void FTPputFile( String localpath, String File, String User, String Password, String HostName, String remotePath, String remoteFile) throws IOException {
    	String connURL = getConnectionURL(User, Password, HostName,"ftp");
		FileSystemOptions opts = new FileSystemOptions();

		SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(opts, "no");
		StandardFileSystemManager fsManager = new StandardFileSystemManager();
		fsManager.init();
    	try{
			FileObject localFileObject = fsManager.resolveFile(localpath + "/" + File,opts);
			
			if(!localFileObject.exists()){
				throw new IOException("File Not Exists");
			}
			FileObject remoteFileObject = fsManager.resolveFile(connURL + remotePath + "/" + remoteFile,opts);
			remoteFileObject.copyFrom(localFileObject, Selectors.SELECT_SELF);
    	}catch(IOException e){
    		throw new IOException(e);
    	}finally{
            fsManager.close();
            fsManager = null;
    	}
    }

	/**
	 * 接続URLを取得する
	 * @param サーバログインユーザー
	 * @param サーバログインパスワード
	 * @param サーバホスト名
	 * @param sftp ftp 等の文字列
	 * @return URL
	 * @throws UnsupportedEncodingException 
	 * */
	public String getConnectionURL(String User, String Password, String HostName, String FileSystem) throws UnsupportedEncodingException {
		return FileSystem + "://" + URLEncoder.encode(User, "UTF-8") + ":" + 
				URLEncoder.encode(Password, "UTF-8") + "@" + 
				URLEncoder.encode(HostName, "UTF-8");
	}
カテゴリー: Java

兵馬俑は事実を可視化したもの

 先日、上野の国立博物館に「始皇帝と大兵馬俑」を見に行きました。混んでる事を見込んで平日水曜日に行きましたが、平日でも結構混んでます。こういうのはじっくり見たいので、混んでると途端に気持ちがダウンしちゃうんですよね。。でも、展示の最後に職業柄考えさせられる一文、「永遠に皇帝であらんが為の事実を可視化する為に兵馬俑を作った」というニュアンスの事が書かれていたのに注視しました。
 システムに携わる人として可視化という言葉には、反応せざるを得ません。システムは画面以外目で見えないものなので、ドキュメントを書く事で可視化しますが、始皇帝は膨大なリソースを費やし、数千、万?のリアルな陶器を作る事で可視化したという事です。
 数年前に携わった、とある企業の基幹システムリプレイス要件定義フェーズで、一番受けが良かったと感じているのが、既存システム構成を可視化した「絵」です。中規模以上の企業ですと、複数のシステムが連携しながらその企業のビジネスの根底を担っていることが多いかと思います。でも、業務ユーザーの方々は自分の社内システムがどう構成されているかをそれほど把握していません。システム部門の方々は、どちらかというとインフラよりの観点が強いです。要件を聞き出す為に今の形が可視化された物が欲しいと思いましたが、既存資料に該当するものが無かったので、下記に気を付けて全体俯瞰図的な絵を書いてみました。
 1.1枚に収める(A4サイズにすべてを収めないとヒアリング時にすぐ見れない)
 2.サーバマシン等のインフラ単位ではなく、ユーザーが日常業務で言っているシステム名を1つの単位とする(利用部門等の境界が解る)
 3.1つのシステムが大きい場合はその中に分離できそうなものをサブシステムとして枠を引く(作業分担すべき境界が解る)
 4.重要データはどこにあるのか解るように入れておく(データ移行元とすべき場所が解る)
 5.システム間、外部、利用者との繋がりを矢印の線でつなぐ(in outが解る)
 6.完成を待たずに常に打ち合わせ時に持っていて見てもらい足りない所や間違いを指摘してもらう
こんな感じでした。
fukan

 その後、空いていた常設展示を見に行き、誰もが知ってる国宝になってる日本の埴輪を見ましたが、明らかにクオリティ的には兵馬俑の方が高いです。しかも、兵馬俑は埴輪に対し700年くらい古いものです。でも、緊張感に満ちた表情が殆どの兵馬俑よりも埴輪は「いい味」出してるなあという感じがします。これってただの自国びいきなんでしょうか?それとも強大な権力者から言われて作ったものよりも、自発的に作られたもの(真意は知りませんが)の方が共感を得やすいという事を表しているのでしょうか?

VFS使用時の注意事項

 前に載せたapache.commons.vfs2を使ったファイルダウンロードをテストすると問題が発生しました。テスト内容は、
1.大きなファイルをダウンロードしてみる
2.複数のブラウザから同時にダウンロードしてみる
ですが、1は問題無いものの、2は後からダウンロードした方がFileObjectからInputStream取得時に失敗しています。今のやり方だとマルチセッションに対応していないようです。
 で、ググってみると下記を発見。
 http://timurlaykov.blogspot.jp/2011_05_01_archive.html

FileSystemManager fsManager = VFS.getManager();
でなく、
StandardFileSystemManager fsManager = new StandardFileSystemManager();
fsManager.init();
に置き換えるというもの

同時アクセスといっても大量に発生する事は今回考えなくてよいので、この内容に変えました。

 しかし、ググってばかりでいいんでしょうか?VFSはオープンソースなんだから、ちゃんとソースを解析して問題を解決したいのですが、やはりプログラミングを10年近くサボっていたからでしょうか、自力だといつ解決できるのか自信が持てません。。

カテゴリー: Java