I want change data after #RestController class in Spring
I need to change the fields data based on the specified language.
example:
enum:
public enum Gender {
WOMAN, MAN, OTHER
}
the controller:
#RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
public User get(#PathVariable Long id) throws Exception {
log.debug("Entering get( id={} )", id);
User user = null;
try {
user = UserService.get(id);
} catch (Exception e) {
log.debug("Error occurred in get().", e);
throw e;
}
return user;
}
this output:
{
fisrtName: 'john',
lastName: 'doe',
gender: 'man'
}
change to this output:
{
fisrtName: 'john',
lastName: 'doe',
gender: 'homme'
}
I assume user.gender is a enum. Then you could add a custom Serializer and Deserializer to your JSON Mapper (hopefully Jackson).
#JsonComponent
public class TranslatedGenderJsonComponent {
public static class TranslatedGenderSerializer extends StdSerializer<Gender> {
public TranslatedGenderSerializer () {
super(Gender.class);
}
public void serialize(
Gender gender, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
String translatedGender = myTranslateFunction(gender);
generator.writeString(translatedGender );
}
}
public static class TranslatedGenderDeserializer extends StdDeserializer<Gender> {
public TranslatedGenderDeserializer () {
super(Gender.class);
}
public Gender deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonToken currentToken = jp.getCurrentToken();
if (currentToken.equals(JsonToken.VALUE_STRING)) {
String text jp.getText().trim();
Gender gender = myInverseTranslateFunction(text);
return gender;
} else if (currentToken.equals(JsonToken.VALUE_NULL)) {
return getNullValue();
} else {
throw ctxt.mappingException(Gender.class);
}
}
}
}
This code is not tested, it is just written in the browser! (I am quite unsure with the deserializer/parser code.) But I hope it will guide you to a solution.
I changed code:
the controller:
#Autowired
GlobalMassengerAdvice globalMassenger;
#RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
public User get(#PathVariable Long id) throws Exception {
log.debug("Entering get( id={} )", id);
User user = null;
try {
user = userService.get(id);
String value = globalMassenger.getMessage(user.getGender().name());
user.setGender( value );
} catch (Exception e) {
log.debug("Error occurred in get().", e);
throw e;
}
return user;
}
I add new Class (GlobalMassengerAdvice):
#ControllerAdvice
public class GlobalMassengerAdvice {
#Autowired
private MessageSource messageSource;
public String getMessage(final String messageKey, final Object... messageParameters) {
Locale locale = LocaleContextHolder.getLocale();
String result = null;
try {
result = messageSource.getMessage(messageKey, messageParameters, locale);
} catch (NoSuchMessageException e) {
result = messageKey;
}
return result;
}
}
Related
i have below resttempalte which invokes rest controller of another service..
#Override
public ResponseEntity<String> callRestAPI(APIReqDataMO apiReqDataMO) {
String apiURL = URIGenerator.getAPIURL(apiReqDataMO);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
HttpEntity<?> request = new HttpEntity<>(apiReqDataMO.getRequestObject(), headers);
ResponseEntity<String> httpRes = restTemplate.postForEntity(apiURL, request, String.class);
return httpRes;
}
and in my service i have controller, which consumes above request..
#RequestMapping(value = "/targetService/createUser", method = RequestMethod.POST, consumes = "application/json")
public String fuzzerServiceAge(UserMO userMO) {
System.out.println("---------------------age is -------------------------" + userMO.getAge());
if (userMO.getAge() > 0) {
System.out.println("error age greater than 0 ");
return "invalid user age";
} else if (userMO.getAge() == 0) {
return "invalid user age";
}
return "user added successfully";
}
when i try my test.. the age which i am pushing through rest template is not getting mapped..and i am getting age as 0 always in my system.out.. what could be wrong in my code... and is there anything missing from configuration perspective..
EDIT -
public class APIReqDataMO {
private String restAPIURL;
private Object[] pathParam;
private Object[] requestParam;
private String requestType;
private String paramType;
private Object requestObject;
public String getParamType() {
return paramType;
}
public void setParamType(String paramType) {
this.paramType = paramType;
}
public String getRequestType() {
return requestType;
}
public void setRequestType(String requestType) {
this.requestType = requestType;
}
public Object getRequestObject() {
return requestObject;
}
public void setRequestObject(Object requestObject) {
this.requestObject = requestObject;
}
public String getRestAPIURL() {
return restAPIURL;
}
public void setRestAPIURL(String restAPIURL) {
this.restAPIURL = restAPIURL;
}
public Object[] getPathParam() {
return pathParam;
}
public void setPathParam(Object[] pathParam) {
this.pathParam = pathParam;
}
public Object[] getRequestParam() {
return requestParam;
}
public void setRequestParam(Object[] requestParam) {
this.requestParam = requestParam;
}
}
controller
#PostMapping("/targetService/createUser")
public String fuzzerServiceAge(UserMO userMO) {
System.out.println("--------------------- age is -------------------------" + userMO.getAge());
if (userMO.getAge() > 0) {
// return ResponseEntity.ok("Hello World!");
} else if (userMO.getAge() == 0) {
System.out.println(" it is else block");
// return ResponseEntity.badRequest().build();
}
// return ResponseEntity.ok("user added successfully!");
return "user added successfully";
}
usermo
public class UserMO {
#JsonProperty("name")
private String name;
#JsonProperty("age")
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Issue
There is an issue in API implementation. You are creating POST API and when the user will invoke this API by passing UserMO in the request body then mapping won't happen because the #RequestBody annotation is missing.
#PostMapping("/targetService/createUser")
public String fuzzerServiceAge(UserMO userMO) {
System.out.println("--------------------- age is -------------------------" + userMO.getAge());
if (userMO.getAge() > 0) {
// return ResponseEntity.ok("Hello World!");
} else if (userMO.getAge() == 0) {
System.out.println(" it is else block");
// return ResponseEntity.badRequest().build();
}
// return ResponseEntity.ok("user added successfully!");
return "user added successfully";
}
Solution
If you are using #RestController annotation on top of the controller class then add #RequestBody annotation before UserMO userMO and try again.
Like this
#PostMapping("/targetService/createUser")
public String fuzzerServiceAge(#RequestBody UserMO userMO) {
//logic
}
if you are using #Controller annotation on top of the controller class then add #ResponseBody annotation on top of method fuzzerServiceAge() and #RequestBody annotation before UserMO userMO and try again.
Like this
#PostMapping("/targetService/createUser")
#ResponseBody
public String fuzzerServiceAge(#RequestBody UserMO userMO) {
//logic
}
response-code: 400 details: name: VALIDATION_ERROR message: Invalid request - see details details: [{
"field": "transactions.amount",
"issue": "Cannot construct instance of com.paypal.platform.payments.model.rest.common.Amount, >problem: INVALID_CURRENCY_AMOUNT_FORMAT"
}] debug-id: 86ad5783892c3 information-link: https://developer.paypal.com/docs/api/payments/#errors
package com.spring.soap.api;
#Configuration
public class PaypalConfig {
#Value("${paypal.client.id}")
private String clientId;
#Value("${paypal.client.secret}")
private String clientSecret;
#Value("${paypal.mode}")
private String mode;
#Bean
public Map<String,String> paypalSdkConfig(){
Map<String,String> configMap= new HashMap<>();
configMap.put("mode",mode);
return configMap;
}
#Bean
public OAuthTokenCredential oAuthTokenCredential() {
return new OAuthTokenCredential(clientId,clientSecret,paypalSdkConfig());
}
#Bean
public APIContext apiContext() throws PayPalRESTException {
APIContext context = new APIContext(oAuthTokenCredential().getAccessToken());
context.setConfigurationMap(paypalSdkConfig());
return context;
}
}
{
#Autowired
PaypalService service;
public static final String SUCCESS_URL = "pay/success";
public static final String CANCEL_URL = "pay/cancel";
#GetMapping("/")
public String home() {
return "home";
}
#PostMapping("/pay")
public String payment(#ModelAttribute("order") Order order) {
try {
Payment payment = service.createPayment(order.getPrice(), order.getCurrency(), order.getMethod(),
order.getIntent(), order.getDescription(), "http://localhost:9090/" + CANCEL_URL,
"http://localhost:9090/" + SUCCESS_URL);
for(Links link:payment.getLinks()) {
if(link.getRel().equals("approval_url")) {
return "redirect:"+link.getHref();
}
}
} catch (PayPalRESTException e) {
e.printStackTrace();
}
return "redirect:/";
}
#GetMapping(value = CANCEL_URL)
public String cancelPay() {
return "cancel";
}
#GetMapping(value = SUCCESS_URL)
public String successPay(#RequestParam("paymentId") String paymentId, #RequestParam("PayerID") String payerId) {
try {
Payment payment = service.executePayment(paymentId, payerId);
System.out.println(payment.toJSON());
if (payment.getState().equals("approved")) {
return "success";
}
} catch (PayPalRESTException e) {
System.out.println(e.getMessage());
}
return "redirect:/";
}
}
{
#Autowired
private APIContext apiContext;
public Payment createPayment(
Double total,
String currency,
String method,
String intent,
String description,
String cancelUrl,
String successUrl) throws PayPalRESTException{
Amount amount = new Amount();
amount.setCurrency(currency);
total = new BigDecimal(total).setScale(2, RoundingMode.HALF_UP).doubleValue();
amount.setTotal(String.format("%.2f", total));
Transaction transaction = new Transaction();
transaction.setDescription(description);
transaction.setAmount(amount);
List<Transaction> transactions = new ArrayList<>();
transactions.add(transaction);
Payer payer = new Payer();
payer.setPaymentMethod(method);
Payment payment = new Payment();
payment.setIntent(intent);
payment.setPayer(payer);
payment.setTransactions(transactions);
RedirectUrls redirectUrls = new RedirectUrls();
redirectUrls.setCancelUrl(cancelUrl);
redirectUrls.setReturnUrl(successUrl);
payment.setRedirectUrls(redirectUrls);
return payment.create(apiContext);
}
public Payment executePayment(String paymentId, String payerId) throws PayPalRESTException{
Payment payment = new Payment();
payment.setId(paymentId);
PaymentExecution paymentExecute = new PaymentExecution();
paymentExecute.setPayerId(payerId);
return payment.execute(apiContext, paymentExecute);
}
}
It would appear your locale is formatting decimals with a comma (,) as the decimal separator.
The PayPal API exclusively accepts numbers with a period (.) as the decimal separator
Take this line:
amount.setTotal(String.format("%.2f", total));
Change %.2f to %.3f. The final code should look like:
amount.setTotal(String.format("%.3f", total));
In my case I was sending the SubTotal on Details with a NON rounded value:
141.750
So I just round the value like this:
details.setSubtotal(subTotal.setScale(2, BigDecimal.ROUND_HALF_EVEN).toString());
(In other words)
141.75
I am going to use #ModelAttribute instead of #RequestParam to bind the user input and write it to the database. I am just confused when the #ModelAttribute bind the data in my request handler method (#ModelAttribute book Book) as an book object then how should I pass this object to the database? Normally using #RequestParam I bind the user inputs variable by variable according to my model class and then I send them to the db using the related DAO method. I show my classes in below. Can anybody say how my request handler method should look like if I use #ModelAttribute?
Model Class:
#Component
public class Book {
int bookId;
String title;
Author author;
Publisher publisher;
public int getBookId() {
return bookId;
}
public void setBookId(int bookId) {
this.bookId = bookId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
public Publisher getPublisher() {
return publisher;
}
public void setPublisher(Publisher publisher) {
this.publisher = publisher;
}
}
DAO:
public class BookDAO extends JdbcDaoSupport {
#Autowired
AuthorDAO authorDAO;
#Autowired
PublisherDAO publisherDAO;
public void addBook(String title, int authorId, int publisherId)
throws ClassNotFoundException, SQLException {
String sql = "insert into tbl_book (title, authId, pubId) values (?, ?, ?)";
this.getJdbcTemplate().update(sql, new Object[]{title, authorId, publisherId});
}
}
Service:
#Service
public class BookService {
#Autowired
BookDAO bookDAO;
public Book getBookById(int bookId) throws ClassNotFoundException,
SQLException {
return bookDAO.getBookById(bookId);
}
public List<Book> getAllBooks() throws ClassNotFoundException,
SQLException {
List<Book> bookList = bookDAO.getAllBooks();
return bookList;
}
public void addBook(String title, int authorId, int publisherId) throws ClassNotFoundException,
SQLException {
bookDAO.addBook(title, authorId, publisherId);
}
}
Controller:
#Controller
public class BookController {
#RequestMapping(value = "/addBookExecution", method = equestMethod.POST)
protected ModelAndView addBookExecution(#RequestParam String title,
#RequestParam int authorId, #RequestParam int blisherId)
throws ClassNotFoundException, SQLException {
bookService.addBook(title, authorId, publisherId);
ModelAndView model = new ModelAndView("adminFunctionsPage");
model.addObject("Msg", "Your request has been processed successfully.");
return model;
}
}
Your form should have parameters names as your book object, check below sample code
<form >
<input type="text" name="authorId"/>
<input type="text" name="authorName"/>
etc...
</form>
Book.java
class Book{
Integer authorId;
String authorName;
etc..
}
#RequestMapping(value = "/addBookExecution", method = equestMethod.POST)
protected ModelAndView addBookExecution(#ModelAttribute Book book)
throws ClassNotFoundException, SQLException {
bookService.addBook(book);
ModelAndView model = new ModelAndView("adminFunctionsPage");
model.addObject("Msg", "Your request has been processed successfully.");
return model;
}
I am trying to mock a getBy() method after adding an element by a mocked service add.
This is what I have:
FeedItem feedItem = feedServiceTested.createFeedItem("Text Test", "Category Test", "Author Test");
Mockito.verify(feedRepository).add(feedItem);
Mockito.verify(feedRepository).findAllByCategory("Category Test");
However I get the following error:
Wanted but not invoked:
feedRepository.findAllByCategory(
"Category Test"
);
-> at ie.cit.adf.services.FeedServiceImplTest.testSearchFeedItemsByCategory(FeedServiceImplTest.java:55)
However, there were other interactions with this mock:
-> at ie.cit.adf.services.FeedServiceImpl.createFeedItem(FeedServiceImpl.java:44)
at ie.cit.adf.services.FeedServiceImplTest.testSearchFeedItemsByCategory(FeedServiceImplTest.java:55)
Any idea how to mock this findAllByCategory()?
Here are the 2 classes:
Repository:
#Secured("ROLE_USER")
public class JdbcFeedRepository implements FeedRepository {
private JdbcTemplate jdbcTemplate;
private FeedItemsMapper feedItemsMapper = new FeedItemsMapper();
public JdbcFeedRepository(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
#Override
public FeedItem findById(String feedItemId) {
return jdbcTemplate.queryForObject(
"SELECT ID, TEXT, CATEGORY, AUTHOR FROM FEEDITEMS WHERE ID=?",
feedItemsMapper,
feedItemId
);
}
#Override
public List<FeedItem> findAll() {
return jdbcTemplate.query(
"SELECT ID, TEXT, CATEGORY, AUTHOR FROM FEEDITEMS",
feedItemsMapper
);
}
#Override
public List<FeedItem> findAllByCategory(String category) {
return jdbcTemplate.query(
"SELECT ID, TEXT, CATEGORY, AUTHOR FROM FEEDITEMS WHERE CATEGORY=?",
feedItemsMapper,
category
);
}
#Override
public List<FeedItem> findAllByAuthor(String author) {
return jdbcTemplate.query(
"SELECT ID, TEXT, CATEGORY, AUTHOR FROM FEEDITEMS WHERE AUTHOR=?",
feedItemsMapper,
author
);
}
#Override
public void add(FeedItem feedItem) {
jdbcTemplate.update(
"INSERT INTO FEEDITEMS VALUES(?,?,?,?)",
feedItem.getId(),
feedItem.getText(),
feedItem.getCategory(),
feedItem.getAuthor()
);
}
#Override
public void delete(String feedItemId) {
jdbcTemplate.update("DELETE FROM FEEDITEMS WHERE ID=?", feedItemId);
}
/**
* Returns the name of the currently logged in Author.
*
* #return String
*/
private String getCurrentUser() {
return SecurityContextHolder.getContext().getAuthentication().getName();
}
}
class FeedItemsMapper implements RowMapper<FeedItem> {
#Override
public FeedItem mapRow(ResultSet rs, int rowNum) throws SQLException {
FeedItem feedItem = new FeedItem();
feedItem.setId(rs.getString("ID"));
feedItem.setText(rs.getString("TEXT"));
feedItem.setCategory(rs.getString("CATEGORY"));
feedItem.setAuthor(rs.getString("AUTHOR"));
return feedItem;
}
}
Service:
#Transactional
public class FeedServiceImpl implements FeedService {
private FeedRepository repo;
public FeedServiceImpl(FeedRepository repo) {
this.repo = repo;
}
#Override
public FeedItem get(String feedItemId) {
return repo.findById(feedItemId);
}
#Override
public List<FeedItem> getAllFeedItems() {
return repo.findAll();
}
#Override
public List<FeedItem> getAllFeedItemsByCategory(String category) {
return repo.findAllByCategory(category);
}
#Override
public List<FeedItem> getAuthorFeedItems(String author) {
return repo.findAllByAuthor(author);
}
#Override
public FeedItem createFeedItem(String text, String category, String author) {
FeedItem feedItem = new FeedItem();
feedItem.setText(text);
feedItem.setCategory(category);
feedItem.setAuthor(author);
repo.add(feedItem);
return feedItem;
}
#Override
public void delete(String feedItemId) {
repo.delete(feedItemId);
}
}
It seems your code never calls:
feedRepository.findAllByCategory("Category Test");
But you added a verifier for it. Mockito verify ensures the method is called one time in your test. When this did not happen its complains with an exception.
Your test calls:
feedServiceTested.createFeedItem(...)
Which only calls the following methods on repo:
add(feedItem)
Which is your first verify. So at the moment it seems your code did not use findAllByCategory and so does the verify throws this exception.
Or is there a call in FeedItem to the repo? Then please provide the code for this class too.
How to perform Spring validation in MultiActionController?
Let's write the following one
public class Person {
private String name;
private Integer age;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And your MultiActionController
import static org.springframework.validation.ValidationUtils.*;
#Component
public class PersonController extends MultiActionController {
public PersonController() {
setMethodNameResolver(new InternalPathMethodNameResolver());
setValidators(new Validator[] {new Validator() {
public boolean supports(Class clazz) {
return clazz.isAssignableFrom(Person.class);
}
public void validate(Object command, Errors errors) {
rejectIfEmpty(errors, "age", "", "Age is required");
rejectIfEmptyOrWhitespace(errors, "name", "", "Name is required");
}
}});
}
public ModelAndView add(HttpServletRequest request, HttpServletResponse response, Person person) throws Exception {
// do something (save our Person object, for instance)
return new ModelAndView();
}
}
MultiActionController defines a property called validators where you should provide any Validator used by your MultiActionController. Here you can see a piece of code which is responsible for validating your Command object inside MultiActionController
ServletRequestDataBinder binder = ...
if (this.validators != null)
for (int i = 0; i < this.validators.length; i++) {
if (this.validators[i].supports(command.getClass())) {
ValidationUtils.invokeValidator(this.validators[i], command, binder.getBindingResult());
}
}
}
/**
* Notice closeNoCatch method
*/
binder.closeNoCatch();
closeNoCatch method says
Treats errors as fatal
So if your Validator returns any Error, closeNoCatch will throw a ServletRequestBindingException. But, you can catch it inside your MultiActionController method, as follows
public ModelAndView hanldeBindException(HttpServletRequest request, HttpServletResponse response, ServletRequestBindingException bindingException) {
// do what you want right here
BindException bindException = (BindException) bindingException.getRootCause();
return new ModelAndView("personValidatorView").addAllObjects(bindException.getModel());
}
In order to test, let's do the following one
#Test
public void failureValidation() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setRequestURI("http://127.0.0.1:8080/myContext/person/add.html");
/**
* Empty values
*/
request.addParameter("name", "");
request.addParameter("age", "");
PersonController personController = new PersonController();
ModelAndView mav = personController.handleRequest(request, new MockHttpServletResponse());
BindingResult bindingResult = (BindingResult) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + "command");
/**
* Our Validator rejected 2 Error
*/
assertTrue(bindingResult.getErrorCount() == 2);
for (Object object : bindingResult.getAllErrors()) {
if(object instanceof FieldError) {
FieldError fieldError = (FieldError) object;
System.out.println(fieldError.getField());
}
}
}