I have a resource, producing JSON, which returns links to a few child resources (which also produce JSON). This resource can be included from a few different points in the tree, so both use the absolutePathBuilder to create links to child resources.
public class AResource {
#GET
#Produces(MediaType.APPLICATION_JSON + ";charset=utf8")
public Map<String, Object> getRoot(#Context final UriInfo info) {
final Map<String, Object> toReturn = new HashMap<String, Object>();
final String[] children = { "one", "two", "thrée" };
final UriBuilder builder = info.getAbsolutePathBuilder().path("{child}");
for (final String child : children) {
toReturn.put(child, builder.build(child).toASCIIString());
}
return toReturn;
}
#Path("{child:(one|two|thrée)}")
public ChildResource getChild(#PathParam("child") final String child) {
return new ChildResource("AResource " + child);
}
}
public class ChildResource {
public Map<String, Object> getRoot(#Context final UriInfo info) {
...
}
}
Now lets say I need to add another MediaType, QueryParam, etc. which would cause the Parent resource to return the existing JSON, with the return of each of the child resources within it.
Is there a way in JAX-RS or Jersey to easily create/inject an altered context into a sub-resource? Or to make a sub-request within the container?
I currently am working with a naïve solution, where I take the map from the child, then post-process it to update any URI rooted at the current path, but it feels like there ought to be a better solution (which wouldn't require re-hashing the map after the fact?)
Not that I know. At least, not the way your current method is setup.
Here's a way to get that sort of functionality, but would require architecting your code differently...
1) Use the Response and ResponseBuilder classes to wrap your entities. Not only will it allow you to provide customize the response status, but you can also allow one method to respond to QueryParams and adjust the entity you deliver, which leads into...
2) If you need to deliver multiple versions of your entities then create Facades for them. Basically just create classes that contain the structure of what you would like your JSON responses to look like. In your case, one would represent child resources as URL strings, while another would simply contain the child objects.
public class ParentResource{
#GET
#Produces("application/json")
public Response getParent(
#Context UriInfo uriInfo
, #DefaultValue("false") #QueryParam("cascade") Boolean cascade
) {
if(cascade)
//This assumes that your ParentEntity stores the child objects already.
return Response.ok().entity(new ParentEntity).build();
else
return Response.ok().entity(new ParentFacadeWithURLS(uriInfo)).build();
}
}
EDIT #1 2012.03.07
public class AResource {
#GET
#Produces("application/json")
public Map<String, Object> getRoot(
#Context final UriInfo info
, #DefaultValue("false") #QueryParam("cascade") Boolean cascade
) {
final Map<String, Object> toReturn = new HashMap<String, Object>();
final String[] children = { "one", "two", "thrée" };
if(cascade){
ChildResource cr= new ChildResource();
toReturn.put(child, cr.getRoot(uriInfo));
} else {
final UriBuilder builder = info.getAbsolutePathBuilder().path("{child}");
for (final String child : children) {
toReturn.put(child, builder.build(child).toASCIIString());
}
}
return toReturn;
}
#GET
#Path("{child:(one|two|thrée)}")
public ChildResource getChild(#PathParam("child") final String child) {
ChildResource cr = new ChildResource();
return new cr.getRoot(child);
}
}
Related
DOMAIN
There is a class Link
public final class Link implements Serializable {
private final String _title;
private final String _href;
private final List<Link> _links;
}
and there is a class LinkDeserialiser to deserialise Link objects from JSON
public final class LinkDeserialiser extends JsonDeserializer<Link> {
#Override
public Link deserialize(JsonParser parser, DeserializationContext context) throws IOException {
final JsonNode node = context.readValue(parser, JsonNode.class);
return Link.builder()
.title(node.path(Constants.TITLE).asText().trim())
.href(node.path(Constants.HREF).asText().trim())
.links(loadLinks(node.path(Constants.LINKS)))
.build();
}
}
PROBLEM
Before the LinkDeserialiser, we had a method
public static List<Link> readLinks(JsonNode node) {
List<Link> links = new ArrayList<>();
node.forEach(childNode -> {
Link link = new Link(childNode);
if (link.valid()) {
links.add(link);
}
});
return links;
}
that had parsed a JsonNode into a List<Link> and filtered that list by link validity. When we introduced the LinkDeserialiser, we found a proper way to deserialise Links, which is
nodeParser.readValueAs(new TypeReference<List<Link>>() {});
The problem is, though, we don't know where to put the filter link -> link.valid() now. The nodeParser would populate the list with both valid and invalid Links.
I'd rather not write a JsonDeserializer<Collection<Link>>, which seems like a dull idea.
QUESTION
I'd love to get an answer to any of these questions:
1) How to modify a collection deserialiser so that it produces a collection filtered out by given conditions?
2) Is there any way to make a collection deserialiser "exception-tolerant" so when an exception is thrown, it keeps collecting objects into a collection? (an exception would be a kind of a filter)
UPDATE 1
Link#valid is a fundamental rule and should be followed regardless of context and caller. I don't want (and can't) force the caller to filter the obtained collection by my (private) rules.
UPDATE 2
From the sources of CollectionDeserializer
Object value;
if (t == JsonToken.VALUE_NULL) {
if (_skipNullValues) {
continue;
}
value = _nullProvider.getNullValue(ctxt);
} else if (typeDeser == null) {
value = valueDes.deserialize(p, ctxt);
} else {
value = valueDes.deserializeWithType(p, ctxt, typeDeser);
}
result.add(value);
it seems a deserialised value is added regardless of any conditions. I am considering extending the class and filter a resulting collection on my own.
Short and uncomplicated: Parse all entries and remove the invalid ones.
nodeParser.readValueAs(new TypeReference<List<Link>>() {})
.removeIf((Link link) -> !link.valid());
in order to write a clean and smart code, I'm wondering what can I do to improve my actual piece of code:
public JSONObject getCustomer(final String customerId) {
if (customerId == null || customerId.equals("")) {
return null;
} else {
final RestTemplate restTemplate = new RestTemplate();
final String result = restTemplate.getForObject("http://localhost:6061/customers/" + customerId,
String.class);
return new JSONObject(result);
}
}
Especially, I didn't like the way I composed the url, neither the check on customerId's value.
I'd like to have something like JPA, where I ask some information passing a parameter, just to be clear (in pseudocode):
public JSONObject getCustomer(final String customerId) {
final RestTemplate restTemplate = new RestTemplate();
final Query query = restTemplate.query("http://localhost:6061/customers/:customerId");
query.addParameter("customerId", customerId);
JSONObject result = query.getForObject();
return result;
}
Then, if customerId would be null or some white spaces or not existing, I'd like that result would be null.
Is there a way to do this with a standard library?
Thanks
First off, I would remove the else branch and refactor the condition to:
public JSONObject getCustomer(final String customerId) {
if (isNull(customerId) || customerId.trim().isEmpty()) {
return null;
}
...
}
Second, if you have a bunch of URI variables, Spring guys recommend using a Map<String, String>:
final String templateURL = "http://localhost:6061/customers/{customerId}";
final Map<String, String> variables = new HashMap<>();
variables.put("customerId", customerId);
...
template.getForObject(templateURL, String.class, variables);
Third, the method shouldn't create a RestTemplate instance on its own. I would prefer injecting the already-tuned object into an instance field:
getTemplate().getForObject(templateURL, String.class, variables);
Finally, I would name the result more meaningful:
final String customerRepresentation = ...;
Some notes:
getCustomer actually returns a JSONObject, not a Customer.
templateURL hardcoded the base URL as well as the URL to customers.
The method does a lot of work (takes too much responsibility) - argument validation, URL construction, making a request. Try to split these responsibilities between corresponding methods.
Firstly I would rather use DTO objects to hold the response data and manipulate them rather than using a String representation of the payload. So you may change it like this. Here Jackson takes care of all the serialization and deserialization of your data.
CustomerDTO customerDTO = restTemplate
.getForEntity("http://localhost:6061/customers/{customerId}", CustomerDTO.class, customerId).getBody();
You can use javax.validators such as #Min, #NotEmpty etc at your controller to check for the empty values. A sample is given below.
#RequestMapping(value = someURL, params = {"id"})
public SomeResponse doSomething(#PathVariable(value = "id") #Size(min=1) String id)
This throws a ValidationException with a relevant error message which can be customized by you. You then need to have an error handling aspect that sets the error message in ErrorDTO object and set the status code appropriately.
Why is there this limitation in Google Cloud Endpoints:
Arrays or collections of entity types are not allowed.
For an API with method:
#ApiMethod(name = "getCollection", path = "getCollection", httpMethod = HttpMethod.POST)
public ArrayList<MyObject> getCollection(List<MyObject> pMyObjects) {
And what's the best way to get around this? Thanks!
I think the reason it's not supported is because the named parameters in the method signature end up being URL query parameters, and they don't want to pollute that with long lists of items. Furthermore, they only support a single object of an Entity type in the signature, because this automatically becomes the "request body". You can read about it here in the docs.
As for working around it, you create a container entity object for the "request body". The nice side effect of this is that the APIs Explorer will expand the pieces of your entity object out in the GUI and help you do the JSON correctly.
Here's an example that adds a Map named "patchFieldOps" for implementing partial update. You can put as many fields into your Entity object as you like. I think if you embed more user-defined types they will also need to have the #Entity annotation.
#Entity
public class EndpointUpdateRequestBody {
// Since Google Cloud Endpoints doesn't support HTTP PATCH, we are overloading
// HTTP PUT to do something similar.
private Map<String, String> patchFieldsOps;
public EndpointUpdateRequestBody() {
patchFieldsOps = new HashMap<String, String>();
}
public EndpointUpdateRequestBody(Map<String, String> patchFieldsOps) {
this.patchFieldsOps = patchFieldsOps;
}
public Map<String, String> getPatchFieldsOps() {
return patchFieldsOps;
}
public void setPatchFieldsOps(Map<String, String> patchFieldsOps) {
this.patchFieldsOps = patchFieldsOps;
}
}
...
#ApiMethod(
name = "stuff.update",
path = "stuff/{id}",
httpMethod = ApiMethod.HttpMethod.PUT
)
public Group update(
User apiUser,
#Named("id") String id,
#Nullable #Named("name") String name,
#Nullable #Named("description") String description,
EndpointUpdateRequestBody requestBody)
throws OAuthRequestException, InternalServerErrorException, NotFoundException,
BadRequestException, UnauthorizedException, ConflictException {
I have an action which requires to get a list of emails from a remote server. Then I want to use the emails to get a list of emailDomainInformation from another remote server (note that this second piece of info depends on the first). After all this, I want to output data from both servers onto a map and render it onto the page with dust.
I managed to get this to work without the second piece of data by doing it like this:
public static Result index()
{
F.Promise<Email> emailPromise = getEmailPromise(...);
F.Promise<Result> results = emailPromise.map( new F.Function<Email, Result>()
{
public Result apply(Email email)
{
Map<String, Object> data = new HashMap<String, Object>();
data.put("email", email.getAddress());
data.put("domain", email.getDomain());
dustRenderer.render(data);
}
}
async(results);
}
Now, since I want to make an async call to getEmailDomainData(email.getDomain()); inside the emailPromise.map() method. What do I do with the Promise<EmailDomain> object I get back? How do I put that into the data map to pass to the dustRenderer?
Here is an example that essentially does what you need:
public static Result social() {
final F.Promise<WS.Response> twitterPromise = WS.url("http://search.twitter.com/search.json").setQueryParameter("q", "playframework").get();
final F.Promise<WS.Response> githubPromise = WS.url("https://api.github.com/legacy/repos/search/playframework").get();
return async(
twitterPromise.flatMap(
new F.Function<WS.Response, F.Promise<Result>>() {
public F.Promise<Result> apply(final WS.Response twitterResponse) {
return githubPromise.map(
new F.Function<WS.Response, Result>() {
public Result apply(final WS.Response githubResponse) {
return ok(views.html.social.render(twitterResponse.asJson().findValuesAsText("text"), githubResponse.asJson().findValuesAsText("name")));
}
}
);
}
}
)
);
}
In this case the two run in parallel but you could move the second Promise creation into the handler for the first Promise.
I need strict compliance with the order of the elements in my xml document. If I use XmlHttpContent serializer to form xml content, fields sort alphabetically.
Is there any way to specify explicitly order of the elements in xml? Or are there other ways to create and post http requests with the xml body?
I know this answer isn't ideal but I recently came across this issue when trying to use the http client library for serialisation to xml. The solution I've found that works is to have my DTO classes provide a method to convert them into a sorted map of some kind.
In my case this is an ImmutableMap<String, Object> as I'm also using Guava but any map with controllable order will do. The basic idea is to work with the java objects to construct your data but then when the time comes to serialise them you serialise the map instead.
public interface OrderedXml {
ImmutableMap<String, Object> toOrderedMap();
}
public class Parent implements OrderedXml {
#Key("First") String first;
#Key("Second") String second;
#Key("Child") Child third;
#Override
public ImmutableMap<String, Object> toOrderedMap() {
return ImmutableMap.of(
// the order of elements in this map will be the order they are serialised
"First", first,
"Second", second,
"Child", third.toOrderedMap()
);
}
}
public class Child implements OrderedXml {
#Key("#param1") String param1;
#Key("#param2") String param2;
#Key("text()") String value;
#Override
public ImmutableMap<String, Object> toOrderedMap() {
return ImmutableMap.of(
// the same goes for attributes, these will appear in this order
"#param1", param1,
"#param2", param2,
"text()", value
);
}
}
public class Main {
public static void main(String[] args) {
// make the objects
Parent parent = new Parent();
parent.first = "Hello";
parent.second = "World";
parent.child = new Child();
parent.child.param1 = "p1";
parent.child.param2 = "p2";
parent.child.value = "This is a child";
// serialise the object to xml
String xml = new XmlNamespaceDictionary()
.toStringOf("Parent", parent.toOrderedXml()); // the important part
System.out.println(xml); // should have the correct order
}
}
I know this solution isn't ideal but at least you can reuse the toOrderedXml to make a nice toString :-).