Project

General

Profile

CdmServerReadWriteRest » History » Version 1

Ben Clark, 02/10/2010 09:32 AM

1 1 Ben Clark
2
# Read / Write REST Services
3
4
5
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.
6
7
8
9
## Specification
10
11
12
### NOUNS
13
14
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.
15
16
17
18
### VERBS
19
20
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.
21
22
23
Services
24
25
If the verb is _packagename_ and the unique identifier of the resource is _uuid_ then the services exposed should be
26
27
28
* `GET` /_packagename_ Retrieve a list of entities 
29
30
* `GET` /_packagename_/_uuid_ Find an entity
31
32
* `POST` /_packagename_  Create an resource (that will be subsequently found at /_packagename_/_uuid_ )
33
34
* `POST` /_packagename_/_uuid_ Update the resource at that location
35
36
* `DELETE` /_packagename_/_uuid_ Delete the resource at that location 
37
38
39
40
## Representation
41
42
43
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.
44
45
46
~~~
47
<?xml version=”1.0” encoding=”UTF-8”?>
48
<InvalidObjectException xmlns=”http://etaxonomy.eu/cdm/1.0”>
49
  <Status>400</Status>
50
  <Code>Bad Request</Code>
51
  <Message>The Person object is not valid. The 'titleCache' field may not be null or empty.</Message>
52
</InvalidObjectException>
53
~~~
54
55
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.
56
57
58
59
## Implementation – Controller Layer
60
61
62
63
### Resource: /_packagename_
64
65
66
Represents the collection of objects of the type represented by _packagename_.
67
68
69
70
#### Method: `GET` 
71
72
73
Get some or all of the objects in that collection
74
75
76
77
##### Optional parameters:
78
79
80
* class: takes the fully qualified class name of a subclass of the type supported. Restricts objects returned to objects of that type. Optional.
81
82
* pageNumber: The offset in pageSize chunks from the beginning of the recordset. Optional. 
83
84
* pageSize: The maximum number of objects returned. Optional.
85
86
* 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
87
88
89
90
##### Request Headers
91
92
93
* 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
94
95
* Accept-Language: Won't affect the representation per-se, but will affect the rendering of error messages
96
97
98
99
##### Status Code
100
101
102
* @200 OK@: on success
103
104
* @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
105
106
* @415 UNSUPPORTED MEDIA FORMAT@: if the client asks for an unsupported representation in the Accept header
107
108
109
110
##### Response
111
112
113
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:
114
115
~~~
116
<?xml version=”1.0” encoding=”UTF-8”?>
117
<Taxa xmlns=”http://etaxonomy.eu/cdm/model/1.0” 
118
           xmlns:taxon=”http://etaxonomy.eu/cdm/model/taxon/1.0”
119
           pageSize=”...” pageNumber=”...” maxResults=”...”>
120
  <taxon:Taxon . . . />
121
  . . .
122
</cdm:Taxa>
123
~~~
124
125
These elements have had three additional attributes; pageSize, pageNumber, and maxResults added to them to allow for paging of the result set
126
127
128
Can be implemented thus:
129
130
131
~~~
132
@RequestMapping(value = “/packagename”, method = RequestMethod.GET)
133
public ModelAndView get(@RequestParam(value = "class", required = false) Class<? extends T> clazz,
134
                        @RequestParam(value = "pageNumber", required = false, defaultValue = DEFAULT_PAGE_NUMBER) Integer pageNumber,
135
                        @RequestParam(value = "pageSize", required = false, defaultValue = DEFAULT_PAGESIZE) Integer pageSize,
136
                        @RequestParam(value = "sort", required = false, defaultValue = DEFAULT_SORT) List<OrderHint> sort) {
137
    ModelAndView modelAndView = new ModelAndView();
138
    modelAndView.addObject(service.list(clazz, pageSize,                     
139
                                        pageNumber,sort,
140
                                        DEFAULT_INIT_STRATEGY));
141
    return modelAndView;
142
}
143
~~~
144
145
146
#### Method: `POST` 
147
148
149
Add a new object to that collection
150
151
152
153
##### Optional parameters:
154
155
None
156
157
158
159
##### Request Headers
160
161
162
* 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
163
164
* Accept-Language: Won't affect the representation per-se, but will affect the rendering of error messages
165
166
167
168
##### Request Body
169
170
An XML document conforming to the CDM Schema that maps to one of the object types that this resource supports.
171
172
173
174
##### Status Code
175
176
177
* @201 CREATED@: on success
178
179
* @400 BAD REQUEST@: if the XML document in the request body is ill formed, or if the object, when marshalled is invalid
180
181
* @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.
182
183
* @409 CONFLICT@: If there is already a resource with the same UUID
184
185
* @415 UNSUPPORTED MEDIA FORMAT@: if the client asks for an unsupported representation in the Accept header
186
187
188
189
##### Response Headers
190
191
192
* Location: containing the location of the newly created resource
193
194
195
196
##### Response
197
198
199
An xml document representing the newly created resource (see below for GET /_packagename_/_uuid_).
200
201
202
203
##### Implementation
204
205
206
~~~
207
@ResponseStatus(value = HttpStatus.CREATED)
208
@RequestMapping(method = RequestMethod.POST)
209
public ModelAndView post(@RequestBody T t1) {
210
    T t2 = fieldMapper.map(t1,t1.getClass());
211
    collectionMapper.map(t1,t2);
212
    List<ConstraintViolation<T>> constraintViolations = validator.validate(t, Level2.class,Level3.class);
213
    if(!constraintViolations.isEmpty()) {
214
        throw new InvalidObjectException(t,constraintViolations);
215
    }
216
    service.saveOrUpdate(t2);
217
    ModelAndView modelAndView = new ModelAndView();
218
    modelAndView.addObject(t);
219
    return modelAndView;
220
}
221
222
~~~
223
224
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.
225
226
227
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. 
228
229
230
231
### Resource /_packagename_/_uuid_
232
233
234
This resource represents a single IdentifiableEntity
235
236
237
Method: GET
238
239
Retrieves the resource
240
241
Optional parameters:
242
243
None
244
245
Request Headers
246
247
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
248
249
Accept-Language: Won't affect the representation per-se, but will affect the rendering of error messages
250
251
Status Code
252
253
200 OK: on success
254
255
400 BAD REQUEST: If the UUID is not well formed
256
257
404 NOT FOUND: If a resource does not exist with that UUID
258
259
410 GONE: If a resource did exist, but has been deleted (optional)
260
261
415 UNSUPPORTED MEDIA FORMAT: if the client asks for an unsupported representation in the Accept header
262
263
Response
264
265
The response is rendered as XML using the standard JAXB mappings present in on the model classes:
266
267
<?xml version=”1.0” encoding=”UTF-8”?>
268
269
<taxon:Taxon 
270
271
    uuid="urn-uuid-49361efb-aad6-448b-a316-f09005cee160"     
272
273
    isDoubtful="false"
274
275
    xmlns=”http://etaxonomy.eu/cdm/model/1.0” 
276
277
    xmlns:common=”http://etaxonomy.eu/cdm/model/common/1.0”
278
279
    xmlns:taxon=”http://etaxonomy.eu/cdm/model/taxon/1.0”
280
281
 >
282
283
  <common:TitleCache>Acherontia Laspeyres, 1809 sec cate-project.org</common:TitleCache>
284
285
  <common:ProtectedTitleCache>false</common:ProtectedTitleCache>
286
287
  <taxon:Name>urn-uuid-a8ab9567-7179-430d-a9fc-e3f22fe269f1</taxon:Name>
288
289
  <taxon:Sec>urn-uuid-b8cd17ba-32ea-4201-85f1-63d31526ccdb</taxon:Sec>
290
291
  <taxon:TaxonomicChildrenCount>1</taxon:TaxonomicChildrenCount>
292
293
  <taxon:RelationsFromThisTaxon>
294
295
    <taxon:FromThisTaxonRelationship uuid="urn-uuid-24f94660-c4e0-490c-8fb3-5cac20a426a4">
296
297
      <taxon:RelatedFrom>urn-uuid-49361efb-aad6-448b-a316-f09005cee160</taxon:RelatedFrom>
298
299
      <taxon:RelatedTo>urn-uuid-cc55da11-3bd7-4ca3-b1e2-6c5503eefea6</taxon:RelatedTo>
300
301
      <taxon:Type>urn-uuid-5799a150-1386-4bae-952e-e040d06f7485</taxon:Type>
302
303
    </taxon:FromThisTaxonRelationship>
304
305
  </taxon:RelationsFromThisTaxon>
306
307
  <taxon:Descriptions xsd:nil=”true”/>
308
309
</taxon:Taxon>
310
311
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:
312
313
<taxon:Descriptions xsd:nil=”true”/>
314
315
Wheras an empty collection looks like this
316
317
<taxon:Descriptions/>
318
319
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.
320
321
Can be implemented thus
322
323
@RequestMapping(method = RequestMethod.GET)
324
325
public ModelAndView get(@PathVariable(value = "uuid") UUID uuid) {
326
327
    T t = service.load(uuid,DEFAULT_INIT_STRATEGY);
328
329
    if(t == null) {
330
331
        throw new ObjectNotFoundException(type,uuid);
332
333
    }
334
335
    ModelAndView modelAndView = new ModelAndView();
336
337
    modelAndView.addObject(t);
338
339
    return modelAndView;
340
341
}
342
343
Method: POST
344
345
Updates the resource
346
347
Optional parameters:
348
349
None
350
351
Request Headers
352
353
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
355
Accept-Language: Won't affect the representation per-se, but will affect the rendering of error messages
356
357
Status Code
358
359
200 OK: on success
360
361
400 BAD REQUEST: If the UUID is not well formed or if the updated object is not valid
362
363
404 NOT FOUND: If a resource does not exist with that UUID
364
365
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.
366
367
410 GONE: If a resource did exist, but has been deleted (optional)
368
369
415 UNSUPPORTED MEDIA FORMAT: if the client asks for an unsupported representation in the Accept header
370
371
Response
372
373
See the response for GET
374
375
Implementation
376
377
@RequestMapping(method = RequestMethod.POST)
378
379
public ModelAndView post(@PathVariable(value = "uuid") UUID uuid, @RequestBody T t2) {
380
381
    T t1 = service.load(uuid,DEFAULT_INIT_STRATEGY);
382
383
    if(t1 == null) {
384
385
        throw new ObjectNotFoundException(type,uuid);
386
387
    }
388
389
    mapper.map(t1,t2);
390
391
    List<ConstraintViolation<T>> constraintViolations = validator.validate(t1, Level2.class,Level3.class);
392
393
    if(!constraintViolations.isEmpty()) {
394
395
        throw new InvalidObjectException(t,constraintViolations);
396
397
    }
398
399
    service.saveOrUpdate(t1);
400
401
    ModelAndView modelAndView = new ModelAndView();
402
403
    modelAndView.addObject(t1);
404
405
    return modelAndView;
406
407
}
408
409
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.
410
411
Method: DELETE
412
413
Deletes the resource
414
415
Optional parameters:
416
417
None
418
419
Request Headers
420
421
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
422
423
Accept-Language: Won't affect the representation per-se, but will affect the rendering of error messages
424
425
Status Code
426
427
200 OK: on success
428
429
400 BAD REQUEST: If the UUID is not well formed
430
431
404 NOT FOUND: If a resource does not exist with that UUID
432
433
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.
434
435
410 GONE: If a resource did exist, but has already been deleted (optional)
436
437
415 UNSUPPORTED MEDIA FORMAT: if the client asks for an unsupported representation in the Accept header
438
439
Response
440
441
Although Clients may not expect any content in the response body, it is recommended to redirect to the parent resource (/{packagename}).
442
443
Implementation
444
445
@RequestMapping(method = RequestMethod.DELETE)
446
447
public ModelAndView post(@PathVariable(value = "uuid") UUID uuid) {
448
449
    T t = service.load(uuid,DEFAULT_INIT_STRATEGY);
450
451
    if(t1 == null) {
452
453
        throw new ObjectNotFoundException(type,uuid);
454
455
    }
456
457
    service.delete(t);
458
459
    ModelAndView modelAndView = new ModelAndView(“redirect:/{packagename}”);
460
461
    return modelAndView;
462
463
}
464
465
Changes
466
467
cdmlib-model
468
469
1.	Creation of a custom AccessorFactory implementation in eu.etaxonomy.cdm.jaxb
470
471
2.	Creation of a Hibernate safe Accessor implentation in eu.etaxonomy.cdm.jaxb
472
473
3.	Addition of @XmlAccessorFactory annotations in all package-info.java files in cdmlib-model
474
475
4.	Addition of “nillable = true” to most @XmlElementWrapper annotations
476
477
5.	Creation of a CdmError base class in cdmlib-model
478
479
cdmlib-io
480
481
1.	Addition of “nillable = true” to equivalent xml elements
482
483
2.	Creation of a CdmIDResolver implentation in eu.etaxonomy.cdm.io.jaxb
484
485
3.	Modification of eu.etaxonomy.cdm.io.jaxb.DataSet and creation of extra elements to incorporate pageSize, pageNumber and maxResults attributes
486
487
cdmlib-remote
488
489
1.	Creation of an XmlView class to render cdm entities as CDM XML
490
491
2.	Creation of dozer mapper custom converters to merge collections if they are not null.
492
493
3.	Creation of dozer mapping files to map cdm objects.
494
495
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.