Wiki source code of Extensible API for accessing structured data
Last modified by Vincent Massol on 2024/11/19 16:14
Show last authors
| author | version | line-number | content |
|---|---|---|---|
| 1 | This API has been implemented as experimental prototypes within the [[UCF>>http://www.ubiquitus-content-framework.fr/]] project: | ||
| 2 | * on the server: see [[Example code in Velocity>>http://extensions.xwiki.org/xwiki/bin/view/Extension/Structured+data+access+API]] | ||
| 3 | * on the client: see [[Example code in JavaScript>>http://extensions.xwiki.org/xwiki/bin/view/Extension/Structured+data+access+JS+API]] | ||
| 4 | |||
| 5 | = Goal = | ||
| 6 | |||
| 7 | Support a high-level API for accessing data from both server and client-side. This is a need that starts to appear in several research projects. Since this is interesting for XWiki as project we put this design idea here for discussion. | ||
| 8 | |||
| 9 | == High-level requirements == | ||
| 10 | |||
| 11 | * There must be a unified way of using the API from Javascript, Velocity or Groovy, i.e., an homogeneous method names and signature. | ||
| 12 | * A "general" API for storing and retrieving elements of the application domain must always be present, i.e., a set of standard methods that are used for performing CRUD operations. | ||
| 13 | * The application editor must be able to extend the "general" API with specific business logic that is specific to the application. | ||
| 14 | * The API - the "general" and the "extended" ones - should be accessible also via HTTP (i.e., it must be exposed also as a REST API) | ||
| 15 | * The API should be easy to use for people writing extensions. | ||
| 16 | |||
| 17 | == Use Cases == | ||
| 18 | |||
| 19 | * uc1 - Get the last 5 objects of the current application (XClass), sorted by a specific field. | ||
| 20 | * uc2 - Get objects of a given application (we know the name of it) using an advanced query | ||
| 21 | * uc3 - Create a new object of my application's type and store it | ||
| 22 | * uc4 - Get an object, alter a field then store it | ||
| 23 | * uc5 - **medium priority** - Add functions to an app, this implies the "app" object is not purely data. | ||
| 24 | * uc6 - Understand what are the field values in an application item. | ||
| 25 | * uc7 - **low priority** - Get a historical version of an item | ||
| 26 | * uc8 - Integrate smoothly with AngularJS - this means the javascript representation of the objects should be iterable as basic map | ||
| 27 | * uc9 - **low priority** - Change the content and title of an XWikiDocument without any AWM project | ||
| 28 | |||
| 29 | == General API == | ||
| 30 | |||
| 31 | This API is used to have a generic CRUD interface with the application, and must be automatically available for every AWM application. | ||
| 32 | |||
| 33 | === EntryPoint === | ||
| 34 | |||
| 35 | * Velocity: {{code language="none"}}$services.xapp{{/code}} | ||
| 36 | * Groovy: {{code language="java"}}services.xapp{{/code}} | ||
| 37 | * Javascript: {{code language="javascript"}}require(['xapp'], function (XApp) { });{{/code}} | ||
| 38 | |||
| 39 | === **##xapp.getApp##** === | ||
| 40 | |||
| 41 | |= Parameters |((( | ||
| 42 | * ##id##: The application id (e.g., the one used in AWM) | ||
| 43 | ))) | ||
| 44 | |= Returns | An object containing all the methods for accessing applications data (see next sections) | ||
| 45 | |||
| 46 | ---- | ||
| 47 | |||
| 48 | === **##xapp.getCurrent##** === | ||
| 49 | |||
| 50 | |= Parameters | (none) | ||
| 51 | |= Returns | An object containing all the methods for accessing applications data (see next sections) | ||
| 52 | |= Remarks | xapp.current and xapp.getCurrent() should both work in javascript and in velocity. | ||
| 53 | |||
| 54 | ---- | ||
| 55 | |||
| 56 | === **##application.getItem##** === | ||
| 57 | |||
| 58 | |= Parameters |((( | ||
| 59 | * ##id## The item id | ||
| 60 | ))) | ||
| 61 | |= Returns | An object sutable for storage using ##storeItem## | ||
| 62 | |= REST binding |((( | ||
| 63 | * **Request:** ##GET .../{applicationId}/items## | ||
| 64 | ** ##options## parameters can be individually specified as query string paratemeters (e.g. ##.../{applicationId}/items?limit=42##) | ||
| 65 | * **Response:** A JSON serialization of the return value + appropriate status code | ||
| 66 | ))) | ||
| 67 | |= Remarks | In the event that an object does not exist, an empty "new" object should be returned, matching the behavior of XWiki.getDocument() | ||
| 68 | |||
| 69 | ---- | ||
| 70 | |||
| 71 | === **##application.getItems##** === | ||
| 72 | |||
| 73 | |= Parameters |((( | ||
| 74 | * ##options##: A map containing a set of options that can be used for customizing the items fetching. Some of them could be: | ||
| 75 | ** ##offset##: The offset from where to start fetching the items (pagination) | ||
| 76 | ** ##limit##: The number of items to be returned (pagination) | ||
| 77 | ** ##query##: A kind of simple query specification for filtering | ||
| 78 | ))) | ||
| 79 | |= Returns |((( | ||
| 80 | An array of maps, each one containing the following fields: | ||
| 81 | |||
| 82 | * ##id## The item id | ||
| 83 | * ##data## The serialized item data using a standard schema for XWikiClasses (see next section) | ||
| 84 | ))) | ||
| 85 | |= REST binding |((( | ||
| 86 | * **Request:** ##GET .../{applicationId}/items## | ||
| 87 | ** ##options## parameters can be individually specified as query string paratemeters (e.g. ##.../{applicationId}/items?limit=42##) | ||
| 88 | * **Response:** A JSON serialization of the return value + appropriate status code | ||
| 89 | ))) | ||
| 90 | |= Remarks |((( | ||
| 91 | * This will likely prove to be a difficult API function to implement and development here needs to be monitored. | ||
| 92 | ))) | ||
| 93 | |||
| 94 | ---- | ||
| 95 | |||
| 96 | === **##application.storeItem##** === | ||
| 97 | |||
| 98 | |= Parameters |##item## the item to be stored | ||
| 99 | |= Returns | An error if there is any, otherwise null/undefined. | ||
| 100 | |= REST binding |((( | ||
| 101 | * **Request:** ##PUT .../{applicationId}/items/{itemId}## | ||
| 102 | * **Request body:** A JSON serialization of the item data using a standard schema for XWikiClasses (see next section) | ||
| 103 | * **Response:** A JSON serialization of the return value + appropriate status code | ||
| 104 | ))) | ||
| 105 | |||
| 106 | ---- | ||
| 107 | |||
| 108 | === **##application.deleteItem##** === | ||
| 109 | |||
| 110 | |= Parameters |((( | ||
| 111 | * ##id## The item id | ||
| 112 | ))) | ||
| 113 | |= Returns |((( | ||
| 114 | * In case of success: An empty map | ||
| 115 | * In case of error: A map containing the following fields: | ||
| 116 | ** ##error## The error description | ||
| 117 | ))) | ||
| 118 | |= REST binding |((( | ||
| 119 | * **Request:** ##DELETE .../{applicationId}/items/{itemId}## | ||
| 120 | ** ##options## parameters can be individually specified as query string paratemeters (e.g. ##.../{applicationId}/items?limit=42##) | ||
| 121 | * **Response:** A JSON serialization of the return value + appropriate status code | ||
| 122 | ))) | ||
| 123 | |||
| 124 | ---- | ||
| 125 | |||
| 126 | === **##application.getSchema##** === | ||
| 127 | |||
| 128 | |= Parameters | None | ||
| 129 | |= Returns |((( | ||
| 130 | A map with a key for every attribute that define the application item. The value associated to each key is a map that describe the type of the corresponding attribute. | ||
| 131 | ))) | ||
| 132 | |= REST binding |((( | ||
| 133 | * **Request:** ##GET .../{applicationId}/schema## | ||
| 134 | * **Response:** A JSON serialization of the return value + appropriate status code | ||
| 135 | |||
| 136 | |= Remarks | The way for describing the schema migh be tricky. This is however needed in order to retrieve important information about the item data (e.g., which values are admissible in a static list, what are the format constraints, and so on) | ||
| 137 | ))) | ||
| 138 | |||
| 139 | == Serialization == | ||
| 140 | |||
| 141 | === Schema serialization === | ||
| 142 | |||
| 143 | This can be a JSON/Map version that mimicks what is already defined in the REST API (e.g., http://localhost:8080/xwiki/rest/wikis/xwiki/classes/XWiki.XWikiUsers) | ||
| 144 | |||
| 145 | In particlar this serialization should provide for every property that is defined in a class, all the information that can be relevant for a client. An important aspect, for example, is to communicate to clients what are the admissible values for list properties for validation purposes. Database lists could pose an issue here, since the set of admissible values is dynamic. | ||
| 146 | |||
| 147 | {{code language="javascript"}} | ||
| 148 | { | ||
| 149 | 'name' : { | ||
| 150 | 'type': 'String' | ||
| 151 | }, | ||
| 152 | 'genre' : { | ||
| 153 | 'type': 'Enum', | ||
| 154 | 'values': ['action', 'drama', 'scifi'] | ||
| 155 | }, | ||
| 156 | 'releaseDate' : { | ||
| 157 | 'type': 'Timestamp', | ||
| 158 | } | ||
| 159 | } | ||
| 160 | {{/code}} | ||
| 161 | |||
| 162 | === Item data serialization === | ||
| 163 | |||
| 164 | This can be a JSON/Map that associate every item property to its desired value. The schema gives information about what kind of types must be used as values. We will leave it to the implementor to determine how best to represent the items in Velocity, Groovy and Javascript with the constraint that they must provide ##toString()## methods which self-serialize to readable JSON and that in Javascript, the item's elements must be enumerable and accessable such that it can be easily integrated with AngularJS (as if they were plain JSON objects). | ||
| 165 | |||
| 166 | {{code language="javascript"}} | ||
| 167 | { | ||
| 168 | 'name': 'The Imitation Game', | ||
| 169 | 'genre': 'drama', | ||
| 170 | 'releaseDate': 1419462000000 | ||
| 171 | } | ||
| 172 | {{/code}} | ||
| 173 | |||
| 174 | == Examples of API usage == | ||
| 175 | |||
| 176 | === Velocity === | ||
| 177 | |||
| 178 | {{code language="velocity"}} | ||
| 179 | |||
| 180 | #set($app = $services.xapp.current) | ||
| 181 | |||
| 182 | ## Get some items | ||
| 183 | #set($movies = $app.getItems({"limit": 50}) | ||
| 184 | #foreach($movie in $movies) | ||
| 185 | | $movie.name | $movie.genre | $datetool.toDate($movie.releaseDate) | ||
| 186 | #end | ||
| 187 | |||
| 188 | ## Create a new item or alter an existing one, the semantics are identical | ||
| 189 | #set($foo = $app.getItem("foo")) | ||
| 190 | $foo.set('genre', 'drama') | ||
| 191 | $foo.set('releaseDate', $datetool.getSystemTime()) | ||
| 192 | $app.storeItem($foo) | ||
| 193 | |||
| 194 | {{/code}} | ||
| 195 | |||
| 196 | === Javascript === | ||
| 197 | |||
| 198 | {{code language="javascript"}} | ||
| 199 | require(['xapp'], function (XApp) { | ||
| 200 | var app = XApp.current; | ||
| 201 | |||
| 202 | /* Asynchronous api is identical to nodejs */ | ||
| 203 | app.getItems({limit: 50}, function (err, result) { | ||
| 204 | if (err) { throw err; } // Or handle somehow else... | ||
| 205 | for(var i = 0; i < result.length; i++) { | ||
| 206 | //Do something with result[i].data.name, etc. | ||
| 207 | } | ||
| 208 | }); | ||
| 209 | |||
| 210 | app.getItem("foo", function (err, foo) { | ||
| 211 | if (err) { throw err; } | ||
| 212 | foo.set('genre', 'drama'); | ||
| 213 | foo.set('releaseDate', (new Date()).getTime()); | ||
| 214 | app.storeItem(foo, function (err) { | ||
| 215 | if (err) { console.log('something went wrong ! ' + err.stack); } | ||
| 216 | }); | ||
| 217 | }); | ||
| 218 | }); | ||
| 219 | |||
| 220 | {{/code}} | ||
| 221 | |||
| 222 | == Open issues == | ||
| 223 | |||
| 224 | == Adding extended API methods == | ||
| 225 | |||
| 226 | Extended API methods should be used to provide clients with specific ways for getting information related to the application domain. These methods can perform very specific tasks, retrieve information using complex queries and return it in more convenient formats (e.g., movies.getMovieBudgetForYear(2014)) | ||
| 227 | |||
| 228 | There are several issues linked to this: | ||
| 229 | |||
| 230 | * How to allow application developer to declare these extended API methods, and provide their implementation. | ||
| 231 | * How to derive the API object that can be used by clients in the different programming environments. | ||
| 232 | * How to derive the REST API associated to it so that Javascript Client can access the server API | ||
| 233 | |||
| 234 | A simple approach is that the developer writes everything (i.e., the Groovy code that is published as an XWiki service accessible via Groovy and Velocity scripts, the JavaScript Code that will call the REST API, and the Groovy code that will handle the REST endpoint) | ||
| 235 | |||
| 236 | This sounds like a lot of work. Maybe by declaratively defining the structure of the API (i.e., the parameters, and the return values) a subset of the code could be automatically generated. For example, a developer would provide just the implementation of the Groovy method - using some constraints - and when published the system will automatically create the corresponding XWiki Service, the REST Endpoint and the Javascript for interacting with it. However this sound like a task that could take a lot of time. | ||
| 237 | |||
| 238 | == Javascript asynchronous semantics == | ||
| 239 | |||
| 240 | Javascript implementation (which is a wrapper to the REST API) must be asynchronous. This can influence a bit the way things are implemented. |