ラジコン操作で作成したWEBサービスをバックエンドJAVAで連携する自作のサンプル。
操作にはBackbone.jsなどで直接REST実装してもよかったが、今後アームの取り付けなど拡張するためJAVAでAPI化した。
(GIT : https://github.com/tsunaki00/radiocontrol)
【イメージ】
使用したライブラリ
・httpcore-4.2.2.jar
・httpclient-4.2.3.jar
・jsonic-1.2.11.jar
・s2-tiger-2.4.44.jar(汎用Beanで利用しているので、commons-beanutilsなどでもOK)
階層
org.tsunaruts.radiocontrolfw
└─ webservice
├ factory
│ ├─ impl
│ │ └─DefaultParserFactory.java
│ └─ AbstractParserFactory.java
├ methods
│ ├─ AbstractWebMethod.java
│ ├─ GET.java
│ └─ POST.java
├ parser
│ ├─ impl
│ │ └─JsonParser.java
│ └─ AbstractParser.java
└─ WebService.java
まず外部から呼び出し用のクラス
【WebService.java】
package org.tsunaruts.radiocontrolfw.webservice;
import java.io.IOException;
import java.text.ParseException;
import org.tsunaruts.radiocontrolfw.webservice.methods.GET;
import org.tsunaruts.radiocontrolfw.webservice.methods.POST;
/**
* <pre>
* WEBサービス用のメソッドを発行するクラス
* ※※DIコンテナなどでURLを外部から設定する
* </pre>
*/
public class WebService {
/** URL */
public String url;
/**
*
* @param url
*/
public void initialize(String url) {
this.url = url;
}
/**
* WEBサービスを実行します
* <GET メソッド>
* @return 実行結果EntityList
*/
public GET get(String path) {
return new GET(url, path);
}
/**
* WEBサービスを実行します
* <POST メソッド>
* @return 実行結果Entity
* @throws IOException
* @throws ParseException
*/
public POST post(String path) {
return new POST(url, path);
}
}
次にWEBサービスに対応したメソッド
【AbstractWebMethod.java】
package org.tsunaruts.radiocontrolfw.webservice.methods;
import java.io.IOException;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.seasar.framework.beans.util.BeanMap;
import org.seasar.framework.exception.IORuntimeException;
import org.seasar.framework.exception.ParseRuntimeException;
import org.tsunaruts.radiocontrolfw.webservice.factory.AbstractParserFactory;
import org.tsunaruts.radiocontrolfw.webservice.parser.AbstractParser;
/**
* <pre>
* WEBメソッドの抽象クラス
* </pre>
*/
public abstract class AbstractWebMethod {
/** WEBサービス提供ホスト名 http://localhost */
protected String host;
/** WEBサービス path /action */
protected String path;
/**
* コンストラクタ
* @param host hostを設定します Examples : http://localhost
* @param path pathを設定 Examples : /action
*/
public AbstractWebMethod(String host, String path) {
this.host = host;
this.path = path;
}
/**
* 子クラスでHttpUriRequestを生成
* @param parameter リクエストパラメータ
* @return HttpUriRequest
* @throws URISyntaxException URLの組み立て失敗
*/
protected abstract HttpUriRequest getHttpUriRequest(BeanMap parameter) throws URISyntaxException;
/**
* @see getStringBodyResult
*/
public String getStringBodyResult(){
return getStringBodyResult(null);
}
/**
* RESTの戻り値を返します。
* @param parameter リクエストパラメータ
* @return RESTレスポンス
*/
public String getStringBodyResult(BeanMap parameter) {
try {
HttpUriRequest request = getHttpUriRequest(parameter);
List<String> list = execute(request, String.class, false);
if (list != null && !list.isEmpty()) {
return list.get(0);
}
return null;
} catch (URISyntaxException e) {
throw new RuntimeException(e);
} catch (ParseException e) {
throw new ParseRuntimeException(e);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
public <T> T getSingleResult(Class<T> returnType) {
return getSingleResult(returnType, null);
}
/**
* RESTの戻り値を返します。
* @param returnType 戻り値の型
* @param parameter リクエストパラメータ
* @return RESTレスポンス
*/
public <T> T getSingleResult(Class<T> returnType, BeanMap parameter) {
try {
HttpUriRequest request = getHttpUriRequest(parameter);
List<T> list = execute(request, returnType, false);
if (list != null && !list.isEmpty()) {
return list.get(0);
}
return null;
} catch (URISyntaxException e) {
throw new RuntimeException(e);
} catch (ParseException e) {
throw new ParseRuntimeException(e);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/**
*
* @return
*/
public <T> List<T> getResultList(Class<T> returnType) {
return getResultList(returnType, null);
}
/**
* RESTの戻り値をList返します。
* @param returnType 戻り値の型
* @param parameter リクエストパラメータ
* @return List<RESTレスポンス>
*/
public <T> List<T> getResultList(Class<T> returnType, BeanMap parameter) {
try {
HttpUriRequest request = getHttpUriRequest(parameter);
List<T> list = execute(request, returnType, true);
if (list != null && !list.isEmpty()) {
return list;
}
return null;
} catch (URISyntaxException e) {
throw new RuntimeException(e);
} catch (ParseException e) {
throw new ParseRuntimeException(e);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/**
* 外部WEBサービスにリクエストを発行
* @param request リクエストオブジェクト
* @param returnType 戻り値型
* @param isList 戻り値の種類
* @return RESTレスポンス
* @throws ParseException レスポンスパースエラー
* @throws IOException リクエスト処理時の例外
*/
@SuppressWarnings("unchecked")
private <T> List<T> execute(HttpUriRequest request, Class<T> returnType, boolean isList) throws ParseException, IOException {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpResponse response = httpClient.execute(request);
HttpEntity httpEntity = response.getEntity();
String body = EntityUtils.toString(httpEntity, "UTF-8");
AbstractParser parser = AbstractParserFactory.getParserFactory().getParser(httpEntity.getContentType().getValue());
if (returnType == String.class) {
List<T> ret = new ArrayList<>();
ret.add((T) body);
return ret;
}
if (parser == null) {
return null;
}
if (isList) {
return parser.parseList(body, returnType);
} else {
List<T> ret = new ArrayList<>();
ret.add(parser.parse(body, returnType));
return ret;
}
}
/**
* @see AbstractWebMethod#uriBuild(BeanMap)
* @return URIBuilder
*/
protected URIBuilder uriBuild() {
return uriBuild(null);
}
/**
* URL組み立て
* @param parameter リクエストパラメータ(GETのみ)
* @return URIBuilder
*/
protected URIBuilder uriBuild(BeanMap parameter) {
URIBuilder builder = new URIBuilder();
String scheme = "http://";
if (host.indexOf("://") > -1) {
scheme = host.substring(0, host.indexOf("://"));
host = host.substring(host.indexOf("://") + "://".length());
}
builder.setScheme(scheme);
builder.setHost(host);
builder.setPath(path);
if (parameter != null) {
Iterator<Map.Entry<String, Object>> ite = parameter.entrySet().iterator();
while(ite.hasNext()) {
Map.Entry<String, Object> entry = ite.next();
builder.setParameter(entry.getKey(), (String) entry.getValue());
}
}
return builder;
}
}
各リクエストメソッド(とりあえずGETとPOST)
【GET.java】
package org.tsunaruts.radiocontrolfw.webservice.methods;
import java.net.URISyntaxException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.seasar.framework.beans.util.BeanMap;
/**
* <pre>
* GETリクエストを実装する
* </pre>
*/
public class GET extends AbstractWebMethod {
public GET(String host, String path) {
super(host, path);
}
/* (non-Javadoc) */
@Override
protected HttpUriRequest getHttpUriRequest(BeanMap parameter)
throws URISyntaxException {
return new HttpGet(uriBuild(parameter).build().toString());
}
}
【POST.java】
package org.tsunaruts.radiocontrolfw.webservice.methods;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.seasar.framework.beans.util.BeanMap;
/**
* <pre>
* POSTリクエストを実装する
* </pre>
*/
public class POST extends AbstractWebMethod {
public POST(String host, String path) {
super(host, path);
}
/* (non-Javadoc) */
@Override
protected HttpUriRequest getHttpUriRequest(BeanMap parameter)
throws URISyntaxException {
try {
URIBuilder builder = uriBuild();
List<NameValuePair> params = new ArrayList<NameValuePair>();
if (parameter != null) {
Iterator<Map.Entry<String, Object>> ite = parameter.entrySet().iterator();
while(ite.hasNext()) {
Map.Entry<String, Object> entry = ite.next();
params.add(new BasicNameValuePair(entry.getKey(), (String) entry.getValue()));
}
}
HttpPost post = new HttpPost(builder.build().toString());
post.setEntity(new UrlEncodedFormEntity(params));
return post;
} catch (UnsupportedEncodingException e) {
throw new URISyntaxException(parameter.toString(), host + path);
}
}
}
最後にレスポンス解析周りのクラス
SOAP対応を考えて、AbstractFactoryで実装
【AbstractParserFactory.java】
package org.tsunaruts.radiocontrolfw.webservice.factory;
import org.tsunaruts.radiocontrolfw.webservice.parser.AbstractParser;
import org.tsunaruts.radiocontrolfw.webservice.parser.impl.JsonParser;
/**
* <pre>
* contentTypeをみてParserを生成するFactoryクラス
* </pre>
*/
public abstract class AbstractParserFactory {
/**
* 環境に合わせてFactoryクラスを生成する
* @return AbstractParserFactory
*/
public static AbstractParserFactory getParserFactory() {
return new DefaultParserFactory();
}
/**
* contentTypeをみてParserを生成します
* @param contentType contentType
* @return
*/
public abstract AbstractParser getParser(String contentType);
}
package org.tsunaruts.radiocontrolfw.webservice.factory.impl;
import org.tsunaruts.radiocontrolfw.webservice.factory.AbstractParserFactory;
import org.tsunaruts.radiocontrolfw.webservice.parser.AbstractParser;
import org.tsunaruts.radiocontrolfw.webservice.parser.impl.JsonParser;
/**
* <pre>
* デフォルトのFactoryクラス
* </pre>
*
*/
public class DefaultParserFactory extends AbstractParserFactory {
/* (non-Javadoc) */
@Override
public AbstractParser getParser(String contentType) {
// JSONの場合
if (contentType != null && contentType.indexOf("application/json") > -1) {
return new JsonParser();
}
// SOAP, HTML, TEXTの場合
return null;
}
}
【AbstractParser.java】
package org.tsunaruts.radiocontrolfw.webservice.parser;
import java.util.List;
/**
* <pre>
* レスポンスを解析クラス
* </pre>
*/
public interface AbstractParser {
/**
* レスポンスを解析します
* @param body レスポンスボディ
* @param clazz 戻り
* @return 取得結果
*/
<T> T parse(String body, Class<T> clazz);
/**
* レスポンスを解析します
* @param body レスポンスボディ
* @param clazz 戻り
* @return 取得結果
*/
<T> List<T> parseList(String body, Class<T> clazz);
【JsonParser.java】
package org.tsunaruts.radiocontrolfw.webservice.parser.impl;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import net.arnx.jsonic.JSON;
import org.seasar.framework.beans.util.Beans;
import org.tsunaruts.radiocontrolfw.webservice.parser.AbstractParser;
/**
* <pre>
* JSONを解析するクラス
* </pre>
*/
public class JsonParser implements AbstractParser {
/* (non-Javadoc) */
@Override
public <T> T parse(String body, Class<T> clazz) {
return JSON.decode(body, clazz);
}
/* (non-Javadoc) */
@Override
public <T> List<T> parseList(String body, Class<T> clazz) {
List<T> ret = new ArrayList<>();
@SuppressWarnings("unchecked")
LinkedHashMap<String, Object>[] mapList = JSON.decode(body, LinkedHashMap[].class);
if (mapList != null) {
for (LinkedHashMap<String, Object> map : mapList) {
ret.add(Beans.createAndCopy(clazz, map).execute());
}
}
return ret;
}
}
これで完了、リクエストパラメータをそのままJSONで返すACTIONを作成して単体テスト
実装のテスト
package org.tsunaruts.radiocontrolfw.webservice;
import java.util.List;
import org.seasar.framework.beans.util.BeanMap;
public class WebServiceTest {
public static void main(String[] args) {
WebService webService = new WebService();
webService.initialize("http://localhost:8080/radiocontrol");
BeanMap map = new BeanMap();
map.put("param1", "test1");
map.put("param2", "test2");
List<BeanMap> list = webService.get("/api/control/front")
.getResultList(BeanMap.class, map);
System.out.println(list);
}
}
[{param1=test1}, {param2=test2}]
無事に動いた。
【追記】
Stringでレスポンスを返せる修正とFactory周りがおかしかったので反映しました。