redmine java api でチケットを取得してみる

 前回の続きで、今度はチケットを取得してみます。単純にチケットIDを指定して一つのチケットを取得するのでは無く、条件を指定し一括してチケットを取得して何かをするような要件への対応です。

 下記サンプルでは、クエリーを使用せずに、
ステータスID=2 かつ トラッカーID=10または11 かつ 題名に「テスト」を含む
という条件に一致するチケットを全て取得する内容です。
100件ずつチケットを取得して、100件以上のチケットがあればページ番号を変えて全チケットを取得します。

String apikey = "APIキー値";
String uri = "redmineサイトURI";

String TICKET_LIMIT = "100";

Map<String,String> params = new HashMap<String,String>();

// リミット
params.put("limit",TICKET_LIMIT);

params.put("status_id","2");

params.put("tracker_id","10|11");
//題名に「テスト」を含む
params.put("subject", "~テスト");

RedmineManager mgr = RedmineManagerFactory.createWithApiKey(uri, apikey);

boolean iscontinue = true;
int page = 1;
while(iscontinue){
	
	List<Issue> issues = mgr.getIssueManager().getIssues(params);
	
	for(Issue issue : issues){
		System.out.println(issue.getSubject());
	}
	if(issues.size() < Integer.valueOf(TICKET_LIMIT)){
		iscontinue = false;
	}
	else{
		page++;
		params.put("page",String.valueOf(page));
	}
}

redmine java api でチケットを登録してみる

 redmineへのチケット登録を自動化したいという話があり、内容的には常時トリガーを拾って登録という感じだったので、javaの常駐プロセスでトリガーを拾う事を前提として、こちらを使用して実装してみました。
https://github.com/taskadapter/redmine-java-api
なお、登録対象のredmineは2.5系でしたが、それほどバージョンを意識しなくても大丈夫そうです。

事前準備として、対象のredmineへapi登録用のユーザーを用意し、登録対象のプロジェクトへの権限を付与、個人設定画面からapiキーを取得します。

その他、プロジェクトidやトラッカーid等のredmine内部で持っているidの値はブラウザからapiで下記のようにidの値を確認してセットとなります。ただ、redmine java apiには、様々なマネージャーがあるので、id指定でなくても都度問い合わせしてidを取得できそうです。

ソース的には下記のようなラッパークラスを作って、登録や更新を呼び出し側で簡略化出来るようにしてます。

package jp.eosoro.redmine;

import java.util.ArrayList;
import java.util.List;

import com.taskadapter.redmineapi.Include;
import com.taskadapter.redmineapi.RedmineException;
import com.taskadapter.redmineapi.RedmineManager;
import com.taskadapter.redmineapi.RedmineManagerFactory;
import com.taskadapter.redmineapi.bean.CustomField;
import com.taskadapter.redmineapi.bean.Issue;
import com.taskadapter.redmineapi.bean.ProjectFactory;
import com.taskadapter.redmineapi.bean.TrackerFactory;
import com.taskadapter.redmineapi.bean.User;
import com.taskadapter.redmineapi.bean.UserFactory;

/**
 * Redmine登録クラス
 * */
public class RedmineRegist {

	RedmineManager mgr;
	
	private Issue issue;
	private List<CustomField> customeField;
	
	/**
	 * コンストラクタ
	 * @param String RedmineサイトURI
	 * @param String APIキー
	 */
	public RedmineRegist(String uri, String apiAccessKey) {
		mgr = RedmineManagerFactory.createWithApiKey(uri, apiAccessKey);

		issue = new Issue();
		customeField = new ArrayList<CustomField>();		
	}
	/**
	 * チケット登録
	 * @param subject タイトル
	 * @param description 詳細
	 * @param assigned_toId 担当者を指定する時はユーザーid 指定しない場合は0
	 * @param assigned_toGroupId グループを指定するときはグループid 指定しない場合は0
	 * @param parentid 親チケットを指定する時はチケットid 指定しない場合は0
	 * @param projectId
	 * @param trackerId 
	 * @param priorityId
	 * @param statusId
	 * @return int 登録チケットID 登録失敗時はゼロ
	 * */ 
	public int regist(String subject, String description, int assigned_toId, int assigned_toGroupId, int parentid, int projectId, int trackerId, int priorityId, int statusId){
		
		getIssue().setSubject(subject);
		
		getIssue().setDescription(description);

		//プロジェクト指定
		getIssue().setProject(ProjectFactory.create(projectId));

		//トラッカー指定
		getIssue().setTracker(TrackerFactory.create(trackerId));
		
		//ステータス/優先度指定
		getIssue().setPriorityId(priorityId);
		getIssue().setStatusId(statusId);
		
		// カスタムフィールド設定
		getIssue().addCustomFields(getCustomeField());
		
		try {
			//担当者指定
			if(assigned_toId > 0){
				getIssue().setAssignee(getMgr().getUserManager().getUserById(assigned_toId));
				
			}
			//グループ
			else if(assigned_toGroupId > 0){
				User u = UserFactory.create(assigned_toGroupId);
				getIssue().setAssignee(u);
			}
			//親チケット
			if(parentid > 0){
				getIssue().setParentId(parentid);
			}
			return getMgr().getIssueManager().createIssue(getIssue()).getId();

		} catch (RedmineException e) {
			return 0;
		}
	}
	/**
	 * 関連するチケットをセットする
	 * @param int チケットID
	 * @throws RedmineException 
	 * */
	public void setRelation(int id, int relateid) throws RedmineException{
		getMgr().getIssueManager().createRelation(id, relateid, "relates");
		
	}
	/**
	 * ジャーナル追加
	 * @param id チケットID
	 * @param Notes コメント
	 * @throws RedmineException 
	 * */
	public int addJournal(int id, String Notes) throws RedmineException{
		Issue issue =  getMgr().getIssueManager().getIssueById(id, Include.journals);
		
		issue.setNotes(Notes);

		getMgr().getIssueManager().update(issue);

		return issue.getId();
	}

	public RedmineManager getMgr() {
		return mgr;
	}
	private Issue getIssue() {
		return issue;
	}
	public List<CustomField> getCustomeField() {
		return customeField;
	}
	public void setCustomeField(List<CustomField> customeField) {
		this.customeField = customeField;
	}
}

下記の呼び出し側では、親チケットを作成してジャーナルを追加、子チケットを2つ作成し関連付けてます。

       RedmineRegist  mine = new RedmineRegist (url, apikey);
       
       int parentId = mine.regist("親チケット", "親チケット本文", 0, 0, 0, 4, 4, 2, 1);
       
       mine.addJournal(parentId, "ジャーナル");

       int childId = mine.regist("子チケット", "子チケット本文",  0, 0, parentId, 4, 4, 2, 1);
       
       int relationId = mine.regist("関連チケット", "関連チケット本文",  0, 0, parentId, 4, 4, 2, 1);
       
       
       mine.setRelation(childId, relationId);

いろいろやってみましたが、redmineに対して殆どの事は出来そうです。

JAX-RSでWeb画面にドラッグアンドドロップされたファイルを読み込む

テキストファイルをサーバへアップロードしたいという要件があり、Web画面からファイルをドラッグアンドドロップできるようにして、それをJAX-RSで処理する事にしてみました。

下記の参考にさせて頂き、それらを組合わせただけと言えばだけですが。。

Jersey(JAX-RS)でファイルアップロード
HTML5 の File API でドラッグ&ドロップする

まず、JAX-RSのルートパスを指定します

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/api")
public class RestApplication extends Application {
//何も書く事はありません・・・
}

次にファイルを処理するJAX-RS部分を作ります

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.stream.Stream;

import javax.enterprise.context.RequestScoped;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataParam;

@RequestScoped
@Path("/upload")
public class FileUploader {
	
	@POST
	@Consumes(MediaType.MULTIPART_FORM_DATA)
	public Response post(@FormDataParam("file") InputStream fileStream,
					@FormDataParam("file") FormDataContentDisposition fileDisposition) {

		int statusCode = 200;
		String out = "";

		//テキストファイルの読み込み
		try (BufferedReader br = new BufferedReader(new InputStreamReader(fileStream))) {
			try(Stream<String> lines = br.lines()){
				//1行毎の処理は省略
			}
        } catch (IOException e) {
			statusCode = 400;
			out = e.getMessage();
		}
		return Response.status(statusCode).type("text/html;charset=Shift-JIS").
			entity(out).
			build();
	}
}

これでファイルアップローダーのJAX-RSパスは、{コンテンツルート}/api/uploadになりました。

続いてJavaScriptの部分(殆ど上記参考から持ってきただけです。。)

$(function() {
   var droppable = $("#droppable");

    // イベントをキャンセルするハンドラです.
    var cancelEvent = function(event) {
        event.preventDefault();
        event.stopPropagation();
        return false;
    }

    // dragenter, dragover イベントのデフォルト処理をキャンセルします.
    droppable.bind("dragenter", cancelEvent);
    droppable.bind("dragover", cancelEvent);

    // ドロップ時のイベントハンドラを設定します.
    var handleDroppedFile = function(event) {

	    var dropfile = event.originalEvent.dataTransfer.files[0];
	    
	    var formData = new FormData();
	    formData.append( 'file', dropfile );
	      
	    var hostUrl= 'api/upload';
	    $.ajax({
	       url: hostUrl,
	       method: 'post',
	       dataType: 'json',
	       data: formData,
	       processData: false,
	       contentType: false,
	       timeout:100000
	    }).done(function(data) {
        	alert( '正常に終了しました');
	    }).fail(function( jqXHR, textStatus, errorThrown ) {
	         alert( 'エラーが発生しました\n'+ jqXHR.responseText);
	    });
	
	    // デフォルトの処理をキャンセルします.
	    cancelEvent(event);
	    return false;
    }
    // ドロップ時のイベントハンドラを設定します.
    droppable.bind("drop", handleDroppedFile);
});

最後にHTML部分のドラッグアンドドロップ部分です。
ここにドロップされたファイルがJAX-RSの処理箇所のInputStream に繋がります。

<div class="droppable" id="droppable" style="border:gray solid 1em; width:100px; padding:2em;">アップロードするファイルはココにドロップしてください。</div>      

これでとりあえずはJAX-RSでファイル処理が出来ることが確認出来ました。

最後にPOM。

<dependency>
    <groupId>javax.ws.rs</groupId>
    <artifactId>javax.ws.rs-api</artifactId>
    <version>2.0.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.sun.jersey.contribs/jersey-multipart -->
<dependency>
    <groupId>com.sun.jersey.contribs</groupId>
    <artifactId>jersey-multipart</artifactId>
    <version>1.19.3</version>
</dependency>

POIでエクセルファイルを読むにはStreamingReaderが必須っPOI

エクセルファイル内のシートをDBに取り込む要件があり、DBサーバであるlinux上で取り込む処理をする必要が出てきました。
とりあえずCSVにでも変換してしまえば後はどうにでもなるので、Apache POIを使って汎用で使えそうな簡易CSV変換ツールを作ってみました。簡単にテストした後、いざ対象となるエクセルファイルで実行してみたところいつまでたっても終わらないし、メモリを数G食うという異常な状態に。
 テストした時のエクセルは数千程度で、いざ実行したエクセルは数万規模の違いがあり、大量データが含まれるエクセルシートの場合にPOIでは問題があるみたいです。で、こりゃ何とかしないとと調べていくと、それを解消してくれる素晴らしいライブラリを発見。
https://github.com/monitorjbl/excel-streaming-reader

上記サイトのREADMEに従い、下記のようにWorkbookFactory.createの箇所を置き換えるのみでした。(inputはエクセルファイルのInputStream)

        Workbook wb;
		try {
//POI標準ではダメ			wb = WorkbookFactory.create(input);
			wb = StreamingReader.builder()
			        .rowCacheSize(1000)    
			        .bufferSize(4096)     
			        .open(input);
・・・以下略

変えた後は何事もなかったかのようにちゃんと終わりました。
それにしてもPOIは非推奨メソッドをいつ置き換えてくれるのだろう?

追伸
sheet.getNumMergedRegions() 等、サポートしていないメソッドが多数あるようです。
結合セルなどを意識しないで、単純にエクセルファイルを行毎に順次読み取るような処理には適してます。

カテゴリー: Java

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自体は分までが対応範囲なので秒については直入力という整理です。

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

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

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

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>