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:
Jon Brisbin
2012-06-13 09:23:58 -05:00
parent e0b3d84362
commit 3e2a79d329
4 changed files with 87 additions and 19 deletions

View File

@@ -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
View 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.

View File

@@ -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);
}
}
}

View File

@@ -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();
}