jsonengineをJSONじゃなくてAMFで使ってみた
jsonengineを使えばサーバーサイドのコーディング不要でGoogle App Engineをデータストアとして利用できるけど、
JSONなのでJavaScriptからだと使いやすいけど、ActionScriptだとちょい使いにくい。ような気がする。
ASでもJSON扱うライブラリはあるけど、Classへのマッピングとか面倒くさいし
(なんかリフレクションつかって自動マッピングしてくれる便利なライブラリあるような気がするけど)
んで、
S3BlazeDSを経由してjsonengineを使えば、ActionScriptからめんどくさい事せずにjsonengine使えるんじゃね?
って思って超適当に作ってみた。
これをつかえば、
get,update,delete,queryを、JSONに変換したり、HTTPリクエストしたりとかせずに、RemoteObject呼び出しできる。
ということは、通信はJSONじゃなくて、AMFになります。(jsonengineなのにAMFです…)
通信の効率ちょい良くなるかな。
あと、ASの型をjsonengineのdocTypeとして使用して、勝手に変換してくれるので便利?です。
権限他、例外処理等はException投げるので、FaultEventで処理ができます。
ソース
とりあえず、適当なので、使うと不具合があると思うので自分で直してください。
S3BlazeDSJEService.java(jsonengineのFrontControllerを元にして作ってます)
これをRemoteObjectとしてBlazeDSに登録します。
public class S3BlazeDSJEService { private static final Pattern condPattern = Pattern .compile("^([^\\.]*)\\.(eq|gt|ge|lt|le)\\.(.*)$"); private static final Pattern quotePattern = Pattern .compile("^[\"'](.*)[\"']$"); private Object convertPropValue(final String propValue) { // if it's quoted, treat it as a String final Matcher m = quotePattern.matcher(propValue); if (m.find()) { return m.group(1); } // if it's not quoted, try to parse it as a BigDecimal try { return new BigDecimal(propValue); } catch (NumberFormatException e) { // failed } // try to parse as a Boolean if ("true".equals(propValue)) { return true; } else if ("false".equals(propValue)) { return false; } // otherwise, treat it as a String return propValue; } private CRUDRequest createCRUDRequest(ASObject object, Long checkUpdatesAfter) throws UnsupportedEncodingException { // parse JSON doc final CRUDRequest jeReq = new CRUDRequest(object); // init the request initJERequest(jeReq, object.getType()); // decode docId if (!StringUtil.isEmpty((String) object.get("_docId"))) { jeReq.setDocId((String) object.get("_docId")); } // set checkConflict flag try { jeReq.setCheckUpdatesAfter(checkUpdatesAfter); } catch (Exception e) { // NPE or NumberFormatException } return jeReq; } private void initJERequest(JERequest jeReq, String docType) { // decode docType if (StringUtil.isEmpty(docType)) { throw new IllegalArgumentException("No docType found"); } jeReq.setDocType(docType); // set timestamp jeReq.setRequestedAt((new JEUtils()).getGlobalTimestamp()); // set Google account info if (JEUserUtils.isLoggedIn()) { jeReq.setRequestedBy(JEUserUtils.userEmail()); jeReq.setAdmin(JEUserUtils.isAdmin()); } // set display name String displayName = JEUserUtils.getDisplayName(); jeReq.setDisplayName(displayName); } private void parseCondFilter(final QueryRequest qReq, final String cond) { // try to parse the cond params final Matcher m = condPattern.matcher(cond); if (!m.find()) { throw new IllegalArgumentException("Illegal condFilter: " + cond); } final String propName = m.group(1); final String condToken = m.group(2); final String propValue = m.group(3); // try to convert propValue final Object propValueObj = convertPropValue(propValue); // if propName ends with "_", extract terms from propValue; if (propName.endsWith("_")) { final Set<String> values = (new JEUtils()).extractTerms(propValue); for (String value : values) { QueryFilter.addCondFilter(qReq, propName, condToken, value); } } else { QueryFilter.addCondFilter(qReq, propName, condToken, propValueObj); } } private void parseLimitFilter(final QueryRequest qReq, final String limitParam) { final int limit = Integer.parseInt(limitParam); QueryFilter.addLimitFilter(qReq, limit); } private void parseSortFilter(final QueryRequest qReq, final String sortParam) { final String[] sortTokens = sortParam.split("\\."); final String propName = sortTokens[0]; final String sortOrder = sortTokens[1]; QueryFilter.addSortFilter(qReq, propName, sortOrder); } public void doDelete(ASObject object, Long checkUpdatesAfter) throws IOException, JEAccessDeniedException, JENotFoundException, JEConflictException, JERedirectException { // do delete final CRUDRequest jeReq = createCRUDRequest(object, checkUpdatesAfter); try { (new CRUDService()).delete(jeReq); } catch (JEAccessDeniedException e) { if (JEUserUtils.isLoggedIn() == false) { jsonRedirectToLogin(); } throw e; } } public ASObject doUpdate(ASObject object, Long checkUpdatesAfter, boolean isUpdateOnly) throws IOException, JEConflictException, JENotFoundException, JERedirectException, JEAccessDeniedException { // do put final CRUDRequest jeReq = createCRUDRequest(object, checkUpdatesAfter); final String resultJson; try { resultJson = (new CRUDService()).put(jeReq, isUpdateOnly); } catch (JEAccessDeniedException e) { if (JEUserUtils.isLoggedIn() == false) { jsonRedirectToLogin(); } if (jeReq.getDisplayName() == null) { jsonRedirectToDisplayName(); } throw e; } ASObject decode = JSON.decode(resultJson, ASObject.class); decode.setType((String) object.getType()); // return the result return decode; } public ASObject doGet(ASObject object) throws IOException, JENotFoundException, JEConflictException, JERedirectException, JEAccessDeniedException { // do get final CRUDRequest jeReq = createCRUDRequest(object, null); final String resultJson; try { resultJson = (new CRUDService()).get(jeReq); } catch (JEAccessDeniedException e) { if (JEUserUtils.isLoggedIn() == false) { jsonRedirectToLogin(); } throw e; } ASObject decode = JSON.decode(resultJson, ASObject.class); decode.setType(object.getType()); // return the result return decode; } public ASObject[] doQuery(String docType, String[] conds, String sortParam, String limitParam) throws IOException, JERedirectException, JEAccessDeniedException { // add QueryFilters for "cond" parameters final QueryRequest qReq = new QueryRequest(); initJERequest(qReq, docType); // add QueryFilters for "cond" if (conds != null) { for (String cond : conds) { parseCondFilter(qReq, cond); } } // add QueryFilters for "sort" if (sortParam != null) { parseSortFilter(qReq, sortParam); } // add QueryFilters for "limit" if (limitParam != null) { parseLimitFilter(qReq, limitParam); } // execute the query final String resultJson; try { resultJson = (new QueryService()).query(qReq); } catch (JEAccessDeniedException e) { if (JEUserUtils.isLoggedIn() == false) { jsonRedirectToLogin(); } throw e; } ASObject[] decodes = JSON.decode(resultJson, ASObject[].class); for (ASObject decode : decodes) { decode.setType(docType); } return decodes; } protected void jsonRedirectToLogin() throws JERedirectException { final String redirectURL = JEUserUtils.getLoginURL("/user/index").toString(); jsonRedirect(redirectURL); } protected void jsonRedirectToDisplayName() throws JERedirectException { final String redirectURL = JEUtils.getRequestServer() + "/user/displayName"; jsonRedirect(redirectURL); } protected void jsonRedirect(String redirectURL) throws JERedirectException { throw new JERedirectException(redirectURL); } }
Flex側
超テキトーですが、こんな感じで使います。
<fx:Script> <![CDATA[ //Get実行 private function jeGet():void{ var s:Sample1 = new Sample1(); s._docId = docId.text; var ro:RemoteObject = new RemoteObject("pingService"); ro.addEventListener(ResultEvent.RESULT, function(e:ResultEvent):void { var rs:Sample1 = e.result as Sample1; Alert.show(rs.messageBody_); }); ro.addEventListener(FaultEvent.FAULT, function(e:FaultEvent):void { Alert.show(e.toString()); }); ro.getOperation("doGet").send(s); } //Update実行 private function jeUpdate():void{ var s:Sample1; if(grid.selectedItem != null){ s = grid.selectedItem as Sample1; } else{ s = new Sample1(); } s.messageBody_ = tiMsg.text; var ro:RemoteObject = new RemoteObject("pingService"); ro.addEventListener(ResultEvent.RESULT, function(e:ResultEvent):void { var rs:Sample1 = e.result as Sample1; Alert.show(rs.messageBody_); }); ro.addEventListener(FaultEvent.FAULT, function(e:FaultEvent):void { Alert.show(e.toString()); }); ro.getOperation("doUpdate").send(s, null, false); } //Delete実行 private function jeDelete():void{ var s:Sample1; if(grid.selectedItem != null){ s = grid.selectedItem as Sample1; } else{ s = new Sample1(); } s._docId = docId.text; var ro:RemoteObject = new RemoteObject("pingService"); ro.addEventListener(ResultEvent.RESULT, function(e:ResultEvent):void { Alert.show("ok."); }); ro.addEventListener(FaultEvent.FAULT, function(e:FaultEvent):void { Alert.show(e.toString()); }); ro.getOperation("doDelete").send(s, null); } //Query実行 private function jeQuery():void{ var ro:RemoteObject = new RemoteObject("pingService"); ro.addEventListener(ResultEvent.RESULT, function(e:ResultEvent):void { gridData = e.result as Array; }); ro.addEventListener(FaultEvent.FAULT, function(e:FaultEvent):void { Alert.show(e.toString()); }); ro.getOperation("doQuery").send(">slim3.model.Sample1", null, null, null); } private function gridChangeEvent(e:ListEvent):void{ if(grid.selectedItem != null){ tiMsg.text = grid.selectedItem.messageBody_; docId.text = grid.selectedItem._docId; } } ]]> </fx:Script> <fx:Declarations> <fx:Array id="gridData"></fx:Array> </fx:Declarations> <mx:VBox> <s:TextInput id="tiMsg" text="Hello!"/> <!-- jsonengineのdocId --> <s:TextInput id="docId" text=""/> <mx:HBox> <s:Button label="get" click="jeGet()"/> <s:Button label="update" click="jeUpdate()"/> <s:Button label="delete" click="jeDelete()"/> <s:Button label="query" click="jeQuery()"/> </mx:HBox> <!-- クエリの結果を表示するgrid --> <mx:DataGrid id="grid" dataProvider="{gridData}" change="gridChangeEvent(event)"> <mx:columns> <mx:DataGridColumn headerText="docId" dataField="_docId"/> <mx:DataGridColumn headerText="messageBody_" dataField="messageBody_"/> <mx:DataGridColumn headerText="createdAt" dataField="_createdAt"/> <mx:DataGridColumn headerText="updatedAt" dataField="_updatedAt"/> <mx:DataGridColumn headerText="displayName" dataField="_displayName"/> </mx:columns> </mx:DataGrid> </mx:VBox>
メタタグでRemoteClassに指定しておけば、Class名がdocTypeとして使用されます。
Sample1.as
package slim3.model { import mx.rpc.remoting.RemoteObject; [RemoteClass] public class Sample1 { public function Sample1() { } public var _docId:String; public var _createdAt:Number; public var _updatedAt:Number; public var _displayName:String; public var messageBody_:String; } }