Project

General

Profile

CdmServerReadWriteRest » History » Version 3

Niels Hoffmann, 11/17/2010 04:16 PM

1 1 Ben Clark
2 3 Niels Hoffmann
3
4
5 1 Ben Clark
# Read / Write REST Services
6
7
8
This page provides some details about a _in progress_ implementation of read / write REST services for the CDM Server and briefly sketches a technical implementation of those services, particularly focussing on the gaps in the current implementation.
9
10
11
12
## Specification
13
14
15
### NOUNS
16
17
The nouns (objects) exposed in the first instance should be the major objects in the CDM. All objects that extend IdentifiableEntity will be exposed as a noun in the first instance. Many such objects are abstract and have subclasses, but these will not have separate nouns. Following the current CDM REST  Services V2, the names of the nouns shall be the package names in the main.
18
19
20
21
### VERBS
22
23
The GET verb is already exposed in the current CDM REST Services V2. To achieve full Read / Write functionality we will need to implement POST (Create), POST (Update) and DELETE. Some tweaking of GET is also neccessary.
24
25
26
Services
27
28
If the verb is _packagename_ and the unique identifier of the resource is _uuid_ then the services exposed should be
29
30
31
* `GET` /_packagename_ Retrieve a list of entities 
32
33
* `GET` /_packagename_/_uuid_ Find an entity
34
35
* `POST` /_packagename_  Create an resource (that will be subsequently found at /_packagename_/_uuid_ )
36
37
* `POST` /_packagename_/_uuid_ Update the resource at that location
38
39
* `DELETE` /_packagename_/_uuid_ Delete the resource at that location 
40
41
42
43
## Representation
44
45
46
The services shall read and write XML following the schema defined in the cdmlib-io package. Reading other representations is currently supported in e.g. spring via the jackson library but beyond the scope of this document. In order to succesfully represent error messages, we propose the creation of a CdmError base class, that can be extended for specific error types and marked up using JAXB annotations e.g.
47
48
49
~~~
50
<?xml version=”1.0” encoding=”UTF-8”?>
51
<InvalidObjectException xmlns=”http://etaxonomy.eu/cdm/1.0”>
52
  <Status>400</Status>
53
  <Code>Bad Request</Code>
54
  <Message>The Person object is not valid. The 'titleCache' field may not be null or empty.</Message>
55
</InvalidObjectException>
56
~~~
57
58
It is reccommended that such errors objects are placed within the general cdm namespace and the base CdmError resides in the cdmlib-model package to allow specialist errors to be subclassed within the cdmlib and thrown by cdmlib components (e.g. in the service layer) and caught and handled transparently in cdmserver.
59
60
61
62
## Implementation – Controller Layer
63
64
65
66
### Resource: /_packagename_
67
68
69
Represents the collection of objects of the type represented by _packagename_.
70
71
72
73
#### Method: `GET` 
74
75
76
Get some or all of the objects in that collection
77
78
79
80
##### Optional parameters:
81
82
83
* class: takes the fully qualified class name of a subclass of the type supported. Restricts objects returned to objects of that type. Optional.
84
85
* pageNumber: The offset in pageSize chunks from the beginning of the recordset. Optional. 
86
87
* pageSize: The maximum number of objects returned. Optional.
88
89
* sort: A property of the object to sort by, in the form propertyName_(asc|desc) e.g. titleCache_asc, created_desc, name.combinationAuthor.firstName_asc
90
91
92
93
##### Request Headers
94
95
96
* Accept: determined what format the representation should be rendered in: application/xml is supported, plus others such as text/json, text/html or application/xhtml might also be supported
97
98
* Accept-Language: Won't affect the representation per-se, but will affect the rendering of error messages
99
100
101
102
##### Status Code
103
104
105
* @200 OK@: on success
106
107
* @400 BAD REQUEST@: if one of the arguments is ill formed e.g. if the class parameter does not map to the correct class or if pageSize is negative
108
109
* @415 UNSUPPORTED MEDIA FORMAT@: if the client asks for an unsupported representation in the Accept header
110
111
112
113
##### Response
114
115
116
From the controller layer, a ModelAndView with a single object of type Pager<T> in it. This can be rendered using the same elements used within the DataSet object in cdmlib-io thus:
117
118
~~~
119
<?xml version=”1.0” encoding=”UTF-8”?>
120
<Taxa xmlns=”http://etaxonomy.eu/cdm/model/1.0” 
121
           xmlns:taxon=”http://etaxonomy.eu/cdm/model/taxon/1.0”
122
           pageSize=”...” pageNumber=”...” maxResults=”...”>
123
  <taxon:Taxon . . . />
124
  . . .
125
</cdm:Taxa>
126
~~~
127
128
These elements have had three additional attributes; pageSize, pageNumber, and maxResults added to them to allow for paging of the result set
129
130
131
Can be implemented thus:
132
133
134
~~~
135
@RequestMapping(value = “/packagename”, method = RequestMethod.GET)
136
public ModelAndView get(@RequestParam(value = "class", required = false) Class<? extends T> clazz,
137
                        @RequestParam(value = "pageNumber", required = false, defaultValue = DEFAULT_PAGE_NUMBER) Integer pageNumber,
138
                        @RequestParam(value = "pageSize", required = false, defaultValue = DEFAULT_PAGESIZE) Integer pageSize,
139
                        @RequestParam(value = "sort", required = false, defaultValue = DEFAULT_SORT) List<OrderHint> sort) {
140
    ModelAndView modelAndView = new ModelAndView();
141
    modelAndView.addObject(service.list(clazz, pageSize,                     
142
                                        pageNumber,sort,
143
                                        DEFAULT_INIT_STRATEGY));
144
    return modelAndView;
145
}
146
~~~
147
148
149
#### Method: `POST` 
150
151
152
Add a new object to that collection
153
154
155
156
##### Optional parameters:
157
158
None
159
160
161
162
##### Request Headers
163
164
165
* Accept: determined what format the representation should be rendered in: application/xml is supported, plus others such as text/json, text/html or application/xhtml might also be supported
166
167
* Accept-Language: Won't affect the representation per-se, but will affect the rendering of error messages
168
169
170
171
##### Request Body
172
173
An XML document conforming to the CDM Schema that maps to one of the object types that this resource supports.
174
175
176
177
##### Status Code
178
179
180
* @201 CREATED@: on success
181
182
* @400 BAD REQUEST@: if the XML document in the request body is ill formed, or if the object, when marshalled is invalid
183
184
* @401 UNAUTHORIZED@: It is expected that this resource will be protected by an authorization mechanism (e.g. HTTP Basic / HTTP Digest / TLS or other) that might be application specific and beyond the scope of this document. If the request is not authorized, this error code is returned.
185
186
* @409 CONFLICT@: If there is already a resource with the same UUID
187
188
* @415 UNSUPPORTED MEDIA FORMAT@: if the client asks for an unsupported representation in the Accept header
189
190
191
192
##### Response Headers
193
194
195
* Location: containing the location of the newly created resource
196
197
198
199
##### Response
200
201
202
An xml document representing the newly created resource (see below for GET /_packagename_/_uuid_).
203
204
205
206
##### Implementation
207
208
209
~~~
210
@ResponseStatus(value = HttpStatus.CREATED)
211
@RequestMapping(method = RequestMethod.POST)
212
public ModelAndView post(@RequestBody T t1) {
213
    T t2 = fieldMapper.map(t1,t1.getClass());
214
    collectionMapper.map(t1,t2);
215
    List<ConstraintViolation<T>> constraintViolations = validator.validate(t, Level2.class,Level3.class);
216
    if(!constraintViolations.isEmpty()) {
217
        throw new InvalidObjectException(t,constraintViolations);
218
    }
219
    service.saveOrUpdate(t2);
220
    ModelAndView modelAndView = new ModelAndView();
221
    modelAndView.addObject(t);
222
    return modelAndView;
223
}
224
225
~~~
226
227
The unmarshalling is handled by the @RequestBody annotation in conjunction with a MarshallingHttpMessageConverter. A custom IDResolver implementation is required to resolve entities that are referenced in the XML fragment – entities already found on the server will be found by this component and substituted for the relevant UUIDs. The fieldMapper.map(...) call maps the object onto a fresh object, filtering out fields that should be generated or set automatically – id, uuid, created, createdBy, updated, updatedBy. collectionMapper.map(...) filters the *-to-many properties of the object.
228
229
230
The object is then validated and, if it is valid, saved (the saveOrUpdate call updates the related objects rather than trying to create a new version of them). The response status is set to 201 CREATED (courtesy of the @ResponseStatus annotation). The Location header is set to the location of the newly created resource in the view. 
231
232
233
234
### Resource /_packagename_/_uuid_
235
236
237
This resource represents a single IdentifiableEntity
238
239
240
241 2 Ben Clark
#### Method: `GET` 
242
243
244 1 Ben Clark
Retrieves the resource
245
246
247 2 Ben Clark
248
##### Optional parameters:
249
250 1 Ben Clark
None
251
252
253
254 2 Ben Clark
##### Request Headers
255 1 Ben Clark
256 2 Ben Clark
 
257
* Accept: determined what format the representation should be rendered in: application/xml is supported, plus others such as text/json, text/html or application/xhtml might also be supported
258 1 Ben Clark
259 2 Ben Clark
* Accept-Language: Won't affect the representation per-se, but will affect the rendering of error messages
260 1 Ben Clark
261
262
263 2 Ben Clark
##### Status Code
264 1 Ben Clark
265
266 2 Ben Clark
* @200 OK@: on success
267 1 Ben Clark
268 2 Ben Clark
* @400 BAD REQUEST@: If the UUID is not well formed
269
270
* @404 NOT FOUND@: If a resource does not exist with that UUID
271
272
* @410 GONE@: If a resource did exist, but has been deleted (optional)
273
274
* @415 UNSUPPORTED MEDIA FORMAT@: if the client asks for an unsupported representation in the Accept header
275
276
277
278
##### Response
279
280
281 1 Ben Clark
The response is rendered as XML using the standard JAXB mappings present in on the model classes:
282
283 2 Ben Clark
~~~
284 1 Ben Clark
<?xml version=”1.0” encoding=”UTF-8”?>
285
<taxon:Taxon 
286
    uuid="urn-uuid-49361efb-aad6-448b-a316-f09005cee160"     
287
    isDoubtful="false"
288
    xmlns=”http://etaxonomy.eu/cdm/model/1.0” 
289
    xmlns:common=”http://etaxonomy.eu/cdm/model/common/1.0”
290
    xmlns:taxon=”http://etaxonomy.eu/cdm/model/taxon/1.0”
291
 >
292
  <common:TitleCache>Acherontia Laspeyres, 1809 sec cate-project.org</common:TitleCache>
293
  <common:ProtectedTitleCache>false</common:ProtectedTitleCache>
294
  <taxon:Name>urn-uuid-a8ab9567-7179-430d-a9fc-e3f22fe269f1</taxon:Name>
295
  <taxon:Sec>urn-uuid-b8cd17ba-32ea-4201-85f1-63d31526ccdb</taxon:Sec>
296
  <taxon:TaxonomicChildrenCount>1</taxon:TaxonomicChildrenCount>
297
  <taxon:RelationsFromThisTaxon>
298
    <taxon:FromThisTaxonRelationship uuid="urn-uuid-24f94660-c4e0-490c-8fb3-5cac20a426a4">
299
      <taxon:RelatedFrom>urn-uuid-49361efb-aad6-448b-a316-f09005cee160</taxon:RelatedFrom>
300
      <taxon:RelatedTo>urn-uuid-cc55da11-3bd7-4ca3-b1e2-6c5503eefea6</taxon:RelatedTo>
301
      <taxon:Type>urn-uuid-5799a150-1386-4bae-952e-e040d06f7485</taxon:Type>
302
    </taxon:FromThisTaxonRelationship>
303
  </taxon:RelationsFromThisTaxon>
304
  <taxon:Descriptions xsd:nil=”true”/>
305
</taxon:Taxon>
306 2 Ben Clark
~~~
307 1 Ben Clark
308
One issues is the incomplete initialization of model entities resulting in LazyInitializationExceptions' when the entities are serialized. This can be overcome by setting a custom JAXB accessor that returns null if an object is uninitialized (much like the current JSON processors). To distinguish between empty collections and uninitialized collections, collections of objects are declared to be “nillable” - an uninitialized collection looks like this:
309
310 2 Ben Clark
311 1 Ben Clark
<taxon:Descriptions xsd:nil=”true”/>
312
313
Wheras an empty collection looks like this
314
315
<taxon:Descriptions/>
316
317 2 Ben Clark
318 1 Ben Clark
Non-collections cannot be distinguished in this way, so all *-To-One relationships should be initialized by default (i.e. the initialization strategy should be “$”). Using nil elements to distinguish between empty and uninitialized collections has benefits on the POST side too, as is allows incomplete representations of objects to be updated.
319
320 2 Ben Clark
321 1 Ben Clark
Can be implemented thus
322
323
324 2 Ben Clark
~~~
325
@RequestMapping(method = RequestMethod.GET)
326 1 Ben Clark
public ModelAndView get(@PathVariable(value = "uuid") UUID uuid) {
327
    T t = service.load(uuid,DEFAULT_INIT_STRATEGY);
328
    if(t == null) {
329
        throw new ObjectNotFoundException(type,uuid);
330
    }
331
    ModelAndView modelAndView = new ModelAndView();
332
    modelAndView.addObject(t);
333
    return modelAndView;
334
}
335
336 2 Ben Clark
~~~
337 1 Ben Clark
338 2 Ben Clark
339
#### Method: `POST` 
340
341 1 Ben Clark
Updates the resource
342
343
344 2 Ben Clark
345
##### Optional parameters:
346
347 1 Ben Clark
None
348
349
350
351 2 Ben Clark
##### Request Headers
352 1 Ben Clark
353 2 Ben Clark
* Accept: determined what format the representation should be rendered in: application/xml is supported, plus others such as text/json, text/html or application/xhtml might also be supported
354 1 Ben Clark
355 2 Ben Clark
* Accept-Language: Won't affect the representation per-se, but will affect the rendering of error messages
356 1 Ben Clark
357
358
359 2 Ben Clark
##### Status Code
360 1 Ben Clark
361
362 2 Ben Clark
* @200 OK@: on success
363 1 Ben Clark
364 2 Ben Clark
* @400 BAD REQUEST@: If the UUID is not well formed or if the updated object is not valid
365 1 Ben Clark
366 2 Ben Clark
* @404 NOT FOUND@: If a resource does not exist with that UUID
367 1 Ben Clark
368 2 Ben Clark
* @401 UNAUTHORIZED@: It is expected that this resource will be protected by an authorization mechanism (e.g. HTTP Basic / HTTP Digest / TLS or other) that might be application specific and beyond the scope of this document. If the request is not authorized, this error code is returned.
369 1 Ben Clark
370 2 Ben Clark
* @410 GONE@: If a resource did exist, but has been deleted (optional)
371 1 Ben Clark
372 2 Ben Clark
* @415 UNSUPPORTED MEDIA FORMAT@: if the client asks for an unsupported representation in the Accept header
373 1 Ben Clark
374
375
376 2 Ben Clark
##### Response
377 1 Ben Clark
378
379 2 Ben Clark
See the response for `GET` 
380 1 Ben Clark
381
382
383 2 Ben Clark
##### Implementation
384 1 Ben Clark
385 2 Ben Clark
~~~
386
@RequestMapping(method = RequestMethod.POST)
387
public ModelAndView post(@PathVariable(value = "uuid") UUID uuid, @RequestBody T t2) {
388
    T t1 = service.load(uuid,DEFAULT_INIT_STRATEGY);
389
    if(t1 == null) {
390
        throw new ObjectNotFoundException(type,uuid);
391 1 Ben Clark
    }
392 2 Ben Clark
    fieldMapper.map(t1,t2);
393
    collectionsMapper.map(t1,t2);
394
    List<ConstraintViolation<T>> constraintViolations = validator.validate(t1, Level2.class,Level3.class);
395
    if(!constraintViolations.isEmpty()) {
396
        throw new InvalidObjectException(t,constraintViolations);
397
    }
398 1 Ben Clark
    service.saveOrUpdate(t1);
399
    ModelAndView modelAndView = new ModelAndView();
400
    modelAndView.addObject(t1);
401
    return modelAndView;
402
}
403 2 Ben Clark
~~~
404 1 Ben Clark
405 2 Ben Clark
This method combines the approach used to `GET` /_packagename_/_uuid_ and to `POST` /_packagename_. However, in this case the mapping is from the new version to transient object.
406 1 Ben Clark
407
408 2 Ben Clark
409
#### Method: `DELETE` 
410
411
412 1 Ben Clark
Deletes the resource
413
414
415 2 Ben Clark
416
##### Optional parameters:
417
418
419 1 Ben Clark
None
420
421
422
423 2 Ben Clark
##### Request Headers
424 1 Ben Clark
425
426 2 Ben Clark
* Accept: determined what format the representation should be rendered in: application/xml is supported, plus others such as text/json, text/html or application/xhtml might also be supported
427 1 Ben Clark
428 2 Ben Clark
* Accept-Language: Won't affect the representation per-se, but will affect the rendering of error messages
429 1 Ben Clark
430
431
432 2 Ben Clark
##### Status Code
433 1 Ben Clark
434
435 2 Ben Clark
* @200 OK@: on success
436 1 Ben Clark
437 2 Ben Clark
* @400 BAD REQUEST@: If the UUID is not well formed
438 1 Ben Clark
439 2 Ben Clark
* @404 NOT FOUND@: If a resource does not exist with that UUID
440 1 Ben Clark
441 2 Ben Clark
* @401 UNAUTHORIZED@: It is expected that this resource will be protected by an authorization mechanism (e.g. HTTP Basic / HTTP Digest / TLS or other) that might be application specific and beyond the scope of this document. If the request is not authorized, this error code is returned.
442 1 Ben Clark
443 2 Ben Clark
* @410 GONE@: If a resource did exist, but has already been deleted (optional)
444 1 Ben Clark
445 2 Ben Clark
* @415 UNSUPPORTED MEDIA FORMAT@: if the client asks for an unsupported representation in the Accept header
446 1 Ben Clark
447
448
449 2 Ben Clark
##### Response
450 1 Ben Clark
451
452 2 Ben Clark
Although Clients may not expect any content in the response body, it is recommended to redirect to the parent resource (/_packagename_) with a message indicating that a resource has been deleted.
453 1 Ben Clark
454
455 2 Ben Clark
456
##### Implementation
457
458
459
~~~
460
@RequestMapping(method = RequestMethod.DELETE)
461
public ModelAndView post(@PathVariable(value = "uuid") UUID uuid) {
462
    T t = service.load(uuid,DEFAULT_INIT_STRATEGY);
463
    if(t1 == null) {
464
        throw new ObjectNotFoundException(type,uuid);
465
    }
466
    service.delete(t);
467
    ModelAndView modelAndView = new ModelAndView(“redirect:/{packagename}”);
468
    return modelAndView;
469 1 Ben Clark
}
470 2 Ben Clark
~~~
471 1 Ben Clark
472
473 2 Ben Clark
## Changes
474 1 Ben Clark
475
476 2 Ben Clark
### cdmlib-model
477 1 Ben Clark
478 2 Ben Clark
 1.	Creation of a custom AccessorFactory implementation in eu.etaxonomy.cdm.jaxb
479 1 Ben Clark
480 2 Ben Clark
 2.	Creation of a Hibernate safe Accessor implentation in eu.etaxonomy.cdm.jaxb
481 1 Ben Clark
482 2 Ben Clark
 3.	Addition of @XmlAccessorFactory annotations in all package-info.java files in cdmlib-model
483 1 Ben Clark
484 2 Ben Clark
 4.	Addition of “nillable = true” to most @XmlElementWrapper annotations
485 1 Ben Clark
486 2 Ben Clark
 5.	Creation of a CdmError base class in cdmlib-model
487 1 Ben Clark
488
489 2 Ben Clark
## cdmlib-io
490
 
491
 1.	Addition of “nillable = true” to equivalent xml elements
492 1 Ben Clark
493 2 Ben Clark
 2.	Creation of a CdmIDResolver implentation in eu.etaxonomy.cdm.io.jaxb
494 1 Ben Clark
495 2 Ben Clark
 3.	Modification of eu.etaxonomy.cdm.io.jaxb.DataSet and creation of extra elements to incorporate pageSize, pageNumber and maxResults attributes
496 1 Ben Clark
497
498 2 Ben Clark
## cdmlib-remote
499
500
 1.	Creation of an XmlView class to render cdm entities as CDM XML
501
502
 2.	Creation of dozer mapper custom converters to merge collections if they are not null.
503
504
 3.	Creation of dozer mapping files to map cdm objects.
505
506 1 Ben Clark
507
All of the following changes are low-risk because they do not change the behaviour of existing code. The custom accessor factory must be explicitly enabled for to have an effect.