I'm working with Spring 3.2. In order to validate double values globally, I use CustomNumberEditor. The validation is indeed performed.
But when I input a number like 1234aaa, 123aa45 and so forth, I expect the NumberFormatException to be thrown but it doesn't. The docs says,
ParseException is caused, if the beginning of the specified string cannot be
parsed
Therefore, such values as mentioned above are parsed up to they are represented as numbers and the rest of the string is then omitted.
To avoid this, and to make it throw an exception, when such values are fed, I need to implement my own Property Editor by extending the PropertyEditorSupport class as mentioned in this question.
package numeric.format;
import java.beans.PropertyEditorSupport;
public final class StrictNumericFormat extends PropertyEditorSupport
{
#Override
public String getAsText()
{
System.out.println("value = "+this.getValue());
return ((Number)this.getValue()).toString();
}
#Override
public void setAsText(String text) throws IllegalArgumentException
{
System.out.println("value = "+text);
super.setValue(Double.parseDouble(text));
}
}
The editors I have specified inside a method annotated with the #InitBinder annotation are as follows.
package spring.databinder;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.context.request.WebRequest;
#ControllerAdvice
public final class GlobalDataBinder
{
#InitBinder
public void initBinder(WebDataBinder binder, WebRequest request)
{
DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
dateFormat.setLenient(false);
binder.setIgnoreInvalidFields(true);
binder.setIgnoreUnknownFields(true);
//binder.setAllowedFields("startDate");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
//The following is the CustomNumberEditor
NumberFormat numberFormat = NumberFormat.getInstance();
numberFormat.setGroupingUsed(false);
binder.registerCustomEditor(Double.class, new CustomNumberEditor(Double.class, numberFormat, false));
}
}
Since I'm using Spring 3.2, I can take advantage of #ControllerAdvice
Out of curiosity, the overridden methods from the PropertyEditorSupport class in the StrictNumericFormat class are never invoked and the statements that redirect the output to the console as specified inside of those methods (getAsText() and setAsText()) don't print anything on the server console.
I have tried all the approaches described in all the answers of that question but none worked for me. What am I missing here? Is this required to configure in some xml file(s)?
Clearly you have nowhere passed the StrictNumericFormat reference. You should register your editor like:
binder.registerCustomEditor(Double.class, new StrictNumericFormat());
BTW Spring 3.X introduced a new way achieving conversion:Converters
Related
I have using Jersey so far and I am doing my first implementation with JSON-B.
I am using Payara, so I working with Jersey and Yasson. I had an issue, because the serialized dates would always contain the "[UTC]" suffix.
I have managed to use an annotation on my date property, in my DTO. But I would like to configure that globally (in the JAX-RS application config?), instead of repeating myself on every date property. Is that possible? I haven't found anything so far...
Side question: I assume that it is possible to get rid of this "[UTC]" suffix, since it breaks all clients trying to parse the date. Any idea?
Thanks to this Github issue, I was able to solve my problem. Here is what I ended up writing in my code:
JSONConfigurator.java:
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import javax.json.bind.config.PropertyNamingStrategy;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
#Provider
public class JSONConfigurator implements ContextResolver<Jsonb> {
#Override
public Jsonb getContext(Class<?> type) {
JsonbConfig config = getJsonbConfig();
return JsonbBuilder
.newBuilder()
.withConfig(config)
.build();
}
private JsonbConfig getJsonbConfig() {
return new JsonbConfig()
.withDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", null);
}
}
And:
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;
#ApplicationPath("/api")
public class ApplicationConfig extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new HashSet<Class<?>>();
addRestResourceClasses(resources);
resources.add(JSONConfigurator.class);
return resources;
}
private void addRestResourceClasses(Set<Class<?>> resources) {
...
}
}
I am using RestTemplate get data from remote rest service and my code is like this.
ResponseEntity<List<MyObject >> responseEntity = restTemplate.exchange(request, responseType);
But rest service will return just text message saying no record found if there are no results and my above line of code will throw exception.
I could map result first to string and later use Jackson 2 ObjectMapper to map to MyObject.
ResponseEntity<String> responseEntity = restTemplate.exchange(request, responseType);
String jsonInput= response.getBody();
List<MyObject> myObjects = objectMapper.readValue(jsonInput, new TypeReference<List<MyObject>>(){});
But I don't like this approach. Is there any better solution for this.
First of all you could write a wrapper for the whole API. Annotate it with #Component and you can use it wherever you want though Springs DI. Have a look at this example project which shows of generated code for a resttemplate client by using swagger codegen.
As you said you tried implementing a custom responserrorhandler without success I assume that the API returns the response body "no record found" while the status code is 200.
Therefore you could create a custom AbstractHttpMessageConverter as mentioned in my second answer. Because you are using springs resttemplate which is using the objectmapper with jackson we don't event have to use this very general super class to create our own. We can use and extend the more suited AbstractJackson2HttpMessageConverter class.
An implementation for your specific use case could look as follows:
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
public class WeirdAPIJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
public static final String NO_RECORD_FOUND = "no record found";
public WeirdAPIJackson2HttpMessageConverter() {
// Create another constructor if you want to pass an already existing ObjectMapper
// Currently this HttpMessageConverter is applied for every MediaType, this is application-dependent
super(new ObjectMapper(), MediaType.ALL);
}
#Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
try (BufferedReader br = new BufferedReader(new InputStreamReader(inputMessage.getBody(), DEFAULT_CHARSET))) {
String responseBodyStr = br.lines().collect(Collectors.joining(System.lineSeparator()));
if (NO_RECORD_FOUND.equals(responseBodyStr)) {
JavaType javaType = super.getJavaType(type, contextClass);
if(Collection.class.isAssignableFrom(javaType.getRawClass())){
return Collections.emptyList();
} else if( Map.class.isAssignableFrom(javaType.getRawClass())){
return Collections.emptyMap();
}
return null;
}
}
return super.read(type, contextClass, inputMessage);
}
}
The custom HttpMessageConverter is checking the response body for your specific "no record found". If this is the case, we try to return a default value depending on the generic return type. Atm returning an empty list if the return type is a sub type of Collection, an empty set for Set and null for all other Class types.
Furthermore I created a RestClientTest using a MockRestServiceServer to demonstrate you how you can use your RestTemplate within the aforementioned API wrapper component and how to set it up to use our custom AbstractJackson2HttpMessageConverter.
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.ExpectedCount;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Optional;
import static org.junit.Assert.*;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {RestTemplateResponseErrorHandlerIntegrationTest.MyObject.class})
#RestClientTest
public class RestTemplateResponseErrorHandlerIntegrationTest {
static class MyObject {
// This just refers to your MyObject class which you mentioned in your answer
}
private final static String REQUEST_API_URL = "/api/myobjects/";
private final static String REQUEST_API_URL_SINGLE = "/api/myobjects/1";
#Autowired
private MockRestServiceServer server;
#Autowired
private RestTemplateBuilder builder;
#Test
public void test_custom_converter_on_weird_api_response_list() {
assertNotNull(this.builder);
assertNotNull(this.server);
RestTemplate restTemplate = this.builder
.messageConverters(new WeirdAPIJackson2HttpMessageConverter())
.build();
this.server.expect(ExpectedCount.once(), requestTo(REQUEST_API_URL))
.andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.OK).body(WeirdAPIJackson2HttpMessageConverter.NO_RECORD_FOUND));
this.server.expect(ExpectedCount.once(), requestTo(REQUEST_API_URL_SINGLE))
.andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.OK).body(WeirdAPIJackson2HttpMessageConverter.NO_RECORD_FOUND));
ResponseEntity<List<MyObject>> response = restTemplate.exchange(REQUEST_API_URL,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<MyObject>>() {
});
assertNotNull(response.getBody());
assertTrue(response.getBody().isEmpty());
Optional<MyObject> myObject = Optional.ofNullable(restTemplate.getForObject(REQUEST_API_URL_SINGLE, MyObject.class));
assertFalse(myObject.isPresent());
this.server.verify();
}
}
What I usually do in my projects with restTemplate is save the response in a java.util.Map and create a method that converts that Map in the object I want. Maybe saving the response in an abstract object like Map helps you with that exception problem.
For example, I make the request like this:
List<Map> list = null;
List<MyObject> listObjects = new ArrayList<MyObject>();
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class);
if (response != null && response.getStatusCode().value() == 200) {
list = (List<Map>) response.getBody().get("items"); // this depends on the response
for (Map item : list) { // we iterate for each one of the items of the list transforming it
MyObject myObject = transform(item);
listObjects.add(myObject);
}
}
The function transform() is a custom method made by me: MyObject transform(Map item); that receives a Map object and returns the object I want. You can check if there was no records found first instead of calling the method transform.
I have been searching for the answer and many answers didn't solve my problem even though they solved very similar problems.
So My problem is this : I have a path variable which may contain character "/". The same value also contains other special characters such as "." "+" "=" etc .basically all valid Base64 characters.
But Spring MVC throws 404 with logs saying no handler found. I tried using regular expressions in path variable as well but to no avail. so below id my code snippets :
http://localhost:8080/sale/public/viewSaleDetails/b91a03730a746a2b27e1c7bbbd94ddf6a9df593301cd96c606348df5eed235da.FkJJbOqEM8Xvhffe6FwUdQ8/mMCD4+fxpY7w5L9kbJ8=
is my URL. If you see it has / in path variable value. along with "." and "+" and "=". Spring maps this correctly if I remove / between character "m" and "8". but with / in value it just doesnt work. I tried a lot of things including character encoding filter,regex in pathvariable etc. Please help.
Also I dont want to use request parameters as far as possible.
#RequestMapping(value = "/public/viewSaleDetails/{id}", method = RequestMethod.GET)
is my mapping. Also the url is hit from the browser as it is without any URL encoding. I tracked it on browser network bar and it doesnt encode it as expected. I am using Spring 4.2.8 RELEASE version with java 8 and tomcat 8
There is open issue in spring Jira according matching slashes in path. And due to discussion it is not reasonable to change mathing strategy on framework level. The issue was created due to this stackoverflow post and I suggest creating value resolver according to the answer.
Here is example code for such resolver:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.List;
#SpringBootApplication
public class SampleSpringApp {
public static void main(String[] args) {
SpringApplication.run(SampleSpringApp.class, args);
}
}
#RestController
class SampleController {
#RequestMapping("/records/**")
public String getId(Id id) {
return id.id;
}
}
#Configuration
#EnableWebMvc
class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new IdResolver());
}
}
class IdResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return Id.class.isAssignableFrom(parameter.getParameterType());
}
#Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
String basePath = ((String) webRequest.getAttribute(
HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE,
RequestAttributes.SCOPE_REQUEST
)).replace("**", "");
String id = ((String) webRequest.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE,
RequestAttributes.SCOPE_REQUEST
)).replace(basePath, "");
return new Id(id);
}
}
class Id {
public final String id;
Id(String id) {
this.id = id;
}
}
I'm implementing a simple RESTful service using Spring Boot, with the interface defined by a .NET (I think) client. Their parameter names are snake_case, rather than camelCase, which obviously means I need to customise how they are mapped.
In the case of JSON input/output, that's fine, I've just customised the ObjectMapper, like so:
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
return objectMapper;
}
That works fine. Now my problem is form data. I have a Spring form like:
public class MyForm {
private String myValue;
public String getMyValue() {return myValue;}
public void setMyValue(String myValue) {this.myValue = myValue;}
}
But the requests I need to accept will look like:
POST /foo/bar HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
my_value=5
I feel like there must be some simple hook into Spring's binding, like the equivalent setting in Jackon's ObjectMapper, but I'm struggling to find one. The only somewhat-relevant post I can find on here is this one, about completely changing the parameter names, which has some suggestions that seem like overkill for my use case.
The simple solution is simply to use snake case for the fields in MyForm, which works fine, but is a bit ugly.
A final suggestion I've seen elsewhere is to use an interceptor to modify the request parameters on the way in, which seems like it would be straightforward but it feels like there are bound to be exceptions that make it non-trivial, and I'm concerned that having code hidden away in an interceptor makes it really hard to find when you hit the one obscure case where it doesn't work.
Is there some 'proper' Spring-y way of handling this that I'm missing, or do I just need to pick one of the above not-quite-perfect solutions?
probably you already have solved this issue, I was fighting with this today and answered a question on StackOverflow PT.
So here is the deal:
Create a filter to be executed before the request reach the controller, and format the parameters accordingly (from snake case to camel case on my scenario).
Talk is cheap, show me the code!
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.OncePerRequestFilter;
import com.google.common.base.CaseFormat;
#Configuration
public class AppConfig {
#Bean
public Filter snakeConverter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
final Map<String, String[]> formattedParams = new ConcurrentHashMap<>();
for (String param : request.getParameterMap().keySet()) {
String formattedParam = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, param);
formattedParams.put(formattedParam, request.getParameterValues(param));
}
filterChain.doFilter(new HttpServletRequestWrapper(request) {
#Override
public String getParameter(String name) {
return formattedParams.containsKey(name) ? formattedParams.get(name)[0] : null;
}
#Override
public Enumeration<String> getParameterNames() {
return Collections.enumeration(formattedParams.keySet());
}
#Override
public String[] getParameterValues(String name) {
return formattedParams.get(name);
}
#Override
public Map<String, String[]> getParameterMap() {
return formattedParams;
}
}, response);
}
};
}
}
The snakeConverter do the magic.
In there, the doFilterInternal is executed always before the request reach the controller, the parameters are stored in a new Map in their formatted form, and are forwarded to the controller through the filterChain.doFilter.
The HttpServletRequestWrapper do the job of provide our new parameters to the controller.
This code is completely based on the azhawkes filter.
Testing it using a simple controller in the the following URL: http://localhost:8080/snakecase?foo_bar=123
Can anyone help me with a Spring Boot problem?
I want to create a factory bean as part of my application context but I want to be able to instantiate it with injected property values. However it seems that Spring will load FactoryBeans before anything else as demonstrated here:
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.beans.factory.config.ListFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
#EnableAutoConfiguration
public class TestClass
{
#Value("${test.value}")
String value;
#Bean
public Object test1()
{
System.out.println("test.value=" + value );
List<String> list = new ArrayList<String>();
ListFactoryBean factory = new ListFactoryBean();
factory.setSourceList(list);
return factory;
}
public static void main(String[] args)
{
SpringApplication.run(TestClass.class, args);
}
}
When run with
java -Dtest.value=HELLO -jar myTest.jar
It loads in the value correctly:
test.value=HELLO
However, when I specify that the bean to be loaded is in fact a factory bean, and run it in the same way:
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.beans.factory.config.ListFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
#EnableAutoConfiguration
public class TestClass
{
#Value("${test.value}")
String value;
#Bean
public AbstractFactoryBean test1()
{
System.out.println("test.value=" + value );
List<String> list = new ArrayList<String>();
ListFactoryBean factory = new ListFactoryBean();
factory.setSourceList(list);
return factory;
}
public static void main(String[] args)
{
SpringApplication.run(TestClass.class, args);
}
}
The value is null because it hasn't been injected yet.
test.value=null
Is there any way around this?
Thanks
Spring often has to query bean definitions for the type of object they produce. Factory beans are always problematic because they can cause dependency cascades in a futile attempt to resolve all dynamic information available before asking for the type.
I think ListFactoryBean is insufficiently precise about its product type (getObjectType() can only return a non-generic List.class). You might be able to write your own factory that is parameterized with the correct generic type. Or you might get away with just declaring the #Bean to return a FactoryBean<List<String>.
Another tip is to move the #Bean definition to a separate class (e.g. a nested static one) so that it can be instantiated independently of the rest of the application context. E.g.
#EnableAutoConfiguration
public class TestClass
{
protected static class NestedConfiguration {
#Value("${test.value}")
String value;
#Bean
public FactoryBean<Properties> test1()
{
System.out.println("test.value=" + value );
// ...
return factory;
}
}
...
}
Not really a Boot question this one so you might consider changing the tags.
Take look at Empowering your apps with Spring Boot's property support
There is new annotation #EnableConfigurationProperties in Spring Boot Actuator
The Spring Environment is a collection of name-value pairs taken from (in order of decreasing precedence)
1) the command line,
2) the external configuration file,
3) System properties,
4) the OS environment.
There is also possible to define application properties (external configuration) in YAML format.