RepresentationModelProcessor nullpointer having mandatory field in controller spring 2.7.4 - spring-boot

I have a class implementing RepresentationModelProcessor that's adding links to a resource:
linkTo(methodOn(MyController.class).findMyElements(file.getId(), null, null))
.withRel("filterFiles"))
The controller looks like this:
#RequestMapping(method = { POST, GET }, value = "/myFiles/{fileId}/filterFiles")
public List<FileDto> findFilterFiles( #PathVariable("fileId") final Long fileId,
#RequestParam(name = "fileType") final Long fileType,
#RequestBody(required = false) final FileFilterDto filter)
In Spring 2.6.4, it used to work. Now, I've tried upgrading to 2.7.4 and it does not work anymore. I've tracked the issue down and it seems like the required request param must not be null anymore.
I'm getting a null pointer exception with text like this:
Illegal character in query at index 93: http://localhost:8080/files/10013/filterFiles?fileType={fileType}
with the index pointing to '=' of fileType={fileType} .
Is it a bug? If yes - how can I fix it? Passing a constant number fixes the null-pointer exception:
linkTo(methodOn(MyController.class) .findMyElements(file.getId(), 1L, null))
.withRel("filterFiles"))
but it leads to incorrect code.
I've raised an issue here: https://github.com/spring-projects/spring-hateoas/issues/1872 but it might be the wrong repository after all.

Related

After upgrading to Spring Boot 2.1.8.RELEASE from 1.5.x, QueryParams for HTTP Get requests with + sign are getting encoded to spaces now

Before the upgrade, we had a #RestController controller end point with HTTP GET:
#RequestMapping(value = "/{dashboardDefinitionId}/widgets/{widgetId}", method = { RequestMethod.GET })
public ResponseEntity<JsonSerializer> getWidgetReportData(#PathVariable String dashboardDefinitionId,
#PathVariable String widgetId, ReportFilter reportFilter) { ... }
The ReportFilter has a startDate and endDate variables that used to be deserialized fine:
private Date startDate;
private Date endDate;
The UI was passing date strings with + signs encoding (%2b) in it for timezone information.
prior to the upgrade, the application was deserializing the strings fine into Date instances. It was encoding the %2b to '+' by the time it got to the controller and worked fine. Now, after the upgrade, it's failing bc it's seeing a space ' ' in the date instead of the + with additional timezone info in the date string.
My main question is, why is spring decoding query params, and then reencoding them somewhere downstream incorrectly and translating + in query params to spaces.
This would be an issue anywhere when then the UI is creating HTTP gets with query params with + in it, such as email addresses.

Spring's LdapTemplate search: PartialResultException: Unprocessed Continuation Reference(s); remaining name '/'

I add users through LDAP for a certain application, made with spring.
While this works for most of the cases, in some cases, it does not work...
The retrieve the users I use:
public class LdapUserServiceImpl implements ILdapUserService {
#Override
public List<LdapUserVO> getUserNamesByQuery(String query) {
return ldapTemplate.search(
query().countLimit(15)
.where("objectClass").is("user")
.and("sAMAccountName").isPresent()
.and(query()
.where("sAMAccountName").like("*" + query + "*")
.or("sAMAccountName").is(query)
.or("displayName").like("*" + query + "*")
.or("displayName").is(query))
,
new AttributesMapper<LdapUserVO>() {
public LdapUserVO mapFromAttributes(Attributes attrs) throws NamingException {
LdapUserVO ldapUser = new LdapUserVO();
Attribute attr = attrs.get(ldapUserSearch);
if (attr != null && attr.get() != null) {
ldapUser.setUserName(attr.get().toString());
}
attr = attrs.get("displayName");
if (attr != null && attr.get() != null) {
ldapUser.setDisplayName(attr.get().toString());
}
return ldapUser;
}
});
}
}
So this works in most of the cases, but sometimes I get the following error:
unprocessed continuation reference(s); remaining name "/"
I've searched a lot about this, and I explicitly set
DefaultSpringSecurityContextSource ctxSrc = new DefaultSpringSecurityContextSource(ldapUrl);
ctxSrc.setReferral("follow");
Some more info:
Search-query "admin_a" works, but "admin_ah" does not
Spring version is 4.2.5.RELEASE
Spring ldap-core version is 2.0.2.RELEASE
I think it strange that the remaining name is the root directory... Does someone has any ideas how to fix this, or even where to start looking?
Thanks in advance!
This may be related with the Active Directory being unable to handle referrals automatically. Please take a look at the LdapTemplate javadoc.
If this is the case, set the ignorePartialResultException property to true in your ldapTemplate configuration.
The reason for this error in my case was that the structure of the new AD had changed (userPrincipleName was now the emailaddress instead of login). Because of this the authentication to the AD worked fine, but no entry could be found that matched the filter, and as such didn't return any result.
So the PartialResultException was only an indication, not the reason. the reason is the lack of any result in the method searchForSingleEntryInternal of the SpringSecurityLdapTemplate class.
In my case, I had to make sure I used the correct userPrincipleName and configure the correct domain and baseDN in my ActiveDirectoryLdapAuthenticationProvider.

Spring4 #RequestBody required doesn't work

I've upgrade Spring version from 4.0.x to 4.2.3 (4.2.4 just now), suddenly, "required = false" property from #RequestBody annotation does not work as expected (as it did before the version change).
Server response with HttpStatus 415 - Unsupported Media Type.
The controller method (same for every Spring versions).
#RequestMapping(value = "/entity/{entityId}/{from}/{size}", method = RequestMethod.POST)
#ResponseBody
#JsonView(ToShowIn.App.class)
public ResponseEntity<?> getActiveEntityPaged(
#PathVariable int entityId, #PathVariable int from, #PathVariable int size,
#RequestBody(required = false) EntityFilterParam entityFilterParam) {
...
}
Really, it's not problem because from the client, avoiding send null, for example, setting empty EntityFilterParam instance is enough to fix it. But I would like to understand if this bothering issue is because a wrong concept from me or rather is due for a bug from the new Spring versions.
UPDATE#1
Versions
<spring-version>4.2.4.RELEASE</spring-version>
<jackson-2-version>2.3.2</jackson-2-version>
Request is from Android device, working with spring-android library (AKA RestTemplate).
Generic method, works on 4.0.x version, where EntityFilterParam can be null.
super.doRestList(
new HttpEntity<EntityFilterParam>(EntityFilterParam, this.getHttpHeaders()),
HttpMethod.POST,
urlBuilder);
On 4.2.3 and 4.2.4, to fix the issue, EntityFilterParam can't be null. -> HttpStatus 415 - Unsupported Media Type. To fix it.
super.doRestList(
new HttpEntity<EntityFilterParam>((EntityFilterParam != null) ? EntityFilterParam : new EntityFilterParam(), this.getHttpHeaders()),
HttpMethod.POST,
urlBuilder);
Inside doRestList, RestTemplate request is performed.
final ResponseEntity<E[]> responseEntity =
(ResponseEntity) super.getRestTemplate()
.exchange(urlBuilder.getURL(), httpMhetod,
requestEntity, this.entityArray.getClass());
Where, urlBuilder.getURL() (URL string, here #PathVariables are working fine) and this.getHttpHeaders() return the next HttpHeaders configuration.
RestFactory.httpHeaders = new HttpHeaders();
RestFactory.httpHeaders.setAuthorization(this.getAuthorization(user));
RestFactory.httpHeaders.set(HttpHeaders.CONNECTION, "Close"); // Switch off keep-alive.
RestFactory.httpHeaders.set(HttpHeaders.ACCEPT_LANGUAGE, "en-US,en;q=0.8");
RestFactory.httpHeaders.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
super.getRestTemplate() is the singleton method which returns the RestTemplate instance set with my own CloseableHttpClient configuration.
I know HttpHeaders.CONTENT_TYPE for application/json is missing, but is consciously, in fact, it works when the http body payload is not empty, and fails when it is.
When the error occurs, 415 is performed before the flow of the request goes inside the controller method, therefore, any operation is doesn't performed.
UPDATE#2
Another interesting point, Jackson2 serializes the null value as "null" string, then the body payload is not being null, is filled with "null".
I've tried reproducing this exact issue, without much success.
Do you confirm that with a curl command the problem still exists?
Something like curl -vvv -X POST https://example.com/entity/12/50/10 (if so, paste here the output)
I'm not 100% about this for RestTemplate in spring-android, but if no content type is provided, the template adds by default a "Content-Type: application/x-www-form-urlencoded". If this is the case, configuring a "DEBUG" log level for "org.springframework.web" should show something like
[INFO] [talledLocalContainer] org.springframework.web.HttpMediaTypeNotSupportedException:
Content type 'application/x-www-form-urlencoded' not supported
I think you should set the Content-Type anyway. You mentioned this fails when you do - could you elaborate a bit on that?

Empty request body gives 400 error

My Spring controller method looks something like this:
#RequestMapping(method=RequestMethod.PUT, value="/items/{itemname}")
public ResponseEntity<?> updateItem(#PathVariable String itemname, #RequestBody byte[] data) {
// code that saves item
}
This works fine except when a try to put a zero-length item, then I get an HTTP error: 400 Bad Request. In this case my method is never invoked. I was expecting that the method should be invoked with the "data" parameter set to a zero-length array.
Can I make request mapping work even when Content-Length is 0?
I am using Spring framework version 4.1.5.RELEASE.
Setting a new byte[0] will not send any content on the request body. If you set spring MVC logs to TRACE you should see a message saying Required request body content is missing as a root cause of your 400 Bad Request
To support your case you should make your #RequestBody optional
#RequestMapping(method=RequestMethod.PUT, value="/items/{itemname}")
public ResponseEntity<?> updateItem(#PathVariable String itemname, #RequestBody(required = false) byte[] data) {
// code that saves item
}

What is your recommended workaround to deal with inconsistent API behavior design of google-gson

Just that recently, I came across some inconsistent API behavior of google-gson.
Non-empty string but with invalid syntax
Gson gson = new Gson();
// com.google.gson.JsonSyntaxException thrown
gson.fromJson("{{", Map.class);
Empty string
Gson gson = new Gson();
// Returns null
gson.fromJson("", Map.class);
No-empty string with all space
Gson gson = new Gson();
// Returns null
gson.fromJson(" ", Map.class);
null string
Gson gson = new Gson();
// Returns null
gson.fromJson((String)null, Map.class)
This problem is being reported in https://code.google.com/p/google-gson/issues/detail?id=457 The status is won't fixed due to backwards-compatible.
I was wondering, what is your usual workaround on the problem?
Workaround 1 : Check for string before passing in gson
if (string != null && !string.trim().isEmpty())
Workaround 2 : Check for returned value of gson
Map m = gson.fromJson(string, Map.class);
if (m != null) {
}
Workaround 3 : Use TypeAdapter and catch using generic Exception
try {
gson.getAdapter(Map.class).fromJson(string);
} catch (Exception ex) {}
I don't think stackoverflow is the correct forum for this, since it seems an opinion based question to me, which would make if off-topic.
That said, i think workaround 2 is the best. There might be other strings, which you do not know, that make fromJson return null instead of throwing an exception; workaround 2 handles that while workaround 1 doesn't. I don't like workaround 3 either, because there might be other exceptions in the tryed code, which might get caught by your exception handler though the handler isn't really prepared to handle them.

Resources