Spring RestTemplate custom mapping - java

I'm new to Spring and following along the example at http://spring.io/guides/gs/consuming-rest.
I noticed they haven't mapped all the JSON elements from http://graph.facebook.com/pivotalsoftware so I wanted to extend the example a little. For this example, I wanted to add "likes" and "were_here_count", like so in Page.java:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = true)
public class Page {
private String name;
private String about;
private String phone;
private String website;
private int were_here_count;
private int likes;
public String getName() {return name;}
public String getAbout() {return about;}
public String getPhone() {return phone;}
public String getWebsite() {return website;}
public int getVisitCount() {return were_here_count;}
public int getLikes() {return likes;}
}
and making these changes in Application.java:
import org.springframework.web.client.RestTemplate;
public class Application {
public static void main(String args[]) {
RestTemplate restTemplate = new RestTemplate();
Page page = restTemplate.getForObject("http://graph.facebook.com/pivotalsoftware", Page.class);
System.out.println("Name: " + page.getName());
System.out.println("About: " + page.getAbout());
System.out.println("Phone: " + page.getPhone());
System.out.println("Website: " + page.getWebsite());
System.out.println("Visit count: " + page.getVisitCount());
System.out.println("Likes: " + page.getLikes());
}
}
I was thinking that the mapping was done by element name, and that worked for "likes", but didn't for "were_here_count". Output:
Name: Pivotal
About: Pivotal is enabling the creation of modern software applications that leverage big & fast data – on a single, cloud independent platform.
Phone: (650) 286-8012
Website: http://www.pivotal.io
Visit count: 0
Likes: 1175
were_here_count is currently at 60. I'm guessing the default converter didn't like the underscore in the variable name. So I used the overloaded version of getForObject, providing my own mapping, like so:
package hello;
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.client.RestTemplate;
public class Application {
public static void main(String args[]) {
RestTemplate restTemplate = new RestTemplate();
Map<String, String> variables = new HashMap<String, String>(3);
variables.put("name", "name");
variables.put("about", "about");
variables.put("phone", "phone");
variables.put("website", "website");
variables.put("were_here_count", "were_here_count");
variables.put("likes", "likes");
Page page = restTemplate.getForObject("http://graph.facebook.com/pivotalsoftware", Page.class, variables);
System.out.println("Name: " + page.getName());
System.out.println("About: " + page.getAbout());
System.out.println("Phone: " + page.getPhone());
System.out.println("Website: " + page.getWebsite());
System.out.println("Visit count: " + page.getVisitCount());
System.out.println("Likes: " + page.getLikes());
}
}
But all to no avail. I've seen a few examples regarding custom JSON converters here but didn't understand them well - plus, this is a much simpler example, could I not get this done with a simple String-String mapping of variable names?
Anyone know how to do this and willing to show me how to build a custom converter and what the necessary steps are? Thank you! :)

Try adding some of Jackson's annotations to your Page class to help with the deserialization of the JSON. You should be able to tell Jackson (which will handle serialization/deserialization of JSON in Spring by default), what attributes in the response JSON map to your POJO attributes.:
public class Page {
...
#JsonProperty("were_here_count")
private int wereHereCount;
...
}
Another option, if you are not sure what attributes are being returned, is to just map the JSON to a Map:
Map<String,Object> map = restTemplate.getForObject("http://graph.facebook.com/pivotalsoftware", Map.class);
for (Map.Entry entry: response.entrySet()){
// do stuff...
}
Sometime this is the easier way to do custom object mapping when the response JSON is convoluted or just doesn't deserialize easily.

What does your setter for Page looks like? It works for me with this setter:
public void setWere_here_count(int were_here_count) {
this.were_here_count = were_here_count;
}

Related

Can I set cache name as dynamic? if yes then how?

`
#RestController
public class TestController {
#Cacheable(cacheNames = "testCache", key = "#name")
#GetMapping("/test/{name}")
public String test(#PathVariable String name) {
System.out.println("########Test Called ###### " + name);
return HttpStatus.OK.toString();
}
}
Here cacheNames is Stirng array, if name is not exists in cacheNames then it should add first then shloud do rest of the things.
I'm using spring boot cache and I have to add cacheNames depend on request parameters.
You can do something like this if you want much flexibility:
import org.springframework.cache.CacheManager;
import org.springframework.cache.Cache;
// other imports
#RestController
public class TestController {
private final Cache myCache;
public TestController(#Autowired CacheManager cacheManager) {
this.myCache = cacheManager.getCache("myCache");
}
#GetMapping("/test/{name}")
public String test(#PathVariable String name) {
return myCache.get(name, () -> {
// your expensive operation that needs to be cached.
System.out.println("########Test Called ###### " + name);
return HttpStatus.OK.toString();
});
}
}
Cache name will not be dynamic in that case, but the cache key will be. And this is probably what you want.

How can I have different names for the same field in different APIs?—Jackson

I have an API whose response is as follows:
{
ruleId:”123”,
ruleName:”Rule1”
}
Now I am introducing a new Api which exactly has these fields but the response should not have name as ruleId ,ruleName but as id,name:
{
id:”123”,
name:”Rule1”
}
I should change in such a way so that the previous Api response should not be impacted.
Thought to use JsonProperty /JsonGetter but it will change the previous Api response as well.
Is there any way that I can have 2 getters for the same field and then use one getter for previous Apis and other one for my purpose? (My concern is only when converting Pojo to JSON)
Can anyone help?
Since you want serialize the object differently in different cases, using jackson mix-in is preferred.
Here is example how to do that.
If your pojo looks something like this:
public class CustomPojo {
private String ruleId;
private String ruleName;
public String getRuleId() {
return ruleId;
}
public void setRuleId(String ruleId) {
this.ruleId = ruleId;
}
public String getRuleName() {
return ruleName;
}
public void setRuleName(String ruleName) {
this.ruleName = ruleName;
}
}
First, you need to create one interface (or class) like this:
import com.fasterxml.jackson.annotation.JsonProperty;
public interface CostomPojoMixin {
#JsonProperty("Id")
String getRuleId();
#JsonProperty("name")
String getRuleName();
}
This interface will be used to rename fields ruleId and ruleName during serilization.
Then when you have all this setup you can write controller method and customize ObjectMapper:
#GetMapping(value = "/test/mixin")
public String testMixin() throwsJsonProcessingException {
CostomPojo cp = new CostomPojo();
cp.setRuleId("rule");
cp.setRuleName("name");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(CustomPojo.class, CostomPojoMixin.class);
String json = objectMapper.writeValueAsString(cp);
return json;
}
This endpoint should return response like this:
{"Id":"rule","name":"name"}

How can we write a custom annotation to mask a argument in the constructor of a response pojo in maven java project

I want to mask mobile number present in the constructor of response pojo that prepares the response for end user to see the output messages.Is custom annotation a good way to solve this problem or is there any other better way to resolve this ?
For ex:
public ResponsePojo(String mobileNumber,
int nextRequestInterval) {
this.message = MessageFormat.format(message, mobileNumber);
this.nextRequestInterval = nextRequestInterval;
}
I want to do something like below :
public ResponsePojo(#MaskField String mobileNumber,
int nextRequestInterval) {
this.message = MessageFormat.format(message, mobileNumber);
this.nextRequestInterval = nextRequestInterval;
}
So that where ever i found some sensitive fields to mask, i can use my annotation #MaskField to mask some part of data in that field. Please suggest with sample code.
An annotation never ever provides any functionality. An annotation only provides metadata, which could be interpreted by an annotation processor according to the annotation. Therefore you would have to implement a custom annotation processor.
To solve your problem, you could use a String wrapper doing the masking for you. (Or you only create a single method to mask the String.)
public class MaskedString{
private final String value;
public MaskedString(String input) {
value = input;
}
public String getMaskedString(){
return "mask-your-string-algorithm"(value);
}
// optional if you use the wrapper as parameter
public String getUnmaskedString(){
return value;
}
}
And use It within your method.
public ResponsePojo(String mobileNumber,
int nextRequestInterval) {
MaskedString maskedMobilNumber = new MaskedString(mobileNumber);
this.message = MessageFormat.format(message, maskedMobilNumber.getMaskedString());
this.nextRequestInterval = nextRequestInterval;
// doSomething with the unmasked mobilNumber
...mobileNumber...;
}
You could use the wrapper as parameter
public ResponsePojo(MaskedString maskedMobilNumber,
int nextRequestInterval) {
this.message = MessageFormat.format(message, maskedMobilNumber.getMaskedString());
this.nextRequestInterval = nextRequestInterval;
// doSomething with the unmasked mobilNumber
...maskedMobilNumber.getUnmaskedString()...;
}
Because you tagged your question with spring-aop, I want to show an AOP solution. But you need to use native AspectJ, if you want to intercept constructors. Spring AOP can only intercept methods of Spring-managed components/beans, but no constructors.
The Spring manual explains how to set up native AspectJ via load-time weaving (LTW) in chapter "Using AspectJ with Spring Applications".
Here is a simple MCVE in stand-alone AspectJ without Spring. You can simply adapt it to your situation:
Parameter annotation:
package de.scrum_master.app;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target(PARAMETER)
public #interface MaskField {}
Helper class:
package de.scrum_master.app;
public class MessageFormat {
public static String format(String message, String mobileNumber) {
return message + mobileNumber;
}
}
Response POJO + main method:
I created your example constructor, but also two others, because we want a negative tests in order to make sure that the aspect only masks #MaskField String parameter, not #MaskField with another type or String without #MaskField.
package de.scrum_master.app;
import java.util.Properties;
public class ResponsePojo {
private static String message = "Mobile number: ";
private String mobileNumber;
private int nextRequestInterval;
public ResponsePojo(#MaskField String mobileNumber, int nextRequestInterval) {
this.mobileNumber = MessageFormat.format(message, mobileNumber);
this.nextRequestInterval = nextRequestInterval;
}
public ResponsePojo(#MaskField int nextRequestInterval, #MaskField Properties properties) {
this.mobileNumber = MessageFormat.format(message, "none");
this.nextRequestInterval = nextRequestInterval;
}
public ResponsePojo(#MaskField int nextRequestInterval, String mobileNumber) {
this.mobileNumber = MessageFormat.format(message, mobileNumber);
this.nextRequestInterval = nextRequestInterval;
}
#Override
public String toString() {
return "ResponsePojo(" + mobileNumber + ", " + nextRequestInterval + ")";
}
public static void main(String[] args) {
// Do not mask anything
System.out.println(new ResponsePojo(11, new Properties()));
System.out.println(new ResponsePojo(22, "+49 9090 87654321"));
// Mask phone number here
System.out.println(new ResponsePojo("+49 9090 87654321", 42));
}
}
Running the program without an aspect would yield:
ResponsePojo(Mobile number: none, 11)
ResponsePojo(Mobile number: +49 9090 87654321, 22)
ResponsePojo(Mobile number: +49 9090 87654321, 42)
So far, so good. No masking, of course.
Aspect:
While it is easy to limit the pointcut to constructors having at least one #MaskField String parameter via execution(*.new(.., #de.scrum_master.app.MaskField (String), ..)), this will match at any parameter position and theoretically there could be multiple #MaskField String parameters. So we cannot simply map a single parameter to an advice method parameter. Instead, we need to iterate over the arrays of arguments and of parameter annotations at the same time and limit masking to only #MaskField String parameters.
We need to use an #Around advice, because only in this advice type we can modify the arguments array and pass it on to the called method via proceed(args).
package de.scrum_master.aspect;
import java.lang.annotation.Annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.ConstructorSignature;
import de.scrum_master.app.MaskField;
#Aspect
public class MaskFieldAspect {
#Around("execution(*.new(.., #de.scrum_master.app.MaskField (String), ..))")
public Object maskFieldInConstructor(ProceedingJoinPoint thisJoinPoint) throws Throwable {
//System.out.println(thisJoinPoint);
ConstructorSignature signature = (ConstructorSignature) thisJoinPoint.getSignature();
Annotation[][] annotations = signature.getConstructor().getParameterAnnotations();
Object[] args = thisJoinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
for (Annotation annotation : annotations[i]) {
if (annotation instanceof MaskField)
args[i] = maskPhoneNumber((String) args[i]);
}
}
return thisJoinPoint.proceed(args);
}
String maskPhoneNumber(String phoneNumber) {
return phoneNumber.replaceFirst("....$", "****");
}
}
Running the program again with an the active aspect, yields:
ResponsePojo(Mobile number: none, 11)
ResponsePojo(Mobile number: +49 9090 87654321, 22)
ResponsePojo(Mobile number: +49 9090 8765****, 42)
See? For the 3rd constructor call, masking is active, but not for the 2nd one, because there the String parameter was not annotated.

Consuming Json in JAX-RS: Prohibiting null values

I'm writing a simple REST service which expects a POST containing a Json.
The service should refuse any Json not containing exactly the keys it expects.
I'm using JAX-RS on a JBoss EAP 7.1 and have been unable to do this. Code example:
import javax.ejb.Stateless;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path("/")
#Stateless
#Produces("text/plain")
public class TestAccess
{
#POST
#Path("test/")
#Consumes(MediaType.APPLICATION_JSON)
public String consumeJson(TestClass c)
{
return c.toString();
}
}
public class TestClass
{
public String first;
public String second;
public String toString()
{
return (first + ", " + second);
}
}
Previously I wrote consumeJson to expect expect a JsonObject and parsed that using a Jackson ObjectMapper. Doing this resulted in an error when receiving a Json missing keys. I think the way I'm doing it now is a "cleaner" way to do it as now the parameter list clearly shows what kind of Object the Json should describe. However, I don't want to check every single field first thing in the method nor do I want to add a isValid() method to every Object I get this way.
The example {"first" : "contentOfFirst"} returns "contentOfFirst, null" instead of failing.
EDIT: To clarify, I attempted something like this:
import javax.validation.constraints.NotNull;
public class TestClass
{
#NotNull
public String first;
#NotNull
public String second;
public String toString()
{
return (first + ", " + second);
}
}
This did not change the outcome, instead of failing (as it was supposed to) the POST still got the response "contentOfFirst, null". I'm aware these annotations still need a validator to validate them. I was (falsely?) under the impression that Jboss provides such a validator.
Turns out I missed that you need another annotation to enforce the checks:
#POST
#Path("test/")
#Consumes(MediaType.APPLICATION_JSON)
public String consumeJson(#Valid TestClass c)
{
return c.toString();
}
That's all that's needed for JBoss to actually enforce the checks.
It should be noted that JBoss doesn't like the use of #Valid and generic classes (like List<PathSegment>) in the same parameter list.
Modern jackson versions have #JsonCreator & #JsonProperty to do some validation during deserialization, e.g. for you case:
public class TestClass
{
#JsonCreator
TestClass(
#JsonProperty(value = "first", required = true) Integer first,
#JsonProperty(value = "second", required = true) Integer second) {
this.first = first;
this.second= second;
}
public String first;
public String second;
public String toString()
{
return (first + ", " + second);
}
}
More robust solution would be to use Bean Validation

formFactory.form() doesn't exist ! PlayFramework

I've a little problem, i want to create a web app and i learn PlayFramework with java documentation of
This sample code :
public Result hello() {
DynamicForm requestData = formFactory.form().bindFromRequest();
String firstname = requestData.get("firstname");
String lastname = requestData.get("lastname");
return ok("Hello " + firstname + " " + lastname);
}
The ''formFactory'' doesn't exist.
http://i.imgur.com/W941Bgz.png
Why I don't have this field ?
And when i want to create a model, i don't have the model class
http://i.imgur.com/9FW7wp1.png
Thanks you so much if you resolve my problem ! :)
From the documentation:
To wrap a class you have to inject a play.data.FormFactory into your Controller
Play already knows about FormFactory, so just add a constructor parameter for it:
public class FooController {
private final FormFactory formFactory;
#Inject
public FooController(final FormFactory formFactory) {
this.formFactory = formFactory;
}
public Result hello() {
DynamicForm requestData = formFactory.form().bindFromRequest();
String firstname = requestData.get("firstname");
String lastname = requestData.get("lastname");
return ok("Hello " + firstname + " " + lastname);
}
}
I'm guessing the Model you mention is that of EBean. You need to enable EBean for your project, and then you'll have the necessary classes on your classpath.
In project/plugins.sbt:
addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "3.0.0")
build.sbt:
lazy val myProject = (project in file(".")).enablePlugins(PlayJava, PlayEbean)
More information is available in the relevant docs.
First make sure to import these 2 libaries in your Play Controller:
import javax.inject.Inject;
import play.data.FormFactory;
After that before using the Form Builder, inject it into your code:
#Inject FormFactory formFactory;
Your code should work fine after this.
You will have to inject your formFactory like this:
#Inject FormFactory formFactory;

Categories