Wiki source code of ERRest Framework

Last modified by Pascal Robert on 2012/07/21 19:16

Hide last authors
Pascal Robert 68.1 1 {{toc/}}
Pascal Robert 63.1 2
Pascal Robert 21.1 3 = Presentations =
4
Pascal Robert 68.1 5 * A presentation made by Mike Schrag on February 16th 2010 about ERREST. [[http:~~/~~/wocommunity.org/podcasts/ERRest-2010-02-16.mov>>url:http://wocommunity.org/podcasts/ERRest-2010-02-16.mov||shape="rect"]]
6 It's also listed on the [[Screencasts Page>>url:http://www.wocommunity.org/webobjects_screencasts.html||shape="rect"]] on wocommunity.org, in [[iTunes>>url:http://itunes.apple.com/us/podcast/webobjects-podcasts/id270165303||shape="rect"]] and in the [[Podcasts RSS Feed>>url:http://www.wocommunity.org/podcasts/wopodcasts.xml||shape="rect"]].
Pascal Robert 21.1 7
Pascal Robert 63.1 8 * Mike also did a session about it at WOWODC West 2009 (see below).
Pascal Robert 21.1 9
Pascal Robert 68.1 10 * A [[session>>url:http://www.wocommunity.org/webobjects_screencasts.html||shape="rect"]] on ERRest integration with Dojo was made by Pascal Robert at WOWODC 2010. Two other sessions about ERRest were made at WOWODC 2011.
Pascal Robert 21.1 11
Pascal Robert 68.1 12 * You can also find the slides from Pascal's three WOWODC 2012 about ERRest on [[SlideShare>>url:http://www.slideshare.net/wocommunity/||shape="rect"]].
Pascal Robert 57.1 13
Pascal Robert 63.1 14 Current and past WOWODC Sessions are available for purchase on the WebObjects Community Association website.
Pascal Robert 21.1 15
Pascal Robert 68.1 16 1. Log in, or create an account here: [[http:~~/~~/www.wocommunity.org/account>>url:http://www.wocommunity.org/account||shape="rect"]]
Pascal Robert 63.1 17 1. Go to the Community Store where there will be options for purchasing memberships
Pascal Robert 39.1 18
Pascal Robert 63.1 19 = Class names vs Entity names =
Pascal Robert 21.1 20
Pascal Robert 63.1 21 Everything internally in ERRest is based on entity names, as it is throughout WebObjects and EOF.
Pascal Robert 21.1 22
Pascal Robert 63.1 23 Most times your Entity Name will be the same as your Class name, but because Entity names must be unique there are situations where you will need to change the Entity name i.e., if you have two Classes named the same thing but are in different packages.
24
25 Lets say you have a class named "School" in your model, but have defined the Entity name as "ERPSchool" you will always refer to it as "ERPSchool" when passing it in as a parameter. For example:
26
27 {{noformat}}
28 routeRequestHandler.addDefaultRoutes("ERPSchool"); // School.ENTITY_NAME
29 {{/noformat}}
30
31 Also, when naming your controller classes, you should use the Entity name.
32
33 {{noformat}}
34
35 public class ERPSchoolController extends ERXDefaultRouteController {
Pascal Robert 21.1 36 ...
Pascal Robert 63.1 37 School school = (School) routeObjectForKey("ERPSchool");
Pascal Robert 21.1 38
Pascal Robert 63.1 39 {{/noformat}}
Pascal Robert 21.1 40
Pascal Robert 63.1 41 If you want to call it "School" to the outside, add this before you register the default routes for "ERPSchool":
Pascal Robert 21.1 42
Pascal Robert 63.1 43 {{noformat}}
44
45 ERXRestNameRegistry.registry().setExternalNameForInternalName("School", "ERPSchool"); // "School", School.ENTITY_NAME
46
47 {{/noformat}}
48
Pascal Robert 21.1 49 After adding this, no other code changes. All you're saying is that the routes and type names that send over the wire should all say "School".
Pascal Robert 37.1 50
51 = WO HTTP adaptor =
52
53 The WO HTTP adaptor coming with WO 5.3 doesn't support PUT and DELETE operations, so you won't be able to use those two HTTP methods. You need to use either the 5.4 adaptor or the Wonder version of the adaptor.
54
55 = To-many relationships =
56
Pascal Robert 63.1 57 ERRest won't let you add or remove objects in a to-many relationship, it can only update an existing object that is in the relationship. To add a new object to a relationship, you first need to fetch (or create) the parent object and make a second call to create the object and add it to the relationship.
Pascal Robert 37.1 58
59 So let's say you have an existing Organization object and you want to add a Member to it. First, you need to fetch the Organization :
60
61 {{code}}
62
63 GET /cgi-bin/WebObjects/ra/Organization/1.json
64
65 {{/code}}
66
67 and after, you create a new Member :
68
69 {{code}}
70
71 POST /cgi-bin/WebObjects/ra/Organization/1/addMember.json
72
73 {{/code}}
74
75 = Same Origin policy =
76
77 If you are planning to offer your REST services to other people, they might run into Same Origin Policy problem. When using XMLHttpRequest on a page who is on a different domain than the REST service, XMLHttpRequest will tell you that it's not acceptable.
78
Pascal Robert 68.1 79 To get around that problem, many solutions has been found, but two of them are more accepted than the others : [[JSONP>>url:http://en.wikipedia.org/wiki/JSON#JSONP||shape="rect"]] and [[HTTP_access_control>>url:https://developer.mozilla.org/en/HTTP_access_control||shape="rect"]]. I didn't try the JSONP route, but I did try HTTP_access_control, and Dojo and Prototype are using this method to get around the Same Origin policy problem.
Pascal Robert 37.1 80
Pascal Robert 68.1 81 HTTP_access_control works by adding new headers in the request that says which HTTP methods the request wants to do, and it's send as a OPTIONS HTTP method. You MUST reply with some headers to this OPTIONS request. Support for those headers was added in ERRest somewhere in october 2010.
Pascal Robert 37.1 82
Pascal Robert 63.1 83 To enable the headers and support for the OPTIONS method, add this property:
Pascal Robert 57.1 84
Pascal Robert 37.1 85 {{code}}
Pascal Robert 63.1 86 ERXRest.accessControlAllowOrigin=*
87 {{/code}}
Pascal Robert 37.1 88
Pascal Robert 63.1 89 If you want to allow origin for only a specific host, change the value of the property to the IP or DNS name of the requester.
Pascal Robert 37.1 90
Pascal Robert 68.1 91 Now, sadly only newer (2008 or later) browsers support the HTTP Access Control standard. For older browsers, you have to support JSONP, or the [[window.name transport>>url:http://www.sitepen.com/blog/2008/07/22/windowname-transport/||shape="rect"]]. To enable window.name transport support, do it with the following property.
Pascal Robert 63.1 92
93 {{code}}
94 ERXRest.allowWindowNameCrossDomainTransport=true
Pascal Robert 37.1 95 {{/code}}
96
Pascal Robert 67.1 97 Lastly, you can now use JSONP. JSONP works by requesting a callback method name in the URL (?callback=MyCallbackMethodName) and the response is wrapper inside a script tag. Again, you need to enable it:
98
99 {{code}}
100 ERXRest.allowJSONP=true
101 {{/code}}
102
Pascal Robert 63.1 103 = Dates =
Pascal Robert 37.1 104
Pascal Robert 63.1 105 The default formatter for dates is :
106
Pascal Robert 37.1 107 {{code}}
108
Pascal Robert 63.1 109 %Y-%m-%dT%H:%M:%SZ
Pascal Robert 37.1 110
111 {{/code}}
112
Pascal Robert 63.1 113 If you want to work with the the GMT offset, you have to use this instead :
Pascal Robert 37.1 114
115 {{code}}
116
Pascal Robert 63.1 117 %Y-%m-%dT%H:%M:%S%z
Pascal Robert 37.1 118
119 {{/code}}
120
Pascal Robert 63.1 121 To change it, you have to set the "er.rest.timestampFormat" property :
Pascal Robert 37.1 122
123 {{code}}
124
Pascal Robert 63.1 125 er.rest.timestampFormat = %Y-%m-%dT%H:%M:%S%z
Pascal Robert 37.1 126
127 {{/code}}
128
Pascal Robert 63.1 129 If you are using Dojo, you can use dojo.date.stamp.toISOString and dojo.date.stamp.fromISOString to convert from or to a Java date object.
Pascal Robert 39.1 130
Pascal Robert 63.1 131 = JSON Schema support =
Pascal Robert 39.1 132
Pascal Robert 68.1 133 ERRest have support for [[JSON Schema>>url:http://www.sitepen.com/blog/2008/10/31/json-schema/||shape="rect"]] since october 2010. JSON Schema is like a mini-WSDL, where it describe which properties JSON objects have. Dojo use JSON Schema to perform client-side validation, which is cool because you can have some automatic validation based on your model, and validation is done even before the data is sent back to your REST service.
Pascal Robert 63.1 134
135 To get the schema, you need to check in your route action. For example, for the "index" action, you do:
136
Pascal Robert 57.1 137 {{code}}
Pascal Robert 39.1 138
Pascal Robert 63.1 139 public WOActionResults indexAction() throws Throwable {
140 if (isSchemaRequest()) {
141 return schemaResponse(yourERXKeyFilter());
142 }
143 ... do your normal work here
144 }
Pascal Robert 39.1 145
Pascal Robert 57.1 146 {{/code}}
147
Pascal Robert 63.1 148 And you call your REST route, but by adding "?schema=true" at the end of the URL. For example, ///ra/events.json?schema=true//. When you do that, a JSON Schema will be returned. Sample:
Pascal Robert 57.1 149
Pascal Robert 39.1 150 {{code}}
151
Pascal Robert 63.1 152 {
153 "name":"Event",
154 "properties":{
155 "isFullDay":{
156 "optional":true,
157 "type":"string",
158 "maxLength":5
159 },
160 "startDate":{
161 "optional":false,
162 "type":"string",
163 "format":"date-time"
164 },
165 "realDuration":{
166 "optional":true,
167 "type":"integer"
168 }
169 }
170 }
Pascal Robert 39.1 171
172 {{/code}}
173
Pascal Robert 63.1 174 So when you create a new Event with Dojo, if you try to put something else than a date in startDate, or you put a string in realDuration, it will fail because the schema says that startDate is using the date-time format, and that realDuration is a integer.
Pascal Robert 39.1 175
Pascal Robert 63.1 176 = Getting the URI of a REST route =
177
Pascal Robert 68.1 178 For whatever reason, you might need to know the URI for a REST route. To get that information, use the ERXRouteController._controllersForRequest(WORequest) method, and get the value of request().uri(). Sample:
Pascal Robert 63.1 179
Pascal Robert 39.1 180 {{code}}
181
Pascal Robert 63.1 182 NSMutableArray<ERXRouteController> routeControllers = ERXRouteController._controllersForRequest(this.context().request());
183 if (routeControllers.count() > 0) {
184 return routeControllers.objectAtIndex(0).request().uri()
185 } else {
186 return this.context().request().uri()
187 }
Pascal Robert 39.1 188
189 {{/code}}
190
Pascal Robert 63.1 191 = Automatic HTML routing =
192
193 Not only you can get XML and JSON out of ERRest, but you can also get rendered components. This give you "clean" URLs without having to write a bunch of Direct Actions.
194
195 To enable the automatic HTML routing, you need to override the isAutomaticHtmlRoutingEnabled method in your controller to return true:
196
197 {{code}}
198
199 public class EventsController extends ERXDefaultRouteController {
200 ...
201 protected boolean isAutomaticHtmlRoutingEnabled() {
202 return true;
203 }
204
205 {{/code}}
206
207 When do, you can now add .html when you reach a REST route with a browser, for example /ra/events.html instead of /ra/events.json. But you will notice that you will get an exception because it cannot find a component. You need to create a component with the following pattern:
208
209 EntityActionPage
210
211 For example, if you have a Event entity that is controlled by EventsController, and you call the "index" REST route to get a list of events, the component name have to be:
212
213 EventIndexPage
214
215 Also, for all actions except "index", your component have to implement IERXRouteComponent, and you have to add setter methods to get the data that the REST route is sending. For example, if you create a EventShowPage to show the details of a event, EventShowPage.java need the following method:
216
217 {{code}}
218
219 @ERXRouteParameter
220 public void setEvent(Event event) {
221 ...
222 }
223
224 {{/code}}
225
226 The @ERXRouteParameter annotation will make the method suitable for "accepting" objects from the controller, if the annotation is not there, you won't be able to receive the object from the controller.
Pascal Robert 67.1 227
228 = Using D2W rules with ERRest =
229
230 This is a tip that was sent by Farrukh Ijaz in the webobjects-dev mailing list. It allow you to use D2W rules to create your filters on your REST routes.
231
232 {{code}}
233
234
235 private D2WContext d2wContext;
236
237 protected D2WContext d2wContext() {
238 if (d2wContext == null) {
239 d2wContext = new D2WContext();
240 }
241 return d2wContext;
242 }
243
244 synchronized public NSArray<String> inferFilterKeysForEntity(String entityName) {
245 EOEntity entity = EOModelGroup.defaultGroup().entityNamed(entityName);
246 d2wContext().setEntity(entity);
247 d2wContext().setTask(request().method());
248 return (NSArray<String>) d2wContext().inferValueForKey("displayPropertyKeys");
249 }
250
251 synchronized public NSArray<String> inferFilterKeysForPage(String page) {
252 d2wContext().setDynamicPage(page);
253 d2wContext().setTask(request().method());
254 return (NSArray<String>) d2wContext().inferValueForKey("displayPropertyKeys");
255 }
256
257 public ERXKeyFilter showFilter(String entityName) {
258 NSArray<String> keys = inferFilterKeysForEntity(entityName);
259 ERXKeyFilter filter = null;
260 if (keys != null && !keys.isEmpty()) {
261 filter = ERXKeyFilter.filterWithNone();
262 for (String key : keys) {
263 filter.include(new ERXKey<String>(key));
264 }
265 } else {
266 filter = ERXKeyFilter.filterWithAttributesAndToOneRelationships();
267 }
268 return filter;
269 }
270
271 {{/code}}
272
273 The D2WRule should be something like:
274
275 {{code}}
276
277 LHS: entity.name = 'Employee' and task = 'GET'
278 RHS: displayPropertyKeys = ('firstName', 'lastName', 'position')
279
280 {{/code}}