I'm working on a sample project using :
JAX-RS (Jersey 2)
JSR-303 Bean validaiton
Jersey Test for testing
Grizzly Http container
Jackson 2.7.3
Before adding the #JsonIgnore and #JsonProperty everything was working as expected, i was able to run my tests without a problem while performing bean validation on my object properties.
Normally The "password" field should not be available for deserialization so i marked its getter with #JsonIgnore and its setter with #JsonProperty so as to allow serializing it when saving a new account object.
When launching my test i receive a 400 code error with following http response result :
avr. 26, 2016 12:25:29 PM org.glassfish.jersey.filter.LoggingFilter log
INFO: 1 * Client response received on thread main
1 < 400
1 < Connection: close
1 < Content-Length: 172
1 < Content-Type: application/json
1 < Date: Tue, 26 Apr 2016 11:25:29 GMT
1 < Vary: Accept
[{"message":"may not be null","messageTemplate":"{javax.validation.constraints.NotNull.message}","path":"AccountEndpoint.saveAccount.account.password","invalidValue":null}]
Note : When removing the #JsonIgnore annotation, no validation error
is received of course
My Resource :
#Path("/account")
public class AccountEndpoint {
#POST
#Path("/save/")
#Produces(MediaType.APPLICATION_JSON)
public Response saveAccount(#Valid Account account){
Account returned = accountService.saveAccount(account);
return Response.status(200).entity(returned).build();
}
My Pojo Class :
public class Account implements Serializable {
private String username;
private String password;
// -- [username] ------------------------
#Size(max = 45)
#NotNull
#Column(name = "username", length = 45,unique=true)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#NotNull
#Size(max = 45)
#Column(name = "password", length = 45)
#JsonIgnore
public String getPassword() {
return password;
}
#JsonProperty
public void setPassword(String password) {
this.password = password;
}
}
My Unit Test :
public class AccountTest extends JerseyTest {
#Before
public void setUp() throws Exception {
super.setUp();
}
#After
public void after() throws Exception {
super.tearDown();
}
#Override
protected TestContainerFactory getTestContainerFactory() {
return new GrizzlyWebTestContainerFactory();
}
#Override
protected DeploymentContext configureDeployment() {
enable(TestProperties.LOG_TRAFFIC);
enable(TestProperties.DUMP_ENTITY);
return ServletDeploymentContext.forServlet(new ServletContainer(new
JerseyConfig()))
.contextParam("contextConfigLocation",TEST_APP_CONTEXT)
.addListener(org.springframework.web.context.ContextLoaderListener.class)
.servletPath("/").build();
}
#Test
public void testSave_New_User_Account {
Account account = new Account();
account.setUsername("Test");
account.setPassword("Test");
Response response = target("/account/save").request().
post(Entity.entity(account,MediaType.APPLICATION_JSON));
assertEquals(200, response.getStatus());
}
}
JeseyConfig Class :
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
packages("org.medel.endpoint");
}
}
It's because on the client side the object is also being serialized by Jackson. And because the password property is ignored on reads, it never get serialized. So the request is being sent with no password. You might just want to create another test class for the entity, with no annotations, just for the client testing.
Edit
So just for clarity, the problem is with the client, not the server. When the client tries to serialize the Account, it doesn't serialize the password property, as it's ignored.
You can test it, but sending the JSON as a string, that way Jackson doesn't get involved. You will see that it works fine
.post(Entity.json("{\"username\":\"user\", \"password\":\"pass\"}"));
Related
I got the following test entity:
public class Test {
public String id;
public String name;
}
My test resource looks like this:
#Path("test")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class TestResource {
#Path("{id}")
#POST
public Test test(#Valid Test test){
return test;
}
}
If I do a POST with name as request body, I will end up with an entity which has a name but no id set. If I want to have set the id, I define a #PathParam("id") String id and then set the id with test.id = id. That is what I am using right now.
In this case, if I put a #NotNull constraint to the ID, the validation fails.
How can I 'put' the parsed ID to the request body, before the validation is happening? Ideal, not manually in any case.
You should remove the #NotNull annotation and ensure that no ID is given from the POST request. And in your GET endpoints ensure that the field is not null, probably with an own annotation. I did this with my own annotation #TestIdNull(false).
In your code you would place the annotation like this:
#Path("{id}")
#POST
public Test test(#TestIdNull(true) Test test){
return test;
}
while the annotation interface looks quite simple:
#Target({ElementType.PARAMETER,ElementType.TYPE_USE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = PetIdNullValidator.class)
public #interface TestIdNull {
String message() default "Post request. ID value forbidden.";
boolean value() default true;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and the implementation would be
#Provider
#NoArgsConstructor
public class TestIdNullValidator implements ConstraintValidator<TestIdNull, Test> {
private boolean switchNullCheck;
/**
* {#inheritDoc}
*/
#Override
public void initialize(final TestIdNull constraintAnnotation) {
switchNullCheck = constraintAnnotation.value();
}
/**
* {#inheritDoc}
*/
#Override
public boolean isValid(final Test test, final ConstraintValidatorContext context) {
if (null == test) {
return true;
}
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("ENTER YOUR MESSAGE HERE").addConstraintViolation();
return switchNullCheck ? test.id() == null : test.id() != null;
}
}
On your "way back" (the GET methods) you must ensure the ID is not null with placing the annotation to your DTO. Code could look like this:
#TestIdNull(false)
final Test test = mapper.map(entity, Test.class);
I know it's not a very elegant way but your idea placing the ID in a POST first and then validate is not possible I'm afraid.
Config client is unable to fetch the changed property values from the modified property files in Git. I need assistance in resolving this issue.
I created a new spring config server and client. Initially the values from the property files were fetched properly. When i changed the values in the property file, the client was still returning old values. I tried POSTing to http://localhost:8080/actuator/refresh and even after that, the old values are returned. Finally i deleted the property file from the git repository and the client still returns old values.
Config Server bootstrap.properties
spring.application.name=ConfigServer
server.port=8888
encrypt.key=123456
spring.security.user.password=configpassword123
spring.cloud.config.server.git.uri=https://some-repository/ConfigRepo.git
spring.cloud.config.server.git.username=git_user
spring.cloud.config.server.git.password=git_password
ConfigServer.java
#Configuration
#EnableDiscoveryClient
#SpringBootApplication
#EnableConfigServer
public class ConfigServer {
public static void main(String[] args) {
SpringApplication.run(ConfigServer.class, args);
}
}
WebSecurityConfiguration.java
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.anyRequest().authenticated().and().httpBasic();
}
}
Config Client bootstrap.properties
spring.application.name=config-client
spring.cloud.config.uri=http://config-server:8888
spring.cloud.config.username=user
spring.cloud.config.password=configpassword123
management.endpoints.web.exposure.include=*
Config Client Controller Class
#RefreshScope
#RestController
public class ConfigController {
#Value("${applicationname}")
private String appName;
#Value("${username}")
private String username;
#Value("${password}")
private String password;
#Value("${instancename}")
private String environment;
#Value("${dbconnection}")
private String dbConnection;
#GetMapping("/user")
public String getUser() {
return "Application: "+ appName +" Instance: "+ environment + " User: " + username + " / " + password;
}
#GetMapping("/dbconn")
public String getDBConnection() {
return dbConnection;
}
}
Please check whether your post refresh request works fine. You need to permit the POST request in security configuration.
Reference code :
http.antMatchers(HttpMethod.POST, "/refresh").permitAll();
I have a simple web controller to return an User entity.
This entity has a property nonEditableProperty that cannot be updated.
It's works fine on the web controller, the nonEditableProperty value is listed but on the UserControllerTest it doesn't work and the returned value is always null.
The annotation #JsonProperty(access = JsonProperty.Access.READ_ONLY) seems to be ignored during the test serialization.
Does anyone have any clue for this issue?
Should I load some Jackson configuration for the tests?
#Getter
#Entity
class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String nonEditableProperty;
User() {
}
public User(String name, String nonEditableProperty) {
this.name = name;
this.nonEditableProperty = nonEditableProperty;
}
}
#RestController
#AllArgsConstructor
#RequestMapping("users")
public class UserController {
private final UserRepository userRepository;
#GetMapping
public Collection<User> getAllUsers() {
return (Collection<User>) userRepository.findAll();
}
#GetMapping(path = "/{id}")
public User getUser(#PathVariable Integer id) {
return userRepository.findOne(id);
}
}
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserControllerTest {
#Autowired
TestRestTemplate testRestTemplate;
#Test
public void getUserShouldReturnData() {
ResponseEntity<User> response = testRestTemplate.getForEntity("/users/{id}", User.class, 1);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody().getName()).isEqualTo("Muhammed SuiƧmez");
assertThat(response.getBody().getNonEditableProperty()).isEqualTo("Non editable property");
}
}
testRestTemplate.getForEntity(URI url, Class<T> responseType) Fetches the api response by hitting url and then converts response to the type given by responseType
Though the API response fetched by hitting URL received the nonEditableProperty value (parse inputMessage.getBody here), While deserializing it to responseType the value was lost, because of READ_ONLY Jackson property.
I am trying to restrict which properties from a JSON object are deserialised using Jackson JSONViews. The aim is to use this to prevent consumers of my API from submitting data that they shouldn't.
The problem is, I have either misunderstood JSONViews or I am doing something wrong. See below.
I started trying to do this in Spring but have noticed that even the simple test below doesn't work.
Account Class
public class Account {
#Id
private String id;
private String name;
private List<String> items;
private List<User> users;
#JsonView(AccountViews.Private.class)
public void setId(String id) {
this.id = id;
}
#JsonView(AccountViews.Public.class)
public void setName(String name) {
this.name = name;
}
#JsonView(AccountViews.Public.class)
public void setItems(List<String> items) {
this.items = items;
}
#JsonView(AccountViews.Private.class)
public void setUsers(List<User> users) {
this.users = users;
}
}
Views
public class AccountViews {
public interface Public {}
public interface Private extends Public {}
}
Test
#Test
public void testDeserialization(){
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
Account account = mapper.readerWithView(AccountViews.Public.class).forType(Account.class).readValue("{ \"name\": \"account1\", \"items\": [\"item1\"], \"users\": [ { \"firstname\": \"user1_firstname\", \"lastname\": \"user1_lastname\" }] }");
assertEquals(account.getName(), "account1");
assertNull(account.getUsers());
}
Unforunately, the 2nd assertion fails because Users has a user object inside.
Basically, even though "users" is a property of Account, I don't want the value to be deserialized because I have used the JSONView (AccountViews.Public.class). However, whatever I try it always seems to be deserialized and is present on the account object.
Any help much appreciated.
Error
`java.lang.AssertionError: expected null, but was:<[User#609db43b]>
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotNull(Assert.java:755)
at org.junit.Assert.assertNull(Assert.java:737)
at org.junit.Assert.assertNull(Assert.java:747)
at`
Currently I am facing, a problem, when try to call Web Api put method from MVC Api Client, lets describe my code structure bellow
Test Model (Web Api end)
public sealed class Test
{
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
Web Api PUT Method
public HttpResponseMessage Put(string token, IEnumerable<Test> data)
{
[...]
return Request.CreateResponse(HttpStatusCode.OK);
}
Web Api Custom Filter
public sealed class ValidateFilterAttribute : ActionFilterAttribute
{
/// <summary>
///
/// </summary>
/// <param name="actionContext"></param>
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
Call from Web Api Client
async System.Threading.Tasks.Task VerifiedFAccount()
{
using (var client = GetHttpClient())
{
var url = string.Concat("/api/Verfication", "?token=", token);
var data = new SampleTest { Id = 1, Name = "xxx" };
var temp = new List<SampleTest>();
temp.Add(data);
using (HttpResponseMessage response = await client.PutAsJsonAsync
<IEnumerable<SampleTest>>(url, temp).ConfigureAwait(false))
{
if (response.IsSuccessStatusCode)
{
}
}
}
Client code unable to execute Api call (Even I placed the debug point within Web Api Put method, unable to hit the debug point) & always got the bellow error response
{StatusCode: 500, ReasonPhrase: 'Internal Server Error', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Pragma: no-cache
X-SourceFiles: =?UTF-8?B?STpcRGV2QXJlYUxvY2FsXENPTVBBTlkgLSBQU1AgUFJPSkVDVFNcRS1BdXRob3JpdHkgLSBBdXN0cmVsaXlhXFNvdXJjZUNvbnRyb2xcVHJ1bmtcMDYgRGVjIDIwMTNcRS1BdXRob3JpdHkuQXBpIC0gMjAxM1xFYXV0aG9yaXR5LldlYi5BcGkuUHJlc2VudGF0aW9uTGF5ZXJcYXBpXFNtc2ZBY2NvdW50VmVyZmljYXRpb24=?=
Cache-Control: no-cache
Date: Mon, 14 Apr 2014 11:23:27 GMT
Server: Microsoft-IIS/8.0
Content-Length: 2179
Content-Type: application/json; charset=utf-8
Expires: -1
}}
But when I remove [Required] from Test Model (Web Api end). Then above described client code execute successfully.
Please tell me what is the reason of this kind of confusing behavior ?
The issue you are facing might be because of the behaviour of the default configuration when it comes to data validation. You have a Required attributed on a non-nullable type and since int can't be null, it will always have a value (the default of 0) if the incoming request does not provide the value.
In these cases, the model validator will throw an exception because it doesn't make sense to have a Required attribute on a property that can't be null.
The straightforward way you would be to change a setting on your MVC application:
DataAnnotationsModelValidatorProvider
.AddImplicitRequiredAttributeForValueTypes = false;
This will get rid of the error that is thrown by the framework. This introduce the problem that you will get a value of 0 even when the request does not include the property. It makes more sense to have your integer be of Nullable<int>. The Required attribute will be able to handle a null value and you will know whether or not the incoming request included the property
public sealed class Test
{
[Required]
public int? Id { get; set; }
[Required]
public string Name { get; set; }
}