谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

TaskCoachとRedmineの連携

TaskCoachのデータ(XML)を、Redmineの時間記録に投入するツールを作成中。
http://svn.sourceforge.jp/view/taskmine/trunk/taskmine/?root=b-light


と言っても、TaskCoachのXMLをJAXBで読み込んでEntityにして、
Apache HttpClientで一気に投入しているだけです。


とりあえずメイン部分のソースだけ解説。
同じHttpClientインスタンスを使い続ければ、追加実装なくCookieを継続させられるので
同一インスタンスで、ログイン → 経過時間の記録を行っています。

public class RedmineAccessor {
	/** RedmineのURL */
	private String url;

	/** Redmineのユーザ名 */
	private String username;

	/** Redmineのパスワード */
	private String password;

	/** HTTPクライアント */
	private DefaultHttpClient client = new DefaultHttpClient();

	/**
	 * コンストラクタ。プロパティファイルを読み込みます。
	 * @throws IOException プロパティファイルの読み込みに失敗した場合
	 */
	public RedmineAccessor() throws IOException {
		// プロパティファイルを読んでインスタンス変数に代入します。
		// 大した処理じゃないので省略。
	}

	/**
	 * 経過時間を記録します。
	 * @param timeEntryList 経過時間のリスト
	 * @throws IOException 通信に失敗した場合
	 */
	public synchronized void trackTime(List<TimeEntry> timeEntryList)
			throws IOException {
		boolean loginSuccess = login();
		if (loginSuccess == false) {
			System.out.println("ログインに失敗しました。");
			return;
		}

		for (TimeEntry timeEntry : timeEntryList) {
			trackSingleTime(timeEntry);
		}
	}

	/**
	 * Redmineへのログインを行います。
	 * @return ログインに成功した場合はtrue、失敗した場合はfalseを返します。
	 * @throws IOException 通信に失敗した場合
	 */
	private boolean login() throws IOException {
		HttpPost post = new HttpPost(this.url + "/login");

		List<NameValuePair> nvps = new ArrayList<NameValuePair>();
		nvps.add(new BasicNameValuePair("username", this.username));
		nvps.add(new BasicNameValuePair("password", this.password));
		post.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));

		Boolean result = client.execute(post, new LoginResponseHandler());
		return result.booleanValue();
	}

	/**
	 * 経過時間を記録します。
	 * @param timeEntry 経過時間
	 * @throws IOException 通信に失敗した場合
	 */
	private void trackSingleTime(TimeEntry timeEntry) throws IOException {
		HttpGet get = new HttpGet(this.url + "/timelog/edit?issue_id="
				+ timeEntry.getIssueId().toString());
		String path = client.execute(get, new TimelogFormResponseHandler());

		HttpPost post = new HttpPost(this.url + path);

		List<NameValuePair> nvps = new ArrayList<NameValuePair>(5);
		nvps.add(new BasicNameValuePair("time_entry[issue_id]", timeEntry
				.getIssueId().toString()));
		nvps.add(new BasicNameValuePair("time_entry[spent_on]", timeEntry
				.getSpentOn()));
		nvps.add(new BasicNameValuePair("time_entry[hours]", timeEntry
				.getHours()));
		nvps.add(new BasicNameValuePair("time_entry[comments]", timeEntry
				.getComments()));
		nvps.add(new BasicNameValuePair("time_entry[activity_id]", timeEntry
				.getActivityId().toString()));
		post.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));

		HttpResponse response = client.execute(post);
		response.getEntity().consumeContent();
	}
}

この中で使ってるNameValuePairやBasicNameValuePairは、
妙にオブジェクト指向ぶってて、あんまり好きなAPIじゃないんですけどね。
Mapとか、Mapで良いじゃないかと思うんですが。


レスポンスの解釈は、ResponceHandlerで処理しています。
普通にResponse#getEntityしてから処理を書いても構わないんですが、
Handlerを書いた方が、責務が明確化して、オブジェクト指向的にイケてる感じになります。

public class LoginResponseHandler implements ResponseHandler<Boolean> {
	/** ログイン成功のメッセージ */
	private static final String LOGIN_SUCCESS = "ログイン中:";

	/** ログイン失敗のメッセージ */
	private static final String LOGIN_FAILURE = "ユーザ名もしくはパスワードが無効";

	/**
	 * {@inheritDoc}<br>
	 * ログインに成功した場合はTRUE、失敗した場合はFALSEを返します。
	 */
	@Override
	public Boolean handleResponse(HttpResponse response) throws IOException {
		HttpEntity entity = response.getEntity();
		if (entity == null) {
			return Boolean.FALSE;
		}

		BufferedReader reader = null;
		InputStream in = entity.getContent();
		try {
			reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));

			String line = null;
			while ((line = reader.readLine()) != null) {
				if (line.contains(LOGIN_SUCCESS)) {
					return Boolean.TRUE;
				} else if (line.contains(LOGIN_FAILURE)) {
					return Boolean.FALSE;
				}
			}
		} finally {
			if (reader != null) {
				reader.close();
			} else {
				in.close();
			}
		}

		return Boolean.FALSE;
	}
}

まぁ見ての通り、ゴリゴリのソースです。
日本語版のRedmine以外で動かすことなんて考えていません。