JSONIC + Slim3 + GAE/JでRESTサービスを構築する方法 Part4
これまでの記事はこちら。
- JSONIC + Slim3 + GAE/JでRESTサービスを構築する方法 Part1 - Jupitris on Laboratory
- JSONIC + Slim3 + GAE/JでRESTサービスを構築する方法 Part2 - Jupitris on Laboratory
- JSONIC + Slim3 + GAE/JでRESTサービスを構築する方法 Part3 - Jupitris on Laboratory
今回は、検索機能や登録機能のビジネスロジック部分を解説。
まず、実装に取り掛かる前に、Eclipseプロジェクトに設定を追加。プロジェクトのルートに、libフォルダを作成して、そこにslim3-gen-1.0.14.jarを追加。slim3-gen-1.0.14.jarは、Google Code Archive - Long-term storage for Google Code Project Hosting.からslim3-blank-1.0.14.zipをダウンロードしてきて解凍すると、libフォルダの中に入ってます。
次に、Eclipseプロジェクトのプロパティから、Java Compiler > Annotation Processingを選択して、Generated source directoryに「target/apt_generated」を入力。
そのままAnnotation Processing > Factory Pathを選択して、Add JARsボタンから最初に追加したslim3-gen-1.0.14.jarを追加。
最後に、Java Build Pathを選択して、target/apt_generatedフォルダをソースフォルダとして追加。
これで、Eclipseプロジェクトの設定は終わり。もし、target/apt_generatedができていなかったら、ビルドしなおしてみるといいかも。
では、ビジネスロジックの実装へ。まずは、com.example.logicパッケージを作成して、そこにGreetingLogicインターフェースを作成。
package com.example.logic; import java.util.List; import java.util.Map; import com.example.model.Messages; /** * @author jupitris * */ public interface GreetingLogic { /** * Returns the list of message which is found by condition. * * @param param * the condition for finding data. * @return the list of message */ List<Messages> find(Map<String, Object> param); /** * Create a message to data store. * * @param messages * the <code>Messages</code> which create to data store. * @return the <code>Messages</code> if created to data store. */ Messages create(Messages messages); /** * Update a message to data store. * * @param messages * the <code>Messages</code> which update to data store. * @return the <code>Messages</code> if updated to data store. */ Messages update(Messages messages); /** * Delete a message from data store. * * @param messages * the <code>Messages</code> which delete from data store. */ void delete(Messages messages); }
次は、さっき作成したインターフェースの具象クラスを作成。com.example.logic.implパッケージを作成して、そこにGreetingLogicImplクラスを作成。
package com.example.logic.impl; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.slim3.datastore.Datastore; import org.slim3.datastore.FilterCriterion; import org.slim3.datastore.ModelQuery; import org.slim3.util.BeanUtil; import com.example.logic.GreetingLogic; import com.example.meta.MessagesMeta; import com.example.model.Messages; import com.google.appengine.api.datastore.Transaction; /** * @author jupitris * */ public class GreetingLogicImpl implements GreetingLogic { /** meta data. */ private final MessagesMeta meta = MessagesMeta.get(); /* * (non-Javadoc) * * @see com.example.logic.GreetingLogic#find(java.util.Map) */ @Override public List<Messages> find(Map<String, Object> param) { ModelQuery<Messages> query = Datastore.query(meta); // create filters. these filters become "where" statement. List<FilterCriterion> filter = new ArrayList<FilterCriterion>(); for (Map.Entry<String, Object> entry : param.entrySet()) { if (entry.getKey().equals(meta.message.getName())) { filter.add(meta.message.equal((String) entry.getValue())); } } FilterCriterion[] filters = filter.toArray(new FilterCriterion[filter.size()]); if (filters.length > 0) { query = query.filter(filters); } return query.asList(); } /* * (non-Javadoc) * * @see com.example.logic.GreetingLogic#create(com.example.model.Messages) */ @Override public Messages create(Messages messages) { Transaction tx = Datastore.beginTransaction(); Datastore.put(messages); tx.commit(); return messages; } /* * (non-Javadoc) * * @see com.example.logic.GreetingLogic#update(com.example.model.Messages) */ @Override public Messages update(Messages messages) { Transaction tx = Datastore.beginTransaction(); Messages latest = Datastore.get(meta, messages.getKey(), messages.getVersion()); BeanUtil.copy(messages, latest); Datastore.put(tx, latest); tx.commit(); return latest; } /* * (non-Javadoc) * * @see com.example.logic.GreetingLogic#delete(com.example.model.Messages) */ @Override public void delete(Messages messages) { Transaction tx = Datastore.beginTransaction(); Messages latest = Datastore.get(meta, messages.getKey(), messages.getVersion()); Datastore.delete(latest.getKey()); tx.commit(); } }
最後に、GreetingControllerクラスを修正。作成したビジネスロジックを呼び出す処理を追加。
package com.example.controller; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.example.logic.GreetingLogic; import com.example.logic.impl.GreetingLogicImpl; import com.example.model.Messages; /** * @author jupitris * */ public class GreetingController { /** logger. */ private static Logger logger = LoggerFactory.getLogger(GreetingController.class); private final GreetingLogic logic = new GreetingLogicImpl(); /** * Returns the list of <code>Messages</code> which is found by condition. * * @param param * the condition for finding data. * @return the list of <code>Messages</code> */ public List<Messages> find(Map<String, Object> param) { logger.debug("find was called."); return logic.find(param); } /** * Create a message to data store. * * @param messages * the <code>Messages</code> which create to data store. */ public void create(Messages messages) { logger.debug("create was called."); logic.create(messages); } /** * Update a message to data store. * * @param messages * the <code>Messages</code> which update to data store. */ public void update(Messages messages) { logger.debug("update was called."); logic.update(messages); } /** * Delete a message from data store. * * @param messages * the <code>Messages</code> which delete from data store. */ public void delete(Messages messages) { logger.debug("delete was called."); logic.delete(messages); } }
これで、簡単ながらもひと通りの処理は実装完了。それぞれの処理に関する詳しい話は、また別の記事にて。一応、最低限のテストコードも載せておきます。
package com.example.logic.impl; import java.util.HashMap; import java.util.List; import java.util.Map; import junit.framework.Assert; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slim3.tester.AppEngineTestCase; import com.example.logic.GreetingLogic; import com.example.model.Messages; /** * @author jupitris * */ public class GreetingLogicImplTest extends AppEngineTestCase { @Override @Before public void setUp() throws Exception { super.setUp(); } @Override @After public void tearDown() throws Exception { super.tearDown(); } @Test public void testCreate01() { Messages expected = new Messages(); expected.setMessage("test create 01"); GreetingLogic logic = new GreetingLogicImpl(); Messages actual = logic.create(expected); Assert.assertNotNull(actual); Assert.assertEquals(expected.getMessage(), actual.getMessage()); Assert.assertNotNull(actual.getKey()); Assert.assertNotNull(actual.getVersion()); } @Test public void testFind01() { Map<String, Object> param = new HashMap<String, Object>(); param.put("message", "test find 01"); Messages expected = new Messages(); expected.setMessage("test find 01"); GreetingLogic logic = new GreetingLogicImpl(); logic.create(expected); List<Messages> actuals = logic.find(param); Assert.assertFalse(actuals.isEmpty()); for (Messages actual : actuals) { Assert.assertEquals(expected.getMessage(), actual.getMessage()); } } @Test public void testUpdate01() { Messages expected = new Messages(); expected.setMessage("test update 01"); GreetingLogic logic = new GreetingLogicImpl(); Messages result = logic.create(expected); result.setMessage("test update 01-B"); Messages actual = logic.update(result); Assert.assertNotNull(actual); Assert.assertEquals(expected.getMessage(), actual.getMessage()); Assert.assertEquals(result.getMessage(), actual.getMessage()); Assert.assertNotNull(actual.getKey()); Assert.assertNotNull(actual.getVersion()); } @Test public void testDelete01() { Map<String, Object> param = new HashMap<String, Object>(); param.put("message", "test delete 01"); Messages expected = new Messages(); expected.setMessage("test delete 01"); GreetingLogic logic = new GreetingLogicImpl(); Messages result = logic.create(expected); logic.delete(result); List<Messages> actuals = logic.find(param); Assert.assertTrue(actuals.isEmpty()); } }
サービスの呼び出し方ですが、findとcreateだけ載せておきます。
まずはfindですが、HTTP MethodはGETにして、http://localhost:8080/restapp/greeting.jsonにアクセス。データが存在していれば、下記のようなJSONデータが返却されます。
[ { "createdAt": 1326102940270, "createdBy": null, "key": { "complete": true, "id": 1, "kind": "Messages", "name": null, "namespace": "", "parent": null }, "message": "Hello world!", "updatedAt": 1326102940270, "updatedBy": null, "version": 1 }, { "createdAt": 1326103005601, "createdBy": null, "key": { "complete": true, "id": 2, "kind": "Messages", "name": null, "namespace": "", "parent": null }, "message": "こんにちは、世界!", "updatedAt": 1326103005601, "updatedBy": null, "version": 1 } ]
続いてcreate。HTTP MethodはPOSTにして、BodyのContent-typeに「application/json」、Charasetに「UTF-8」、メッセージBody(っていう表現であってるかしら?)に「{"message" : "Hello world!"}」を設定して、http://localhost:8080/restapp/greeting.jsonにアクセス。処理が正しく動作すれば、データが登録されます。
次回は、APIの増やし方について。今はひとつのfindしか使えないけど、用途に応じて複数のfindメソッドが欲しくなるときありますよね。その定義の仕方について説明します。余裕があれば、他のこともふれるかも。