ITお絵かき修行

3歩歩いても忘れないために

RedmineのREST APIを使ってチケット登録・参照

RedmineにはRESTのAPIが用意されており、HTTPボディ部にJSON or xml形式のデータを指定してリクエストを送信することで、チケットに対するCRUD操作ができる。
今回はRedmine公式が配布している「Redmine Java API library」*1というライブラリを使ってチケット登録してみた。


【すること】
RedmineREST APIを使ってチケット登録・参照
→登録は1件単位で、参照は1件単位およびプロジェクトに含まれる全てのチケット情報単位で実行できるようにする。

【環境】
●OS
Windows7 64bit

Redmine
- Redmine 2.6.2
- Apache 2.4.12
- MySQL 5.5.42
- Ruby 2.0.0-p594-i386-mingw32
- Rails 4.2.0
- RubyGems 1.8.12
Redmine Cloud Hosting, Redmine Hosting - Installers and VM
※インストールが面倒だったので上記のオールインワンパッケージを入れた。
ソースコード管理は別立てのGitで行うので入れなかった。

Redmine操作用ライブラリ
Redmine Java API library 2.1.0
taskadapter/redmine-java-api · GitHub

Java
jdk1.8.0_25


Redmine Java API libraryのコンパイルに必要なライブラリ】
ソースをGithubから取得するため、依存関係にあるライブラリは別途取得する必要がある。
Gradleのビルドスクリプトが同梱されているので、実行することで実行資産を作成可能(のはず)。
# Gradleの使い方がわからなかったので、スクリプトを読んでググって人力で集めたorz

1.HttpClient
httpclient-4.4.jar
httpcore-4.4.jar

2.CommonsCodec
commons-codec-1.10.jar

3.fest ※テスティングフレームワーク
fest-assert-1.4.jar

4.SLF4J
slf4j-api-1.6.6.jar
slf4j-log4j12-1.6.6.jar

5.Log4J
log4j-1.2.17.jar

6.org-json-java ※jsonパーサ
org.json-20120521.jar


【事前準備】
1.Redmine側で、管理 → 設定 → 認証 の順で設定ページを表示した後、
[REST による Web サービスを有効にする] にチェックを入れる必要がある。
2.自分のユーザのアクセスキー(APIキー)を確認する。
⇒ 個人設定 ページの右側(デフォルトテーマの場合)に表示されている。


【処理】
1.TicketClient クラス
⇒チケット発行&取得する。
⇒チケット発行においては「題名」が重複する場合はチケットを発行しない仕様としている。発行が目的なので・・・

package sample.redmine;

import java.util.List;
import java.util.Optional;

import com.taskadapter.redmineapi.IssueManager;
import com.taskadapter.redmineapi.RedmineException;
import com.taskadapter.redmineapi.RedmineManager;
import com.taskadapter.redmineapi.RedmineManagerFactory;
import com.taskadapter.redmineapi.bean.Issue;
import com.taskadapter.redmineapi.bean.IssueFactory;
import com.taskadapter.redmineapi.bean.Project;
import com.taskadapter.redmineapi.bean.ProjectFactory;
import com.taskadapter.redmineapi.bean.Tracker;
import com.taskadapter.redmineapi.bean.TrackerFactory;
import com.taskadapter.redmineapi.bean.User;

public class TicketClient {

	private static final String BUG = "バグ";
	private static final String FUNCTION = "機能";
	private static final String SUPPORT = "サポート";

	private RedmineManager redmineMgr = null;
	private IssueManager issueMgr = null;
	private List<User> userList = null;

	private boolean flg = false;

	public void setup(String url, String apiAccessKey) throws Throwable {
		System.out.println("【接続先】" + url);
		redmineMgr = RedmineManagerFactory.createWithApiKey(url, apiAccessKey);
		issueMgr = redmineMgr.getIssueManager();
		userList = redmineMgr.getUserManager().getUsers();
		flg = true;
	}

	private void flgChk() {
		if (!flg) {
			System.err.println("TicketClient#setup(String, String)を先に実行してください。");
			System.exit(1);
		}
	}

	public void putIssue(String projectKey, String trackerName, String subject,
			String description) throws RedmineException {
		flgChk();
		System.out.println("■■■チケットを1件登録します。■■■");
		// チケット名重複チェック
		if(chkSubject(projectKey, subject)){
			System.out.println("題名が重複する場合、チケットは発行しません。題名 :" + subject);
		}else {
			
			// Issue(1チケット)生成
			Issue issue = IssueFactory.create(null);
			// プロジェクト
			setProjectInfo(issue, projectKey);
			
			// 題名
			issue.setSubject(subject);
			// 説明
			issue.setDescription(description);
			// トラッカー
			setTrackerInfo(issue, trackerName);

			// 1チケット登録
			Issue newIssue = issueMgr.createIssue(issue);
			issueMgr.update(newIssue);
			System.out.println("チケットを登録しました。題名 :" + subject);
		}

	}

	private void setProjectInfo(Issue issue, String projectKey) throws RedmineException {
		int intProjectKey = redmineMgr.getProjectManager().getProjectByKey(projectKey).getId();
		Project project = ProjectFactory.create(intProjectKey);
		issue.setProject(project);		
	}

	private void setTrackerInfo(Issue issue, String trackerName) {
		int trackerId = getTrackerId(trackerName);
		Tracker tracker = TrackerFactory.create(trackerId, null); // 第二引数はなくてもよい
		issue.setTracker(tracker);		
	}

	private boolean chkSubject(String projectKey, String subject) throws RedmineException {
		
		List<Issue> issues = issueMgr.getIssues(projectKey, null); // 第二引数はなくてもよい?
		boolean contains = issues.stream().anyMatch(a -> {
			if(subject.equals(a.getSubject())){
				return true;
			}
			return false;
		});
		return contains;
	}

	private int getTrackerId(String trackerName) {
		switch (trackerName) {
		case BUG:
			return 1;
		case FUNCTION:
			return 2;
		case SUPPORT:
			return 3;
		default:
			return 1;
		}
	}

	public List<Issue> getAllIssues(String projectKey) throws RedmineException {
		flgChk();

		System.out.println("■■■チケットを全件取得します。■■■");
		List<Issue> issues = issueMgr.getIssues(projectKey, null); // 第二引数はなくてもよい?
		
		System.out.println("チケットの件数 : " + issues.size());
		issues.stream().forEach(is -> {
			System.out.println("【題名】" + is.getSubject());
		});
		return issues;
	}
	
	public Issue getOneIssue(String projectKey, String subject) throws RedmineException {
		flgChk();
		System.out.println("■■■チケットを1件取得します。■■■");
		List<Issue> issues = issueMgr.getIssues(projectKey, null); // 第二引数はなくてもよい?

		// チケットの探索(無かったらnull)
		Optional<Issue> is = issues.stream().filter(i ->(i.getSubject().equals(subject))).findAny();
		if(is.isPresent()){
			System.out.println("【題名】" + is.get().getSubject());
			return is.get();		
		}
		System.out.println("チケットは取得できませんでした。" );
		return null;	 
	}
}


2.TicketClientTest クラス
⇒TicketClientクラスの各操作メソッドを実行する。

package sample.redmine;

import java.util.List;

import com.taskadapter.redmineapi.RedmineException;
import com.taskadapter.redmineapi.bean.Issue;

public class TicketClientTest {

	/**
	 * mainメソッド
	 * 
	 * @param args
	 */
	public static void main(String[] args) {

		TicketClient client = new TicketClient();
		try {
			client.setup("(RedmineのトップページURL)", "(アクセスキー)");

			// 全件取得
			List<Issue> allIssues = client.getAllIssues("(プロジェクトの識別名)");
			// 1件登録
			client.putIssue("(プロジェクトの識別名)", "バグ", "題名題名題名題名題名題名", "説明説明説明説明説明説明説明説明説明説明");
			// 1件取得
			Issue issue = client.getOneIssue("(プロジェクトの識別名)", "題名題名題名題名題名題名");

		} catch (RedmineException e) {
			e.printStackTrace();
		} catch (Throwable th) {
			th.printStackTrace();
		}
	}
}


【実行結果】
●実行前
f:id:hhhhhskw:20150411180844p:plain
※実行前に「前もってきっておいたチケット」というチケットを作成済。

●実行後
f:id:hhhhhskw:20150411180851p:plain


【実行ログ ※標準出力】

【接続先】(RedmineのトップページURL)
■■■チケットを全件取得します。■■■
チケットの件数 : 1
【題名】前もってきっておいたチケット
■■■チケットを1件登録します。■■■
チケットを登録しました。題名 :題名題名題名題名題名題名
■■■チケットを1件取得します。■■■
【題名】題名題名題名題名題名題名


【感想】
Redmine Java API libraryはJSON or XMLデータの取り回しやHttp通信時の例外処理を隠蔽し、1チケット分の情報を取り回すBeanと操作メソッドを用意しているため、かゆいところにだいたい手が届くライブラリだった。
⇒カスタムフィールドの操作も簡単だった。履歴情報が操作できるかは要検証。
 ※JSON or XMLデータをいじって実装していたときは、名前空間やタグに関するドキュメントが少なくよく迷ったので、かなり楽になった。
・チケットを削除する場合はIssueManager#deleteIssueを実行する。
・今回作ったクラスでは、操作側にRedmine Java API libraryのクラスが見えてるので、見えないようにする必要がある。


【参考文献】
Rest api - Redmine
Rest api with java - Redmine
いぬこいのこや Redmineのチケット登録をRedmine REST APIを使ってやってみる
RedmineのREST APIを使ってみる | 世界はどこまでもシンプルである
Redmine(オールインワンパッケージ)のWindowsへの導入メモ - Qiita


Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築

Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築

*1:Apache License 2.0らしい。