Added wiki documentation on API usage from curl. Also fixed a couple bugs, tweaked how rels are generated in some links.
This commit is contained in:
@@ -74,7 +74,7 @@ configure(subprojects) { subproject ->
|
||||
// Testing
|
||||
testCompile "org.spockframework:spock-core:$spockVersion"
|
||||
testCompile "org.spockframework:spock-spring:$spockVersion"
|
||||
testCompile "org.hamcrest:hamcrest-all:1.1"
|
||||
testCompile "org.hamcrest:hamcrest-library:1.1"
|
||||
testCompile "org.springframework:spring-test:$springVersion"
|
||||
testRuntime "org.springframework:spring-context-support:$springVersion"
|
||||
|
||||
|
||||
43
doc/curl_usage.md
Normal file
43
doc/curl_usage.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Example API usage with curl
|
||||
|
||||
Here is some example usage of the REST API with `curl`. First we'll add a `Family`:
|
||||
|
||||
$ curl -v -d '{"surname" : "Doe"}' -H "Content-Type: application/json" http://localhost:8080/family
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Location: http://localhost:8080/family/1
|
||||
Content-Length: 0
|
||||
|
||||
Now we'll add a `Person`:
|
||||
|
||||
$ curl -v -d '{"name" : "John Doe"}' -H "Content-Type: application/json" http://localhost:8080/people
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Location: http://localhost:8080/people/1
|
||||
Content-Length: 0
|
||||
|
||||
Now we'll add this person to the "Doe" family we added above:
|
||||
|
||||
$ curl -v -d 'http://localhost:8080/people/1' -H "Content-Type: text/uri-list" http://localhost:8080/family/1/members
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Content-Length: 0
|
||||
|
||||
Notice that we don't return a `Location` when we add items to a referenced collection because we can add N numbers of items (here we're just adding one) so the `Location` header wouldn't be very meaningful as you couldn't match which URL you POSTed with the corresponding URL in the header.
|
||||
|
||||
Now that we have some links created, let's query them so our user agent can keep track of them:
|
||||
|
||||
$ curl -v http://localhost:8080/family/1/members
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json;charset=ISO-8859-1
|
||||
Content-Length: 118
|
||||
|
||||
{
|
||||
"_links" : [ {
|
||||
"rel" : "family.Family.Person.1",
|
||||
"href" : "http://localhost:8080/family/1/members/1"
|
||||
} ]
|
||||
}
|
||||
|
||||
We can continue adding other top-level entities by sending JSON data and can add links to referenced entities by sending `text/uri-list` data with the URIs to those other top-level entities.
|
||||
@@ -107,19 +107,19 @@ public class ValidatingRepositoryEventListener
|
||||
validate("afterDelete", entity);
|
||||
}
|
||||
|
||||
private Errors validate(String event, Object entity) {
|
||||
private Errors validate(String event, Object o) {
|
||||
Errors errors = null;
|
||||
if (null != entity) {
|
||||
Class<?> domainType = entity.getClass();
|
||||
if (null != o) {
|
||||
Class<?> domainType = o.getClass();
|
||||
errors = new ValidationErrors(domainType.getSimpleName(),
|
||||
entity,
|
||||
o,
|
||||
repositoryMetadataFor(domainType).entityMetadata());
|
||||
Collection<Validator> validators = this.validators.get(event);
|
||||
if (null != validators) {
|
||||
for (Validator v : validators) {
|
||||
if (v.supports(entity.getClass())) {
|
||||
LOG.debug(event + ": " + entity + " with " + v);
|
||||
ValidationUtils.invokeValidator(v, entity, errors);
|
||||
if (v.supports(o.getClass())) {
|
||||
LOG.debug(event + ": " + o + " with " + v);
|
||||
ValidationUtils.invokeValidator(v, o, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -93,6 +95,8 @@ public class RepositoryRestController
|
||||
public static final String SELF = "self";
|
||||
public static final String LINKS = "_links";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RepositoryRestController.class);
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private MediaType uriListMediaType = MediaType.parseMediaType("text/uri-list");
|
||||
@@ -269,7 +273,7 @@ public class RepositoryRestController
|
||||
while (iter.hasNext()) {
|
||||
Object o = iter.next();
|
||||
Serializable id = (Serializable) repoMeta.entityMetadata().idAttribute().get(o);
|
||||
links.add(new SimpleLink(repoMeta.rel() + "." + o.getClass().getSimpleName(),
|
||||
links.add(new SimpleLink(repoMeta.rel() + "." + o.getClass().getSimpleName() + "." + id.toString(),
|
||||
buildUri(baseUri, repository, id.toString())));
|
||||
}
|
||||
links.add(new SimpleLink(repoMeta.rel() + ".search",
|
||||
@@ -591,23 +595,27 @@ public class RepositoryRestController
|
||||
)
|
||||
public ModelAndView deleteEntity(@PathVariable String repository,
|
||||
@PathVariable String id) {
|
||||
Map model = new HashMap();
|
||||
RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
|
||||
Serializable serId = stringToSerializable(id,
|
||||
(Class<? extends Serializable>) repoMeta.entityMetadata()
|
||||
.idAttribute()
|
||||
.type());
|
||||
CrudRepository repo = repoMeta.repository();
|
||||
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new BeforeDeleteEvent(serId));
|
||||
}
|
||||
repo.delete(serId);
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new AfterDeleteEvent(serId));
|
||||
Object entity = repo.findOne(serId);
|
||||
if (null == entity) {
|
||||
model.put(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new BeforeDeleteEvent(entity));
|
||||
}
|
||||
repo.delete(serId);
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new AfterDeleteEvent(entity));
|
||||
}
|
||||
model.put(STATUS, HttpStatus.NO_CONTENT);
|
||||
}
|
||||
|
||||
Map model = new HashMap();
|
||||
model.put(STATUS, HttpStatus.NO_CONTENT);
|
||||
return new ModelAndView(viewName("empty"), model);
|
||||
}
|
||||
|
||||
@@ -656,7 +664,10 @@ public class RepositoryRestController
|
||||
if (propVal instanceof Collection) {
|
||||
for (Object o : (Collection) propVal) {
|
||||
String propValId = idAttr.get(o).toString();
|
||||
String rel = repository + "." + entity.getClass().getSimpleName() + "." + attrType.getSimpleName();
|
||||
String rel = repository + "."
|
||||
+ entity.getClass().getSimpleName() + "."
|
||||
+ attrType.getSimpleName() + "."
|
||||
+ propValId;
|
||||
URI path = buildUri(baseUri, repository, id, property, propValId);
|
||||
links.add(new SimpleLink(rel, path));
|
||||
}
|
||||
@@ -998,6 +1009,7 @@ public class RepositoryRestController
|
||||
@ExceptionHandler(OptimisticLockingFailureException.class)
|
||||
@ResponseBody
|
||||
public ResponseEntity handleLockingFailure(OptimisticLockingFailureException ex) throws IOException {
|
||||
LOG.error(ex.getMessage(), ex);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
Map m = new HashMap();
|
||||
@@ -1008,6 +1020,7 @@ public class RepositoryRestController
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@ExceptionHandler(RepositoryConstraintViolationException.class)
|
||||
public Model handleValidationFailure(RepositoryConstraintViolationException ex) throws IOException {
|
||||
LOG.error(ex.getMessage(), ex);
|
||||
Model model = new ExtendedModelMap();
|
||||
model.addAttribute(STATUS, HttpStatus.BAD_REQUEST);
|
||||
|
||||
@@ -1022,6 +1035,18 @@ public class RepositoryRestController
|
||||
return model;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
@ResponseBody
|
||||
public ResponseEntity handleMessageConversionFailure(HttpMessageNotReadableException ex) throws IOException {
|
||||
LOG.error(ex.getMessage(), ex);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
Map m = new HashMap();
|
||||
m.put("message", ex.getMessage());
|
||||
return new ResponseEntity(objectMapper.writeValueAsBytes(m), headers, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
private static URI buildUri(URI baseUri, String... pathSegments) {
|
||||
return UriComponentsBuilder.fromUri(baseUri).pathSegment(pathSegments).build().toUri();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user