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
レスポンスの解釈は、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以外で動かすことなんて考えていません。