ごきげんよう nishino@サポートの人 です。
普段はお仕事として弊社サービスに関する問い合わせやトラブルの対応をやっております。
社内外の問い合わせデータを集約したいなーという思惑のもと、今回は弊社で使用しているWebサービスのデータをAPIで取得してみようと思います。
Play!frameworkで各種Webサービスにつながってみる
今回、データの集約をしたいWebサービスは、バックログさんとセールスフォースさんです。ちなみにシャノンさんもAPIでつながることができます。是非つながりたい人は、こちらのSHANON MARKETING PLATFORMをご購入いただくか、右上の「人材募集」バナーをクリック!クリック!してください。
バックログ
http://www.backlog.jp/nulab社が提供する「楽しくチームで仕事ができる」プロジェクト管理のWebサービスです。
シャノンCRM部および技術部(QA)では、主に社内の問い合わせをバックログに登録し、ナレッジベースとして活用しています。
このブログでも何度か紹介されていますね。
開発者にやさしい環境
クラウド時代のプロジェクトマネジメント
セールスフォース
https://www.salesforce.com/セールスフォース・ドットコム社が提供する「CRM (顧客関係管理)」のWebサービスです。
シャノンCRM部では、社外からの問い合わせをSalesforceのケースに登録しています。
Webサービスのプロトコル
Webサービスと連携するためのプロトコルはいくつかあります。
- XML-RPC
- JSON-RPC
- SOAP
- REST(正確にはプロトコルではなくアーキテクチャスタイル)
- OAuth
- OAuth 2.0
今回は、XML-RPC(バックログ)、OAuth 2.0(セールスフォース)によるAPI連携を行います。
XML-RPC
XML-RPCは、RPCプロトコルの一種であり、エンコード(符号化)にXMLを採用し、転送機構に HTTP を採用してます。非常に単純なプロトコルで、少数のデータ型やコマンドだけを定義しているだけです。(Wikipediaからの抜粋です)
Backlogは、XML-RPCでAPI連携できますので、さっそくPlay!でつながってみようと思います。
、、とその前に
取得したデータは、前回の投稿「Play! frameworkでつくるスクリーンショット取得サービス」で紹介したcrudモジュールを使って表示します。知らないよ~って方は前回の投稿も覗いてみてください。
モデルはこんな感じです。
hoge/app/models/Data.java
package models;
import javax.persistence.Entity;
import javax.persistence.Lob;
import play.db.jpa.Model;
import play.data.validation.MaxSize;
@Entity
public class Data extends Model {
public String code;
public String question;
@Lob
@MaxSize(4000)
public String answer;
public Data(String code, String question, String answer) {
this.code = code ;
this.question = question;
this.answer = answer;
}
public String toString() {
return code + ":" + question;
}
}
crudモジュールを使用したコントローラはこんな感じです。
hoge/app/controllers/Datas.java
package controllers;
import play.*;
import play.mvc.*;
public class Datas extends CRUD {}
では、話を戻して
XML-RPCをつかって、バックログにアクセスしてみます。
コントローラにbacklog()メソッドを追加します。
hoge/app/controllers/Application.java
package controllers;
import play.*;
import play.mvc.*;
import jobs.*;
public class Application extends Controller {
public static void index() {
render();
}
public static void backlog() {
new ImportBacklog().now();
redirect("/datas/list");
}
}
backlog()メソッドでは、Play!frameworkのジョブ機能を使って、非同期に実行後、/datas/listにリダイレクトしています。
処理の実装はImportBacklog ジョブに記述します。
hoge/app/jobs/ImportBacklog.java
package jobs;
import java.util.*;
import play.*;
import play.jobs.*;
import play.libs.WS;
import play.libs.WS.*;
import play.libs.XPath;
import play.templates.*;
import org.w3c.dom.*;
import models.*;
public class ImportBacklog extends Job {
public void doJob() {
String authorizeUrl = "https://shanonpj.backlog.jp/XML-RPC";
String clientId = "loginId";
String clientSecret = "password";
WSRequest wsrequest = WS.url(authorizeUrl).authenticate(clientId, clientSecret);
Template template = TemplateLoader.load("conf/backlog.findIssue.xml");
Map<String, Object> templateMap = new HashMap(16);
templateMap.put("projectId", "5816");
templateMap.put("limit", "100");
templateMap.put("offset", "0");
wsrequest.body = template.render(templateMap);
Document document = wsrequest.post().getXml();
Map<String, String> map = new HashMap(16);
for(Node node: XPath.selectNodes("//data/value/struct/member", document)) {
String name = XPath.selectText("name", node);
String value = XPath.selectText("value", node);
if(name.equals("key") || name.equals("summary") || name.equals("description")) {
map.put(name, value);
}
if(map.size() == 3) {
// create Data
Data data = Data.find("byCode", map.get("key")).first();
if(data == null) {
data = new Data(map.get("key"), map.get("summary"), map.get("description"));
data._save();
}
map.clear();
}
}
}
}
バックログAPIは、ログインIDとパスワードで認証します(ベーシック認証)。
今回は課題データをごっそり持ってきたかったので、backlog.findIssueメソッドを使用します。
Backlog API:
http://www.backlog.jp/api/method3_4.html
xmlrpcライブラリでさくっと取ってきたかったのですが、なぜかうまくいかなかったので今回はテンプレートを使ってxmlを生成しています。
hoge/conf/backlog.findIssue.xml
<?xml version="1.0" encoding="utf-8"?>
<methodCall>
<methodName>backlog.findIssue</methodName>
<params>
<param>
<value>
<struct>
<member>
<name>projectId</name>
<value>
<int>${projectId}</int>
</value>
</member>
<member>
<name>limit</name>
<value>
<int>${limit}</int>
</value>
</member>
<member>
<name>offset</name>
<value>
<int>${offset}</int>
</value>
</member>
<member>
<name>sort</name>
<value>
<string>CREATED</string>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>
リクエスト用XMLは、play.templateのrender()メソッドをつかって値を置き換えています。(projectIdとlimitとoffset)
課題データのうち、課題キー(key)と件名(summary)と詳細(description)を取得したら、課題キーをキーに既に登録されていないかを確認して、もしなければDataモデルを新規作成しています。
http://localhost:9000/application/backlog へアクセスすると
こんな感じで、データが取得できました。
課題データのうち、課題キー(key)と件名(summary)と詳細(description)を取得したら、課題キーをキーに既に登録されていないかを確認して、もしなければDataモデルを新規作成しています。
http://localhost:9000/application/backlog へアクセスすると
こんな感じで、データが取得できました。
SalesforceのOAuth 2.0接続に関しては、以下の公式サイトに詳しい手順があるので、この通り実装します。
http://blogjp.sforce.com/2011/04/forcecom-rest-a-4182.html
では、さっそくPlay!でつながってみます。
コントローラは、認証用URLとコールバック用URLの2つのアクセスがあるので、新たにsfdc()とcallback()を追加します。
hoge/app/controllers/Application.java
package controllers;
import play.*;
import play.mvc.*;
import jobs.*;
public class Application extends Controller {
public static void index() {
render();
}
public static void backlog() {
new ImportBacklog().now();
redirect("/datas/list");
}
public static void sfdc() {
String authorizeUrl = "https://login.salesforce.com/services/oauth2/authorize";
String redirectUrl = "http://localhost:9000/application/callback";
String clientId = "loginid";
String url = authorizeUrl + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + redirectUrl;
redirect(url);
}
public static void callback() {
String code = params.get("code");
new ImportSfdc(code).now();
redirect("/datas/list");
}
}
Play!frameworkには、play.lib.OAuth2というヘルパークラスがあるのですが、Salesforceの認証URLへのアクセスには、response_typeパラメータが必要であるため、play.lib.OAuth2を使わず、urlを作成して直接リダイレクトしています。
ソースコードをみても、retrieveVerificationCode()メソッドではリダイレクトしているだけなので特に問題はなさそうです。
コールバックの実装はImportSfdc ジョブ に記述します。
各手順の詳細は公式サイトの「Salesforce.comのOAuth 2.0を掘り下げてみよう」を読んでもらえれば図解で書いてあるので、ここでは要点だけ記載すると、
1. 認証URLにアクセスし認証を行った後、callbackURLにリダイレクトされるので、URLパラメータからcodeを取得します。
2. 取得したcodeとgrant_type、client_id、client_secret、redirect_uri パラメータを使用して、今度はトークン取得URLにPOSTでアクセスします。
3. トークン取得URLからJson形式でレスポンスが返ってくるのでその中のアクセストークン(accessToken)を取得します。
4. アクセストークンをHTTPヘッダ(Authorization: OAuth)に含めることで、以後REST形式でデータを取得できます。
hoge/app/jobs/ImportSfdc.java
package jobs;
import java.util.*;
import play.*;
import play.jobs.*;
import play.libs.WS;
import com.google.gson.*;
import models.*;
public class ImportSfdc extends Job {
String code;
public ImportSfdc(String code) {
this.code = code;
}
public void doJob() {
String tokenUrl = "https://login.salesforce.com/services/oauth2/token";
String redirectUrl = "http://localhost:9000/application/callback";
String clientId = "loginid";
String clientSecret = "password";
String url = tokenUrl + "?grant_type=authorization_code&code=" + code + "&client_id=" + clientId + "&client_secret=" + clientSecret + "&redirect_uri=" + redirectUrl;
JsonElement tokens = WS.url(url).post().getJson();
String accessToken = tokens.getAsJsonObject().get("access_token").getAsString();
String instanceUrl = tokens.getAsJsonObject().get("instance_url").getAsString();
url = instanceUrl + "/services/data/v23.0/query/?q=SELECT Id, Subject, Description from Case";
JsonElement cases = WS.url(url).setHeader("Authorization", "OAuth " + accessToken).get().getJson();
Logger.info(cases.toString());
Iterator<JsonElement> records = cases.getAsJsonObject().get("records").getAsJsonArray().iterator();
while(records.hasNext()) {
JsonElement attributes = records.next();
String caseId = attributes.getAsJsonObject().get("Id").getAsString();
String subject = attributes.getAsJsonObject().get("Subject").getAsString();
JsonElement elem = attributes.getAsJsonObject().get("Description");
String description = elem.isJsonNull() ? "" : elem.getAsString();
Data data = Data.find("byCode", caseId ).first();
if(data == null) {
data = new Data(caseId, subject, description);
data._save();
}
}
}
}
こちらもごっそりCaseを取得したかったので、SOQLクエリで取得します。
Force.com REST API クイックリファレンス
http://localhost:9000/application/sfdc へアクセスすると
こんな感じでデータが取得できました。
まとめ
今回は社内のデータ収集のためにバックログとセールスフォースのAPI連携をPlay! frameworkで作ってみました。Play!のジョブ機能には、cronの機能も備わっているので、自動で同期したりすることもできて便利です。社内の情報を集約して、使えるナレッジベースになるように修練していきたいですね。
ではまた。