We have many #RestController receiving phrases in common language written by users. Phrases can be very long and contains punctuation, like periods and, of course, commas.
Simplified controller example:
#RequestMapping(value = "/countphrases", method = RequestMethod.PUT)
public String countPhrases(
#RequestParam(value = "phrase", required = false) String[] phrase) {
return "" + phrase.length;
}
Spring boot default behaviour is to split parameters values at comma, so the previous controller called with this url:
[...]/countphrases?phrase=john%20and%20me,%20you%and%her
Will return "2" istead of "1" like we want. In fact with the comma split the previous call is equivalent to:
[...]/countphrases?phrase=john%20and%20me&phrase=you%and%her
We work with natural language and we need to analyze phrases exactly how the users wrote them and to know exactly how many they wrote.
We tried this solution: https://stackoverflow.com/a/42134833/1085716 after adapting it to our spring boot version (2.0.5):
#Configuration
public class MvcConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
// we hoped this code could remove the "split strings at comma"
registry.removeConvertible(String.class, Collection.class);
}
}
But it doesn't work.
Anyone know how to globally remove the "spring boot split string parameters at comma" behaviour in spring boot 2.0.5?
I find the solution.
To override a default conversion we must add a new one. If we remove the old one only it doesn't work.
The correct (example) code should be:
#Configuration
public class MvcConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.removeConvertible(String.class, String[].class);
registry.addConverter(String.class, String[].class, noCommaSplitStringToArrayConverter());
}
#Bean
public Converter<String, String[]> noCommaSplitStringToArrayConverter() {
return new Converter<String, String[]>() {
#Override
public String[] convert(String source) {
String[] arrayWithOneElement = {source};
return arrayWithOneElement;
}
};
}
}
This way any controller like the one in the main question will not split parameters values:
[...]/countphrases?phrase=a,b will return 1 (and fq=["a,b"])
[...]/countphrases?phrase=a,b&phrase=c,d will return 2 (and fq=["a,b", "c,d"])
Replacing your formatter registry with a completely new list could make you loose some needed default formatters that would come with the framework. This will also disable all String-To-Collections parsing for the entire application, on every endpoint, such that if you want to a request filter such as the following at another endpoint, it won't work:
identifiers = 12,34,45,56,67
Solution:
Just change your delimiter into something else... # or ; or $
identifiers = 12;23;34;45;56
This is what I have been doing, so I don't mess with all the goodies in the formatter registry.
Related
I have a field in my entity that holds phone-number. According to the conventions of the project, I need to save it in E.164 format in the DB. At the moment I use #PrePersist and #PreUpdate annotations for changing the phone number to the specified format. This method is good for one or two entities but it becomes very error-prone when you have to repeat it over and over.
I was thinking that it would be awesome if I could put the code in annotation and the annotation reads the fields and changes its value just before the persistence something like what #LastModifiedDate and annotation do. I searched the web for the codes of this annotation but I didn't understand how they managed it.
How I can write an annotation that reads the value of a field and changes it before persistence, and how I can do it before some specific operations like delete (I want to set some params before deleting the object too)
Take a look at EntityListeners.
You can create a listener that checks your custom annotation and triggers the appropriate methods.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface TheCustomAnnotation{
}
#Entity
#EntityListeners(TheListener.class)
public class TheEntity {
#TheCustomAnnotation
private String phoneNumber;
public class TheListener {
#PrePersist
public void prePersist(Object target) {
for(Field field : target.getClass().getDeclaredFields()){
Annotation[] annotations = field.getDeclaredAnnotations();
// Iterate annotations and check if yours is in it.
}
}
This is just an example.
#Pattern is a pretty powerful annotation that would be a good fit for validations if you are experienced with regular expressions.
For example,
#Pattern(regexp="^[0-9]{3}-[0-9]{3}-[0-9]{4}$")
private String phoneNumber;
The downside is that this only works for Strings though.
If you are interested more in conversions than validations, you may want to look into #JsonDeserialize if you are using Jackson.
For example:
#JsonDeserialize(using=PhoneNumberDeserializer.class)
private String phoneNumber;
Pattern phonePattern = Pattern.compile("^[0-9]{3}(.+)[0-9]{3}(.+)[0-9]{4}$");
public class PhoneNumberDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
String phone = jsonParser.getText();
if (matcher.matches(phone)) {
Matcher matcher = phonePattern.matcher(phone);
for (int i = 1; i < matcher.groupCount(); i++) {
marcher.group(i).replaceAll(".*", "");
}
}
}
}
This will work for any type, not just strings.
Sorry it's a little convoluted, I was having fun reteaching myself.
In order to increase performance, my API is going to receive an array of string that I need to convert to an array of objects.
My array looks like this:
List<String> listPersons = ["1, Franck, 1980-01-01T00:00:00, 00.00", "2, Martin, 1989-01-01T00:00:00, 00.00"];
How could I easily convert it to a list of Persons (List), if possible using Java 8 so I don't have to create a loop and manually explode the String?
class Person {
private Integer id;
private String name;
private Date dateOfBirth;
// getter and setter
}
Ideally I'd like to automate this directly using SpringBoot - Using a custom converter such as:
public class StringToPersonConverter implements Converter<String, Person> {
#Override
public Person convert(String from) {
String[] data = from.split(",");
return new Person(Integer.parseInt(data[0]), data[1], new Date(data[2]));
}
}
Declaring the converter:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToCreditCardConverter());
}
}
And ideally map it from my controller directly?
#RequestMapping(value = "/insertPersons", method = RequestMethod.POST)
#ResponseBody
public String savePersons(#RequestBody List<Person> listPersons) {}
Unfortunately, it doesn't seem to detect my converter and it's throwing an error ;/
Any idea? Thanks
As a side note : String[] data = from.split(","); will not work with , as character separator because values use this character, for example : 1980-01-01T00:00:00, 00.00.
In order to increase performance, my API is going to receive an array
of string that I need to convert to an array of objects.
Here is a non answer. JSON is a format that consumes very few memory (literally characters) to convey the structure. So you don't need and even don't have to concatenate in a single String distinct information that here represent difference Person.
Designing an API that waits for unstructured JSON/text and restructuring the JSON/text in the backend in an anti pattern : it is not more efficient, it makes the API unclear and add boiler plate code both in the front and the back end.
This feels like it should be a simple thing, but I'm still pretty new to SpringBoot, and the whole Servlet ecosystem, so it's not readily apparent. I would love an interface similar to HandlerInterceptor that allows me to modify the request and response object once I'm done in a controller. Even better would be to decorate mapping annotation, so I can specify which controllers need the operation.
The problem I'm solving right now, though I anticipate expanding this in the future, is that I have an encrypted header coming into my application that I would like to decrypt for use in the controller and then encrypt again on the way out.
EDIT: For clarity.
I have a rest controller, something like:
#RestController
public class PojoService {
#GetMapping(value = "/path/to/resource")
public ResponseEntity<SomeClass> getLocationData(
#RequestHeader(value = "EncryptedHeader", required = false) String ecryptedHeaderValue) {
DecryptionObject decryptedHeader = new DecryptionObject(pageHeaderValue);
SomePojo result = getResult();
return decryptedHeader.decorateResponseWithEncryptedHeader(result);
}
}
I would love to not have the DecryptionObject on every mapping, but rather, before I even get to the mapping, I decrypt the header via some filter or hook and then re-encrypt the header on the way out. Then my code would look something like:
#RestController
public class PojoService {
#GetMapping(value = "/path/to/resource", decryptHeader="EncryptedHeader")
public ResponseEntity<SomeClass> getLocationData(
#RequestHeader(value = "EncryptedHeader", required = false) String decryptedHeaderValue) {
SomePojo result = getResult();
return result;
}
}
I found that the HandlerInterceptor doesn't work because I cannot modify the request or response in the interceptor. Hope that clarifies the issue.
You can still use HandlerInterceptor. Create your class implementing HandlerInterceptor, and then register it using another class which extends WebMvcConfigurer.
#EnableWebMvc
#Configuration
#ComponentScan
public class MyWebConfig implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new [...]); //Apply to all controllers
registry.addInterceptor(new [...]).addPathPatterns("path1","path2"); //Apply to specific paths to restrict to some controllers.
}
}
You also could do it using a Filter - create your Filter class and register it by declaring a #Bean of type FilterRegistrationBean - this also allows you to restrict to some paths.
UPDATE: You could do this with request attributes which can be set by interceptors (request.setAttribute("decryptedHeaderValue",<decrypted>). Or if you're specific about using headers, a filter would be more suitable for your purpose. Create a new wrapped request type that wraps the incoming request and does whatever you want, and pass this wrapper to the next filter in chain.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
[...]
HttpServletRequestWrapper decryptedRequest = new HttpServletRequestWrapper((HttpServletRequest) request) {
public String getHeader(String name) {
if (name.equals("DecryptedHeader")) {
String encrypted = super.getHeader("EncryptedHeader");
String decrypted = decrypt(encrypted);
return decrypted;
}
return super.getHeader(name); //Default behavior
}
}
chain.doFilter(decryptedRequest, response); //Pass on the custom request down
}
Then any class down the line (other filters, controllers etc) can just call request.getHeader("DecryptedHeader") to retrieve the decrypted header. This is just one of many similar approaches. You can restrict the paths for which this filter executes when registering it.
For response, there is a similar class HttpServletResponseWrapper which you can use for customization.
We can do this via addingAttribute in the interceptor
httpServletRequest.setAttribute(,);
I have three text files, they all contain data of the same type, but data is stored differently in each file.
I want to have one interface:
public interface ItemRepository() {
List<Item> getItems();
}
And instead of creating three implementations I want to create one implementation and use dependency injection to inject a path to the text file
and an analyser class for each text file:
public class ItemRepositoryImpl() implements ItemRepository {
Analyser analyser;
String path;
public ItemRepositoryImpl(Analyser analyser, String path) {
this.analyser = analyser;
this.path = path;
}
public List<Item> getItems() {
// Use injected analyser and a path to the text file to extract the data
}
}
How do I wire everything and inject the ItemRepositoryImpl into my controller?
I know I could simply do:
#Controller
public class ItemController {
#RequestMapping("/items1")
public List<Item> getItems1() {
ItemRepository itemRepository = new ItemRepositoryImpl(new Analyser1(), "file1.txt");
return itemRepository.getItems();
}
#RequestMapping("/items2")
public List<Item> getItems1() {
ItemRepository itemRepository = new ItemRepositoryImpl(new Analyser2(), "file2.txt");
return itemRepository.getItems();
}
#RequestMapping("/items3")
public List<Item> getItems1() {
ItemRepository itemRepository = new ItemRepositoryImpl(new Analyser3(), "file3.txt");
return itemRepository.getItems();
}
}
But I don't know how to configure Spring to autowire it.
You can achieve it in many different ways and it probably depends on your design.
One of them can be initialising 3 different analyzers in spring context and wiring all the three analyzers in ItemRepositoryImpl using '#Qualifier' annotation. With the help of an extra method parameter, ItemRepositoryImpl can decide which analyzer it should route the requests to.
For the path variable also you can follow a similar approach.
If your question is specific about how to wire the primitive type in the bean, check this post . It specifies how to initialize a String variable in spring context.
I have a class:
public class user{
private String id;
private MultiPartFile file;
**Getters And Setters**
}
And in the Controller:
#PostMapping(value="/upload)
public void upload(User user){
}
In the front end I post data with form-data.I can get the user object.
But when I add #RequestBody and #RequestParam,it can't works.
in my opinion,#RequestParam is used to binding parameter to simple class . when I use #RequestBody ,spring will find HttpMessageConverter to convert http request body to class.But I'm not sure about that.Does anyone can explain to me?
So, I believe we are talking about org.springframework.web.multipart.MultipartFile, which is to be used together with #RequestParam variable. The mechanism is somewhat special in this case.
I had a similar problem, and what I ended up using was org.springframework.web.multipart.commons.CommonsMultipartResolver. From frontend I've constructed multipart request with two parts, in your scenario it could be user (containing just JSON data) and file (containing the file itself), e.g.:
#PostMapping(value="/upload")
public void upload(#RequestParam("user") User user, #RequestParam("file") MultipartFile file){
...
}
But then, you need to configure custom serialization of the User part, which can be done using org.springframework.web.multipart.commons.CommonsMultipartResolver. You can configure it using bean config like this:
#Configuration
public class MappingConfig {
#Order(Integer.MIN_VALUE)
#Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
#Bean
public Converter<String, User> stringToUser() {
return new Converter<String, User>() {
#Override
public User convert(String jsonString) {
return new Gson().fromJson(jsonString, User.class);
}
};
}
...
}
Also, as you can see I am using Gson manually, I couldn't find a better way how to do it. Also, it doesn't play with Java 8 lambdas, so it cannot be shortened (because of explicit types are needed for it to work).
I hope that this will at least points you to a right path.