In a service file I would simply use #Value and initialize the variable instially there. I have tried this approach in a model class but (I assume how things get autowired and that its a model class) this results in it always being null.
The need for this comes out that in different environments the default value is always different.
#Value("${type}")
private String type;
I would avoid trying to use Spring logic inside the models as they are not Spring beans themselves. Maybe use some form of a creational (pattern) bean in which the models are constructed, for example:
#Component
public class ModelFactory {
#Value("${some.value}")
private String someValue;
public SomeModel createNewInstance(Class<SomeModel> clazz) {
return new SomeModel(someValue);
}
}
public class SomeModel {
private String someValue;
public SomeModel(String someValue) {
this.someValue = someValue;
}
public String getSomeValue() {
return someValue;
}
}
#ExtendWith({SpringExtension.class})
#TestPropertySource(properties = "some.value=" + ModelFactoryTest.TEST_VALUE)
#Import(ModelFactory.class)
class ModelFactoryTest {
protected static final String TEST_VALUE = "testValue";
#Autowired
private ModelFactory modelFactory;
#Test
public void test() {
SomeModel someModel = modelFactory.createNewInstance(SomeModel.class);
Assertions.assertEquals(TEST_VALUE, someModel.getSomeValue());
}
}
Related
I have an enum class as such:
ONE("1", "Description1"),
TWO("2", "Description2");
String value;
String description;
MyEnum(String value, String description) {
this.value = value;
this.description = description;
}
#Override
public String toString() {
return this.value;
}
#JsonValue
public String value() {
return this.value;
}
The API I am interacting with is expecting a param with type String and the values can be comma separated.
For example: api.com/test?param1=1,2
I configured a feign client with the url api.com/test
And then created a POJO like so
public class POJO {
private List<MyEnum> param1;
}
And in my feign client I have:
#RequestMapping(method = RequestMethod.GET)
MyResponse getResponse(#SpringQueryMap POJO request);
Is it possible to somehow turn the List of Enums to a List of String before the API call is made via some Spring approach?
As of right now, when I pass a List of Enums, it is only taking into account the last Enum within this list.
UPDATE: I annotated the property I want to convert to a list using #JsonSerialize(converter=abc.class). However #SpringQueryMap doesn't seem to honor that serialization..
Yes is possible, you need to create an interceptor and in that method do the mapping.
This topic may be for you.
Spring - Execute code before controller's method is invoked
So turns out #JsonSerialize was not working with #SpringQueryMap
So I did have to add an interceptor.
Like so:
public class MyInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate requestTemplate) {
if(requestTemplate.queries().containsKey("param1")) {
requestTemplate.query("param1", convert(requestTemplate.queries().get("param1")));
}
}
//convert list to a string
public String convert(Collection<String> values) {
final String s = String.join(",", values.stream().map(Object::toString).collect(Collectors.toList()));
return s;
}
}
And then in my Feign config class added this:
#Bean
public MyInterceptor myInterceptor() {
return new MyInterceptor();
}
I'm sending a POST-Request from angular to spring. Its getting deserialized mostly correct, but one nested object is getting deserialized as an empty Object.
My Angular interfaces look as follows:
// ClientMedicalModal.ts
export interface ClientMedicalModal {
clientMedicalId: number;
client: ClientModel;
medicalEvidence: MedicalEvidence;
}
// ClientModal.ts
export interface ClientModal {
clientId: number;
}
// MedicalEvidenceModal.ts
export interface MedicalEvidenceModal {
B001: string;
B003: string;
B004: string;
}
My Java-Objects look like this:
public class ClientMedical implements Serializable {
private Integer clientMedicalId;
private Client client;
private MedicalEvidence medicalEvidence;
// getter and setter
}
public class Client implements Serializable {
private Integer clientId;
// getter and setter
}
public class MedicalEvidence implements Serializable {
private String B001;
private String B003;
private String B004;
public String getB001() {
return B001;
}
public MedicalEvidence setB001(String b001) {
B001 = b001;
}
// all other getter and setter
}
When I check the post message from my browser everything seems to be okay:
{"medicalEvidence":{"B001":"Test","B003":"TestMessage","B004":"Whatever"},"client":{"clientId":1}}
Debugging in Spring I get the request, there is a Client-Object with clientId = 1, but the ClientEvidence-Object is empty, all B00* fields are null.
See here the debugging values
Spring form binding binds the form parameters to respective fields for Client class, but MedicalEvidence is blank, so Spring instantiates a new MedicalEvidence class with all fields having null values. Why does the parameters does not get bound to the MedicalEvidence's class fields but to Client's class (and all other classes I'm using the same way)? Btw. It does not work either if I just send MedicalEvidence from Angular. The object params are still all empty.
Try using b001, b002,.. as names, the first letter should not be uppercase in your use case, except you want to use some annotation. And use 'this.' in the setter method.
public class MedicalEvidence implements Serializable {
private String b001;
private String b003;
private String b004;
^^^^
public String getB001() {
return b001;
}
public MedicalEvidence setB001(String b001) {
this.b001 = b001;
^^^^^
}
Say I have two different APIs: A and B, that get the same POJO C.
POJO C has 2 different fields x and y.
public class C
{
String x;
String y;
}
Is it possible to set some conditional validation annotation,
so when passing POJO c to API A, only field x is mandatory,
and while for API B, both fields x and y are mandatory?
Thanks.
You can use validations for this kind of scenario
For example :-
import javax.validation.constraints.NotEmpty;
public class C {
#NotEmpty
String X;
String Y;
}
And you may also decide for the use of #NotEmpty over #NotNull for checking if it is not empty.
And on controller level you can make use of something like below:-
methodGet(#Valid #RequestBody final C c)
this methodGet = controller level method
Since you want different validations for the same POJO. You will have to use custom validation.
Basically you will have to create 2 custom annotaions. And then apply those annotaions on your respective Apis.
You can follow this tutorial.
Custom Validator
The simpliest way to do it, would be using validation groups. Please try :
Controller :
#RestController
public class FooController {
#PostMapping("/api1")
public Foo foo(#Validated(Api1Validated.class) Foo foo) {
return foo;
}
#PostMapping("/api2")
public Foo foo2(#Validated(Api2Validated.class) Foo foo) {
return foo;
}
}
Domain class :
#Data
public class Foo {
#NotEmpty(groups = {Api1Validated.class, Api2Validated.class})
private String name;
#NotNull(groups = {Api2Validated.class})
private Integer age;
}
Validation interfaces :
public interface Api1Validated {
}
public interface Api2Validated {
}
Unit test :
#SpringBootTest
#AutoConfigureMockMvc
public class FooControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Test
public void test() throws Exception {
Foo foo = new Foo();
foo.setName("foo");
this.mockMvc.perform(post("/api1")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(foo)))
.andDo(print())
.andExpect(status().isOk());
this.mockMvc.perform(post("/api2")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(foo)))
.andDo(print())
.andExpect(status().isBadRequest());
}
}
Versions : spring boot 2.3.4, junit 5.7.0
I'm creating a Spring boot REST API which should take 2 Lists of custom objects. I'm not able to correctly pass a POST body to the API I've created. Any idea what might be going wrong ?
Below is my code :
Controller Class Method :
// Main controller Class which is called from the REST API. Just the POST method for now.
#RequestMapping(value = "/question1/solution/", method = RequestMethod.POST)
public List<Plan> returnSolution(#RequestBody List<Plan> inputPlans, #RequestBody List<Feature> inputFeatures) {
logger.info("Plans received from user are : " + inputPlans.toString());
return planService.findBestPlan(inputPlans, inputFeatures);
}
Plan Class , this will contain the Feature class objects in an array:
public class Plan {
public Plan(String planName, double planCost, Feature[] features) {
this.planName = planName;
this.planCost = planCost;
this.features = features;
}
public Plan() {
}
private String planName;
private double planCost;
Feature[] features;
public String getPlanName() {
return planName;
}
// getters & setters
}
Feature POJO Class :
// Feature will contain features like - email , archive etc.
public class Feature implements Comparable<Feature> {
public Feature(String featureName) {
this.featureName = featureName;
}
public Feature() {
}
private String featureName;
// Getters / Setters
#Override
public int compareTo(Feature inputFeature) {
return this.featureName.compareTo(inputFeature.getFeatureName());
}
}
You cannot use #RequestBody twice!
You should create a class that holds the two lists and use that class with #RequestBody
You should create json like this:
{
"inputPlans":[],
"inputFeatures":[]
}
and create Class like this:
public class SolutionRequestBody {
private List<Plan> inputPlans;
private List<Feature> inputFeatures;
//setters and getters
}
POST mapping like this:
#RequestMapping(value = "/question1/solution/", method = RequestMethod.POST)
public List<Plan> returnSolution(#RequestBody SolutionRequestBody solution) {
logger.info("Plans received from user are : " + solution.getInputPlans().toString());
return planService.findBestPlan(solution);
}
I have the next hierarchy:
public class A extends B {
#Valid
public A(String languageCode, C token) {
super(languageCode, token);
}
}
The B class has a property call language code that should be validated with #NotEmpty only if a service has the #Validated({RequiringLanguageCode.class})
public class B extends D {
private String languageCode;
#Valid
public B(String languageCode, C token) {
super(token);
this.languageCode = languageCode;
}
#NotEmpty(groups = {RequiringLanguageCode.class})
public String getLanguageCode() {
return languageCode;
}
}
Now, D is the base class that has a C property that should be validated as NotNull and also the values that are inside the C class.
public class D {
private C token;
#Valid
public D(C token) {
this.token = token;
}
#NotNull
public C getToken() {
return token;
}
}
C class contains two String that are validated as #NotEmpty:
public class C {
private String value1;
private String value2;
public C(String value1,
String value2) {
this.value1 = value1;
this.value2 = value2;
}
#NotEmpty
public String getValue1() {
return value1;
}
#NotEmpty
public String getValue2() {
return value2;
}
}
When trying to test this using mockito the values of C class aren't validated if the token values (value1 and value2) are empty.
Can somebody help me?
Does somebody have any idea about what is happening?
The test are as followed:
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("test")
public class ATest {
#Autowired
private AAssembler aAssembler;
#Mock
A request;
#Mock
C token;
#Test
public void test() {
when(token.getValue1()).thenReturn("");
when(token.getValue2()).thenReturn("key");
when(request.getToken()).thenReturn(token);
assertThatIllegalArgumentException()
.isThrownBy(() -> aAssembler.build(request));
}
}
The AAssembler is annotated as #Validated({RequiringLanguageCode.class})
The reason why I launch an IllegalArgumentException instead of ConstraintViolationException is something out of scope of this problem. I catch the constraint violation an throw the IllegalArgumentException instead.
The Assembler build method has also a constraint annotated as :
public Response build(#Valid #NotNull A request) {
....
}
I would be very thankful if somebody can help me. Thanks anyway.
I had the same problem javax.validation.ConstraintDeclarationException: HV000131: A method return value must not be marked for cascaded validation more than once in a class hierarchy, but the following two methods are marked as such: while testing REST services annotated with javax.validation.valid.
Based on this AutoValue issue I thought maybe the same happens here, too. Mockito copies by default all annotations. So I was able to resolve this problem by preventing this.
A stub = Mockito.mock(A.class, withSettings().withoutAnnotations());
The disadvantage compared to the AutoValue solution no annotation will be copied. So if you need them you're stuck.
Regards Christian