PageRenderTime 53ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

HTML | 205 lines | 187 code | 18 blank | 0 comment | 0 complexity | fd514baf936acbc1f885d28e78569913 MD5 | raw file
  1. <html>
  2. <body>
  3. <h1>JavaRESTAdaptor (Experimental Still!)</h1>
  4. <h2>Overview</h2>
  5. <p>
  6. JavaRESTAdaptor is an EOF adaptor implementation on top of a RESTful web service, similar to ActiveResource
  7. in Rails. Currently JavaRESTAdaptor is read-only, but should be sufficient for consuming RESTful services.
  8. </p>
  9. <h2>Usage</h2>
  10. <p>
  11. The usage of JavaRESTAdaptor is best explained in an example. This example is adapted from an ActiveResource
  12. tutorial, but it provides several examples of common techniques. The service we're going to communicate with
  13. is a Beast installation. Beast is a simple Rails forum application. We'll use as
  14. our example service provider.
  15. </p>
  16. <h2>Creating the Model</h2>
  17. <ol>
  18. <li>Create a Wonder project.</li>
  19. <li>Create an EOModel (of any name) and choose "None" from the list of adaptors
  20. in the wizard.</li>
  21. <li>In the default database config (assuming you're using a recent WOLips), select the
  22. prototype "EORESTPrototypes" and select the Adaptor "REST".</li>
  23. <li>In the URL, enter "" and leave the username and password
  24. blank (currently JavaRESTAdaptor does not support HTTP authentication).</li>
  25. </ol>
  26. <h2>Creating Entites</h2>
  27. <p>
  28. Beast provides four entities that we want to interact with: Forum, Topic, Post, User.
  29. </p>
  30. <ol>
  31. <li>Create an entity named Forum.</li>
  32. <li>
  33. The "table name" of Forum will tell JavaRESTAdaptor the URL extensions and XML tags that can be used to
  34. access the entity. In the case of Forum, the XML tag name comes in two variants -- a singular and a plural
  35. form. For the Forum entity, set the table name to "forum,forums".
  36. <p>
  37. Table name for JavaRESTAdaptor comes in several forms:
  38. <ul>
  39. <li>If you do not set a table name, JavaRESTAdaptor will simply use the entity name as the singular form,
  40. and use ERXLocalizer to generate a plural form.</li>
  41. <li>If you only set a single value (i.e. "forum"), the adaptor will use that as the singular form and
  42. use ERXLocalizer to generate a plural form.</li>
  43. <li>If you specify two values (i.e. "forum,forums"), the adaptor will use the first value as the singular
  44. form and the second value as the plural form.</li>
  45. <li>Additionally, as you will see later, you can specify a set of possibly URL prefixes to use to access
  46. the entity. When you do not specify a URL prefix (as in our Forum example), the adaptor will assume that
  47. it can go to /[plural form].xml and /[plural form]/[id].xml to retrieve objects of this entity type.</li>
  48. </ul>
  49. </p>
  50. </li>
  51. <li>We need to determine the attributes of Forum, so open in your browser and
  52. view the source. ActiveRecord (or more importantly Ruby) can one-up us here, because they automatically
  53. determine the attributes of records based on queries to the service. We ultimately want to generate Java code,
  54. so we need to create an EOModel with attributes.</li>
  55. <li>Forum has the attributes: description, description-html, id, name, position, posts-count, and topics-count.
  56. Create attributes in your entity with the names converted to camelCase (description-html becomes descriptionHtml,
  57. etc). For each attribute, select a representative prototype, and set the external name to be the original name
  58. from the XML. For example, descriptionHtml is a "varcharLarge" prototype (we could pick one with a smaller
  59. restriction if we want to limit the values) and its external name is "description-html". postsCount is an
  60. "intNumber" and its external name is "posts-count". Do this for the rest of the attributes of Forum.</li>
  61. <li>In Beast, forums contain topics, so let's create the Topic entity. Topics present an interesting issue
  62. with modeling, because Beast does not allow you to select topics from the top level, rather you can only
  63. select topics through a Forum. For example, there is no /topics/1.xml there is only /forums/1/topics/1.xml.
  64. This oddity does cause some problems for EOF, because EOF expects to be able to fetch objects
  65. with only a primary key. JavaRESTAdaptor provides the ability to model fetching these entities, but you
  66. should be careful of places where EOF may cause faults for individual objects. You may find it safer to
  67. model relationships to entities of this type in code with fetch specs, or if you're using the Wonder eogen
  68. templates, traverse these kind of relationships with forum.topics(null, true), which will force a refetch of
  69. the entities.</li>
  70. <li>Request to see an example of what topics look like. Follow the
  71. same procedure as for Forum, creating attributes from the XML. For dates, use the prototype "dateTime". For
  72. foreign keys, use the attribute "id" and mark them as non-class attributes.</li>
  73. <li>Now to address the URL issue for Topics. Because there is no /topics.xml, we need to tell JavaRESTAdaptor
  74. how to find topic objects. Edit the Topic entity and set the table name to "/forums/[forumID]/topics,topic,topics".
  75. This says that to fetch a topic, you must use the URL "/forums/[forumID]/topics" where forumID is a variable
  76. that corresponds to the Topic attribute of the same name. Any time a fetch is performed on a Topic, a forumID
  77. must appear in the qualifier or an exception will be thrown from JavaRESTAdaptor. When traversing a to-many
  78. relationship from a Forum to a Topic, this happens automatically inside of EOF. However, if you are trying to
  79. fetch a particular topic, you must provide its forum in your qualifier to prevent an error.</li>
  80. <li>Now create the Post entity. Posts are similar to Topics in that you cannot access them from the top level
  81. of the service, but they can be accessed in several different ways. It turns out that you can get posts for
  82. a user, for a forum, or for a topic (which requires a forum). JavaRESTAdaptor allows you to model these methods
  83. as alternative URLs in the table name by separating the URLs with a pipe. For instance, the table name for
  84. Post is "/users/[userID]/posts|/forums/[forumID]/posts|/forums/[forumID]/topics/[topicID]/posts,post,posts". I
  85. admit this is ugly, and ideally these definitions would be defined on the relationships instead of the entity,
  86. but unfortunately at the adaptor level, the EORelationship that was used to fetch the objects is long gone. What
  87. ERREST does in this case is that it looks at the qualifier provided and finds the URL that matches the most
  88. variables from your qualifier keys and uses that as the fetching URL. For instance, if your qualifier only
  89. has a userID in it, the /users/[userID]/posts URL will be used. However, if you qualifier has a forumID and
  90. a topicID in it, the /forums/[forumID]/topics/[topicID]/posts will be used. An exception will be thrown if
  91. a suitable URL cannot be found given the provided qualifier.</li>
  92. <li>Lastly, create your User entity. Nothing fancy here, and top-level fetches are allowed, so take a look
  93. at to create your attributes, and use the table name "user,users" on this
  94. entity.</li>
  95. </ol>
  96. <h2>Relationships</h2>
  97. <p>
  98. You have created all of the entities for reading information from Beast. You can now create the
  99. relationships between these entities. For entities that can be fetched from the top level, or when modeling
  100. relationships that will provide enough context for the fetch, relationships can be modeled just like a
  101. normal eomodel.
  102. </p>
  103. <p>
  104. For instance, from post.user() is a completely normal to-one relationship because User can be
  105. fetched from the top level, and post will provide the "id" necessary to execute the fetch. Similarly,
  106. forum.topics() is a normal to-many relationship, because while Topic does not have a topic level fetch URL,
  107. traversing the relationship from Forum will provide the "forumID" necessary to complete the fetch.
  108. </p>
  109. <p>
  110. The "problem child" relationships are, for instance, topic.posts(). Traversing the posts relationship on Topic
  111. will only provide a topicID. Unfortunately Posts requires a topicID AND a forumID. It turns out that this
  112. can be modeled by putting both topicID and forumID in the joins of the to-many relationship definition. The
  113. downside of this is that it will not be symmetric with the other side of the relationship, and will not be
  114. updated automatically when write ability is added to JavaRESTAdaptor. The other problem is that, while this
  115. technique works on to-many relationships, it does not work on to-one relationships. EOF does not allow
  116. a to-one relationship to be defined with a join on anything expect a primary key attribute.
  117. </p>
  118. In this situation, you will need to construct the relationship using a fetch spec, and not using normal
  119. relationship definitions in the model. Your fetch spec will work just like a normal fetch spec, but you must
  120. provide all variables necessary to complete a fetch. As an example, if you want to fetch a particular topic,
  121. you must fetch it like:
  122. </p>
  123. <p>
  124. Topic singleTopic = Topic.fetchRequiredTopic(editingContext, Topic.FORUM.eq(aForum).and(ERXQ.equals("id", 633)));
  125. </p>
  126. <p>
  127. or
  128. </p>
  129. <p>
  130. aForum.topics(ERXQ.equals("id", 633), true)
  131. </p>
  132. <p>
  133. Note that JavaRESTAdaptor allows fetching on non-class attributes.
  134. </p>
  135. <h2>Fetching Notes</h2>
  136. <p>
  137. When fetching against the remote service, only topic level EOKeyValueQualifiers, EOAndQualifiers, and
  138. EOOrQualifiers with a single entry will be processed for constructing the remote URL. Complex qualifiers
  139. can be constructed, but they will be evaluated in-memory against the most restrictive URL that could
  140. be constructed given your qualifier attributes.
  141. </p>
  142. <h2>Examples</h2>
  143. <pre>
  144. EOEditingContext editingContext = ERXEC.newEditingContext();
  145. NSArray<Forum> forums = Forum.fetchAllForums(editingContext);
  146. System.out.println("Application.Application: Fetching all forums");
  147. for (Forum forum : forums) {
  148. System.out.println("Application.Application: " + + ", " + forum.postsCount() + ", " + forum.topicsCount());
  149. }
  150. System.out.println("Application.Application: Fetching forum w/ PK");
  151. Forum singleForum = (Forum) EOUtilities.objectWithPrimaryKeyValue(editingContext, Forum.ENTITY_NAME, "3");
  152. System.out.println("Application.Application: " +;
  153. System.out.println("Application.Application: Fetching topics for " +;
  154. NSArray<Topic> topics = singleForum.topics();
  155. for (Topic topic : topics) {
  156. System.out.println("Application.Application: " + topic.title() + " created " + topic.createdAt());
  157. }
  158. System.out.println("Application.Application: Fetching posts for forum");
  159. NSArray<Post> forumPosts = singleForum.posts();
  160. for (Post post : forumPosts) {
  161. System.out.println("Application.Application: " + post.createdAt());
  162. }
  163. System.out.println("Application.Application: Refetching single topic w/ PK");
  164. Topic singleTopic = Topic.fetchRequiredTopic(editingContext, Topic.FORUM.eq(singleForum).and(ERXQ.equals("id", 633)));
  165. System.out.println("Application.Application: " + singleTopic.title());
  166. System.out.println("Application.Application: Fetching topic user");
  167. User user = singleTopic.user();
  168. System.out.println("Application.Application: " + user.displayName());
  169. System.out.println("Application.Application: Fetching posts for topic (composite pk, which is kind of interesting)");
  170. NSArray<Post> topicPosts = singleTopic.posts();
  171. for (Post post : topicPosts) {
  172. System.out.println("Application.Application: " + post.createdAt());
  173. }
  174. Post randomPost = topicPosts.lastObject();
  175. System.out.println("Application.Application: Fetching the topic for a post (this will break if topic is not already fetched)");
  176. System.out.println("Application.Application: " + randomPost.topic().title());
  177. System.out.println("Application.Application: Fetch author of post");
  178. User postedByUser = randomPost.user();
  179. System.out.println("Application.Application: " + postedByUser.displayName());
  180. System.out.println("Application.Application: Fetching posts by author");
  181. NSArray<Post> userPosts = postedByUser.posts();
  182. for (Post post : userPosts) {
  183. System.out.println("Application.Application: " + post.createdAt());
  184. }
  185. </pre>
  186. </body>
  187. </html>