I have two properties in my class. I changed the names, but one of the properties ends with two uppercase letters and the other does not.
private final List<String> variableAB = new ArrayList<>();
private final List<String> variableWord = new ArrayList<>();
and they both have the same getter setters:
public void setVariableAB(String recipients)
{
variableAB.clear();
if (StringUtils.isNotBlank(recipients)){
variableAB.addAll(Arrays.asList(recipients.split(PARAM_SEPARATOR)));
}
}
public List<String> getVariableAB()
{
return Collections.unmodifiableList(this.variableAB);
}
public void setVariableWord(String recipients)
{
variableWord.clear();
if (StringUtils.isNotBlank(recipients)){
variableWord.addAll(Arrays.asList(recipients.split(PARAM_SEPARATOR)));
}
}
public List<String> getVariableWord()
{
return Collections.unmodifiableList(this.variableWord);
}
When I try to set the property variableAB with
PropertyUtils.setSimpleProperty()
I get the error in the title, while debugging it turns out the PropertyUtil can recognize the read method, but does not find the write method.
I thought it was a problem with the name of the varibale so I changed it to not end with two uppercase letters but it did not help. My second guess was a type mismatch in the setter parameters; however setting variableWord does not produce this error.
Related
This question already has answers here:
Spring's #RequestParam with Enum
(9 answers)
Closed 3 years ago.
I have an Enum like below
public enum Customer {
RETAIL("retail"),
FREELANCER("FreeLancer"),
MARKET("market"),
PUBLICATION("publication");
private String contentType;
private static final Map<String,Customer> contentTypeMap;
public String getContentType(){
return this.contentType;
}
static {
Map<String,Customer> map = new ConcurrentHashMap<>();
for(Customer type : Customer.values ()){
map.put (type.getContentType (),type);
}
contentTypeMap = map;
}
Customer(String contentType){
this.contentType=contentType;
}
public static Customer getContentType(String contentType){
return contentTypeMap.get (contentType);
}
}
This enum represents the type of customer.
We have an API that return the customer details
#RequestMapping(value="/getData", method=RequestMethod.GET, produces="application/json")
public BatchResponse triggerBatchJob(
#RequestParam(value="updateFrom", required=false) #DateTimeFormat(pattern="yyyyMMdd") String updateFrom,
#RequestParam(value="updateTo", required=false) #DateTimeFormat(pattern="yyyyMMdd") String updateTo,
#RequestParam(value="customerType") (VALIDATE_HERE)String customerType) {
// ...
}
I need to validate the customerType value to be the ones present in the Enum, Is there a way to validate the same with the method declaration as I have done in the case of date rather than method body by using getContentType method or something.
Please help.
Change your method to following:
#RequestMapping(value="/getData", method=RequestMethod.GET, produces="application/json")
public BatchResponse triggerBatchJob(
#RequestParam(value="updateFrom", required=false) #DateTimeFormat(pattern="yyyyMMdd") String updateFrom,
#RequestParam(value="updateTo", required=false) #DateTimeFormat(pattern="yyyyMMdd") String updateTo,
#RequestParam(value="customerType") CustomerType customerType) {
// ...
}
i.e. customerType type should be CustomerType not String. Now only values those match enum will be mapped.
Note:- The values will have to be provided is specific format i.e. enum name itself e.g. in your case FREELANCER,RETAIL, PUBLICATION etc values should be passed in request.
Edit
As requested by OP below is customizing the enum handling from String:
Add #initBinder in the controller and add following method:
#InitBinder
public void initBinder(final WebDataBinder webdataBinder) {
webdataBinder.registerCustomEditor(Customer.class, new CustomerConverter());
}
and declare a converter class as below:
import java.beans.PropertyEditorSupport;
public class CustomerConverter extends PropertyEditorSupport{
public void setAsText(final String text) throws IllegalArgumentException {
System.out.println("--->"+Customer.getContentType(text));
setValue(Customer.getContentType(text));
}¡¡
}
Added System.out.println to show that value is interpreted and printed as expected.
A simple null check will do
Customer customer = Customer.getContentType(customerType);
if (customer == null) {
throw new Exception("Invalid Customer type");// use validation exception
}
I am trying to use mapstruct to transform an object as below
Source
MainObject
{
String key;
List<ChildObject> children;
}
ChildObject{
String childVar1;
String childVar2;
}
Target
List<TargetObj> targetObjects;
TargetObj{
String key;
String var1;
String var2;
}
I need to prepare a list of TargetObj instances with the key mapped from the key from MainObject and var1 and var2 mapped from ChildObject.
I tried to use ObjectFactory and Decorator as mentioned in the mapstruct documentation. But couldn't find a way to get this done. Both cases I got an error which states cannot return iterable object from non iterable parameters.
You can try and use a combination of #BeforeMapping or #AfterMapping with the #Context.
Your mapper can look like:
#Mapper
public interface MyMapper {
default List<TargetObj> map(MainObject source) {
if (source == null) {
return Collections.emptyList(); // or null or whatever you prefer
}
return map(source.getChildren(), new CustomContext(source));
}
List<TargetObject> map(List<ChildObject> children, #Context CustomContext context);
#Mapping(target = "key", ignore = true) // key is mapped in the context
TargetObject map(ChildObject child, #Context CustomContext context);
}
And the custom context would look something like:
public class CustomContext {
protected final MainObject mainObject;
public CustomContext(MainObject mainObject) {
this.mainObject = mainObject;
}
#AfterMapping // of #BeforeMapping
public void afterChild(#MappingTarget ChildObject child) {
child.setKey(mainObject.getKey());
// More complex mappings if needed
}
}
The goal is to do manual mapping from your MainObject to the List<TargetObj> by using other methods that MapStruct will generate
Assuming that we have an object with the following attributes:
public class MyObject {
private String attr1;
private Integer attr2;
//...
public String getAttr1() {
return this.attr1;
}
public Integer getAttr2() {
return this.attr2;
}
}
One way of sorting a list mylist of this object, based on its attribute attr1 is:
mylist.sort(Comparator.comparing(MyObject::getAttr1));
Is it possible to use this code inside a method in a dynamic way and replace the getAttr1 part with a method that returns the getter of an attribute of the object based on its name? Something like:
public void sortListByAttr(List<MyObject> list, String attr) {
list.sort(Comparator.comparing(MyObject::getGetterByAttr(attr)));
}
The MyObject::getGetterByAttr(attr) part does not compile, I wrote it just as an example to explain my idea
I tried to implement a method with the following code new PropertyDescriptor(attr, MyObject.class).getReadMethod().invoke(new MyObject()) but It's still not possible to call a method with a parameter from the comparing method
You could add a method like
public static Function<MyObject,Object> getGetterByAttr(String s) {
switch(s) {
case "attr1": return MyObject::getAttr1;
case "attr2": return MyObject::getAttr2;
}
throw new IllegalArgumentException(s);
}
to your class, but the returned function is not suitable for Comparator.comparing, as it expects a type fulfilling U extends Comparable<? super U> and while each of String and Integer is capable of fulfilling this constraint in an individual invocation, there is no way to declare a generic return type for getGetterByAttr to allow both type and be still compatible with the declaration of comparing.
An alternative would be a factory for complete Comparators.
public static Comparator<MyObject> getComparator(String s) {
switch(s) {
case "attr1": return Comparator.comparing(MyObject::getAttr1);
case "attr2": return Comparator.comparing(MyObject::getAttr2);
}
throw new IllegalArgumentException(s);
}
to be used like
public void sortListByAttr(List<MyObject> list, String attr) {
list.sort(getComparator(attr));
}
This has the advantage that it also may support properties whose type is not Comparable and requires a custom Comparator. Also, more efficient comparators for primitive types (e.g. using comparingInt) would be possible.
You may also consider using a Map instead of switch:
private static Map<String,Comparator<MyObject>> COMPARATORS;
static {
Map<String,Comparator<MyObject>> comparators=new HashMap<>();
comparators.put("attr1", Comparator.comparing(MyObject::getAttr1));
comparators.put("attr2", Comparator.comparing(MyObject::getAttr2));
COMPARATORS = Collections.unmodifiableMap(comparators);
}
public static Comparator<MyObject> getComparator(String s) {
Comparator<MyObject> comparator = COMPARATORS.get(s);
if(comparator != null) return comparator;
throw new IllegalArgumentException(s);
}
More dynamic is only possible via Reflection, but this would complicate the code, add a lot of potential error source, with only little benefit, considering that you need only to add one line of source code for adding support for another property in either of the examples above. After all, the set of defined properties gets fixed at compile time.
You could also have a single place where this comparators would be defined:
static enum MyObjectComparator {
ATTR1("attr1", Comparator.comparing(MyObject::getAttr1));
MyObjectComparator(String attrName, Comparator<MyObject> comparator) {
this.comparator = comparator;
this.attrName = attrName;
}
private final Comparator<MyObject> comparator;
private final String attrName;
private static MyObjectComparator[] allValues = MyObjectComparator.values();
public static Comparator<MyObject> findByValue(String value) {
return Arrays.stream(allValues)
.filter(x -> x.attrName.equalsIgnoreCase(value))
.map(x -> x.comparator)
.findAny()
.orElseThrow(RuntimeException::new);
}
}
And your usage would be:
public void sortListByAttr(List<MyObject> list, String attr) {
list.sort(MyObjectComparator.findByValue(attr));
}
I want to create a page where a person sees a list of users and there are check boxes next to each of them that the person can click to have them deleted.
In my MVC that consumes a REST API, I want to send a List of User objects to the REST API.
Can the #RequestParam annotation support that?
For example:
#RequestMapping(method = RequestMethod.DELETE, value = "/delete")
public #ResponseBody Integer delete(
#RequestParam("users") List<Users> list) {
Integer deleteCount = 0;
for (User u : list) {
if (u != null) {
repo.delete(u);
++deleteCount;
}
}
return deleteCount;
}
In the MVC client, the url would be:
List list = new ArrayList<User>();
....
String url = "http://restapi/delete?users=" + list;
Request parameters are a Multimap of String to String. You cannot pass a complex object as request param.
But if you just pass the username that should work - see how to capture multiple parameters using #RequestParam using spring mvc?
#RequestParam("users") List<String> list
But I think it would be better to just use the request body to pass information.
Spring mvc can support List<Object>, Set<Object> and Map<Object> param, but without #RequestParam.
Take List<Object> as example, if your object is User.java, and it like this:
public class User {
private String name;
private int age;
// getter and setter
}
And you want pass a param of List<User>, you can use url like this
http://127.0.0.1:8080/list?users[0].name=Alice&users[0].age=26&users[1].name=Bob&users[1].age=16
Remember to encode the url, the url after encoded is like this:
http://127.0.0.1:8080/list?users%5B0%5D.name=Alice&users%5B0%5D.age=26&users%5B1%5D.name=Bob&users%5B1%5D.age=16
Example of List<Object>, Set<Object> and Map<Object> is displayed in my github.
Just a reminder, any List of custom objects might require custom converters to be registered, like:
#Bean
public Converter<String, CustomObject> stringToCustomObjectConverter() {
return new Converter<>() {
#Override
public CustomObject convert(String str) {
return new ObjectMapper().readValue(str, CustomObject.class);
}
};
}
#Bean
public Converter<String, List<CustomObject>> stringToListCustomObjectConverter() {
return new Converter<>() {
#Override
public List<CustomObject> convert(String str) {
return new ObjectMapper().readValue(str, new TypeReference<>() {
});
}
};
}
So you can cover custom cases like:
/api/some-api?custom={"name":"Bla 1","age":20}
/api/some-api?custom={"name":"Bla 1","age":20}&custom={"name":"Bla 2","age":30}
/api/some-api?custom=[{"name":"Bla 1","age":20},{"name":"Bla 2","age":30}]
where: #RequestParam("custom") List customObjects
I have found an interesting bug/feature while writing webservice. I am returning JSON data for selection filter in frontend. Then these selections are returned me back to get data. I am sending it to a database so I need exactly the same format.
Problem is when there are more then two spaces in its name. On JSON output it removes any number of spaces and leaves only one. But I need all of them. How can I force RestController to leave there all spaces?
#RestController
#RequestMapping("/")
public class FilterController {
private static final Logger log = Logger.getLogger(FilterController.class);
#Autowired
SentimentService sentimentService;
#RequestMapping(value="/filter", method=RequestMethod.GET)
public Filter getValues(#RequestParam(value="sources", defaultValue="50") int sourceNb) {
Filter filter = sentimentService.filterGetValue();
return filter;
}
}
This is my controller. Filter is object with tree structure. One of them is Product layer. I even added sysout there. The spaces are saved in the object but not passed to JSON output.
public class Product {
private String name;
public String getName() {
System.out.println("Name: " + name); // it really has two spaces there
return name;
}
public void setName(String value) {
this.name = value;
}
}
Is there any annotation I need to add to my class variable to be left as it is? I couldn't find anything useful so I just hope that it can be done easily. Thanks.