I am trying to test a Spring MVC method, which looks like this:
#PostMapping("/register")
public Callable<String> register(...) {
return () -> {...}
}
However, when I test the code like this (as described in the spring docs):
WebDriver driver;
#BeforeEach
void setup(WebApplicationContext context) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
}
#Test
void someTest() {
...
submitButton.click() // sending the post request
}
then the test always fails with the following exception:
java.lang.IllegalStateException: Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding "<async-supported>true</async-supported>" to servlet and filter declarations in web.xml.
at org.springframework.util.Assert.state(Assert.java:76) ~[spring-core-5.3.25.jar:5.3.25]
at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.startAsync(StandardServletAsyncWebRequest.java:112) ~[spring-web-5.3.25.jar:5.3.25]
at org.springframework.web.context.request.async.WebAsyncManager.startAsyncProcessing(WebAsyncManager.java:483) ~[spring-web-5.3.25.jar:5.3.25]
at org.springframework.web.context.request.async.WebAsyncManager.startCallableProcessing(WebAsyncManager.java:331) ~[spring-web-5.3.25.jar:5.3.25]
at org.springframework.web.context.request.async.WebAsyncManager.startCallableProcessing(WebAsyncManager.java:267) ~[spring-web-5.3.25.jar:5.3.25]
at org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler.handleReturnValue(CallableMethodReturnValueHandler.java:51) ~[spring-webmvc-5.3.25.jar:5.3.25]
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78) ~[spring-web-5.3.25.jar:5.3.25]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:135) ~[spring-webmvc-5.3.25.jar:5.3.25]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.25.jar:5.3.25]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.25.jar:5.3.25]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.25.jar:5.3.25]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071) ~[spring-webmvc-5.3.25.jar:5.3.25]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964) ~[spring-webmvc-5.3.25.jar:5.3.25]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.25.jar:5.3.25]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.25.jar:5.3.25]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:696) ~[tomcat-embed-core-9.0.71.jar:4.0.FR]
...
How can I configure async support with htmlUnit/webdriver/selenium?
Related
I have a test method for one of the mappings where I provide all required data for the test
#Test
public void getAllMessagesNoFilterNotByTag() throws Exception {
List<MessageDTO> messageDTOS = Arrays.asList(new MessageDTO(MESSAGE1), new MessageDTO(MESSAGE2));
when(messageService.getAllMessages("", false,
PageRequest.of(0, 5, Sort.by("creationDate").descending()))).thenReturn(messageDTOS);
mockMvc.perform(get("/api/message?filter=&bytag=false&page=0"))
.andExpect(status().isOk())
.andExpect(content().json(
objectMapper.writeValueAsString(Arrays.asList(new MessageDTO(MESSAGE1), new MessageDTO(MESSAGE2)))));
verify(messageService, times(1)).getAllMessages("", false,
PageRequest.of(0, 5, Sort.by("creationDate").descending()));
}
Here is the controller itself
#GetMapping
public List<MessageDTO> getAllMessages(#RequestParam(required = false) String filter,
#RequestParam(name = "bytag", required = false) Boolean findByTag,
#PageableDefault(sort = {"creationDate"}, direction = Sort.Direction.DESC, size = 5) Pageable pageable) {
return messageService.getAllMessages(filter, findByTag, pageable);
}
But after running it I get this exception about failing to create Pageble object
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: No primary or single public constructor found for interface org.springframework.data.domain.Pageable - and no default constructor found either
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:183)
at com.training.vueblog.controllers.MessageControllerTest.getAllMessagesNoFilterNotByTag(MessageControllerTest.java:62)
Caused by: java.lang.IllegalStateException: No primary or single public constructor found for interface org.springframework.data.domain.Pageable - and no default constructor found either
at org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:250)
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.createAttribute(ModelAttributeMethodProcessor.java:216)
at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.createAttribute(ServletModelAttributeMethodProcessor.java:85)
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:144)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:170)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1063)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
... 74 more
The problem was solve by adding extra configuration for mockMvc
#BeforeEach
public void setup(){
mockMvc = MockMvcBuilders.standaloneSetup(messageController)
.setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver())
.build();
}
How to throw a custom exception in the proper way when using #javax.validation.Valid?
I'm using #Valid in controller, and #AssertTrue to validate request body fields.
public ResponseEntity<Foo> createFoo(
#Valid #RequestBody Foo FooRequest ...
#AssertTrue()
public boolean isFooValid() {
if (invalid)
return false;
...
}
However, I want to throw customized Exception class in some condition.
#AssertTrue()
public boolean isFooValid() {
if (invalid)
return false;
...
// note below
if (invalidInAnotherCondition)
throw new CustomizedException(...);
}
I know this is not desirable way to utilize #Valid in controller, and #AssertTrue. Nevertheless, as I can make my own Exception class which contains customized error info, with the convenience of #Valid.
However the error happens.
javax.validation.ValidationException: HV000090: Unable to access isFooValid
at org.hibernate.validator.internal.util.ReflectionHelper.getValue(ReflectionHelper.java:245)
at org.hibernate.validator.internal.metadata.location.GetterConstraintLocation.getValue(GetterConstraintLocation.java:89)
at org.hibernate.validator.internal.engine.ValueContext.getValue(ValueContext.java:235)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:549)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForSingleDefaultGroupElement(ValidatorImpl.java:515)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:485)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:447)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:397)
at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:173)
at org.springframework.validation.beanvalidation.SpringValidatorAdapter.validate(SpringValidatorAdapter.java:117)
at org.springframework.boot.autoconfigure.validation.ValidatorAdapter.validate(ValidatorAdapter.java:70)
at org.springframework.validation.DataBinder.validate(DataBinder.java:889)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.validateIfApplicable(AbstractMessageConverterMethodArgumentResolver.java:266)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:137)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:523)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:590)
at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269)
at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78)
at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133)
at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130)
at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249)
at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78)
at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99)
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:376)
at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.reflect.InvocationTargetException: null
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.hibernate.validator.internal.util.ReflectionHelper.getValue(ReflectionHelper.java:242)
... 70 common frames omitted
Caused by: com.finda.services.finda.common.exception.CustomizedException: 'df282e0d-1205-4574-adaa-0af819af66c0'
at ...
... 75 common frames omitted
I think this happens because originally, #AssertTrue throws its own Exception itself and it is to be processed through the internal logic; However, customized thrown Exception is not acceptable which can be seen in Caused by: java.lang.reflect.InvocationTargetException: null and javax.validation.ValidationException: HV000090: Unable to access isFooValid
So my final question is below,
Can I bypass this error, still throwing customized Exception?
I really appreciate that you read this long posting in advance.
Consider the example below where I implemented something like what you asking for:
#RestController
#RequestMapping("/accounts")
public class SavingsAccountController {
private final BankAccountService accountService;
#Autowired
public SavingsAccountController(SavingsAccountService accountService) {
this.accountService = accountService;
}
#PutMapping("withdraw")
public ResponseEntity<AccountBalance> onMoneyWithdrawal(#RequestBody #Validated WithdrawMoney withdrawal, BindingResult errors) {
//this is the validation barrier
if (errors.hasErrors()) {
throw new ValidationException(errors);
}
double balance = accountService.withdrawMoney(withdrawal);
return ResponseEntity.ok(new AccountBalance(
withdrawal.getAccountNumber(), balance));
}
#PutMapping("save")
public ResponseEntity<AccountBalance> onMoneySaving(#RequestBody #Validated SaveMoney savings, BindingResult errors) {
//this is the validation barrier
if (errors.hasErrors()) {
throw new ValidationException(errors);
}
double balance = accountService.saveMoney(savings);
return ResponseEntity.ok(new AccountBalance(
savings.getAccountNumber(), balance));
}
}
In the code above, we're using Bean Validation to check that the user's DTO contains valid information. Any errors found in the DTO are provided through the BindingResult errors variable, from where the developer can extract all the details of what went wrong during the validation phase.
To make it easier for the developers to deal with this pattern, in the code above, I simply wrap the BindingResult into a custom ValidationException which knows how to extract the validation error details.
public class ValidationException extends RuntimeException {
private final BindingResult errors;
public ValidationException(BindingResult errors) {
this.errors = errors;
}
public List<String> getMessages() {
return getValidationMessage(this.errors);
}
#Override
public String getMessage() {
return this.getMessages().toString();
}
//demonstrate how to extract a message from the binging result
private static List<String> getValidationMessage(BindingResult bindingResult) {
return bindingResult.getAllErrors()
.stream()
.map(ValidationException::getValidationMessage)
.collect(Collectors.toList());
}
private static String getValidationMessage(ObjectError error) {
if (error instanceof FieldError) {
FieldError fieldError = (FieldError) error;
String className = fieldError.getObjectName();
String property = fieldError.getField();
Object invalidValue = fieldError.getRejectedValue();
String message = fieldError.getDefaultMessage();
return String.format("%s.%s %s, but it was %s", className, property, message, invalidValue);
}
return String.format("%s: %s", error.getObjectName(), error.getDefaultMessage());
}
}
Notice that in my controller definition I do not use Bean Validation's #Valid annotation, but the Spring counterpart #Validated, but under the hood Spring will use Bean Validation.
How to Serialize the Custom Exception?
In the code above the ValidationException will be thrown when the payload is invalid. How should the controller create a response for the client out of this?
There are multiple ways to deal with this, but perhaps the simplest solution is to define a class annotated as #ControllerAdvice. In this annotated class we will place our exception handlers for any specific exception that we want to handle and turn them into a valid response object to travel back to our clients:
#ControllerAdvice
public class ExceptionHandlers {
#ExceptionHandler
public ResponseEntity<ErrorModel> handle(ValidationException ex) {
return ResponseEntity.badRequest()
.body(new ErrorModel(ex.getMessages()));
}
//...
}
I wrote a few other examples of this and other validation techniques with Spring in case you may be interested in reading more about it.
Here is a solution I used (that might not answer this question directly but possibly help others who get to this page that came with similar intentions as I had):
My aim was foremost to respond with a custom error message to the client who sent a request with an invalid object.
In my controller I use a standard #Valid annotation from javax.validation.Valid before the parameter
In my entity class I use the standard validation constraint, e.g. #NotNull(message = "Field XYZ has to be provided") from javax.validation.constraints.NotNull
I catch the ValidationException in my ControllerAdvice class:
#ControllerAdvice
public class MyControllerAdvice extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException exception,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
return new ResponseEntity<>(
new JSONObject().put("message", extractValidationMessage(exception)).toString(),
HttpStatus.BAD_REQUEST);
}
private String extractValidationMessage(MethodArgumentNotValidException exception) {
String exceptionMessage = exception.getMessage();
String[] messageParts = exceptionMessage.split(";");
String finalPart = messageParts[messageParts.length -1];
return finalPart.trim().replaceAll("default message \\[|]]","");
}
}
This will return a 400 Error code and a body of this style:
{"message":"Field XYZ has to be provided"}
I am testing an API which is supposed to fetch a document from an envelope. Here is the example code:
#RestController
#Controller
#RequestMapping(value = "DocuSign")
public class DocusignController extends CoreController {
#RequestMapping(value = "/FetchPDF", method = RequestMethod.GET)
#ResponseBody
public MessageItem downloadDocument(Model model) throws ApiException, IOException {
WorkArguments args = new WorkArguments();
args.setAccountId("1e8dce91-c5....");
args.setDocumentId("1");
args.setEnvelopeId("ec1f77....");
String accessToken = "eyJ0eXAiOiJNVCIsImFsZyI6I.....";
String baseUrl = "https://demo.docusign.net/restapi";
JSONObject obj = doWork(args, null, accessToken, baseUrl);
MessageItem msg = new MessageItem();
msg.setMsg("Done!");
return msg;
}
protected JSONObject doWork(WorkArguments args, ModelMap model,
String accessToken, String basePath) throws ApiException, IOException {
// Data for this method
// accessToken (argument)
// basePath (argument)
String accountId = args.getAccountId();
String envelopeId = args.getEnvelopeId();
String documentId = args.getDocumentId();
ApiClient apiClient = new ApiClient(basePath);
apiClient.addDefaultHeader("Authorization", "Bearer " + accessToken);
EnvelopesApi envelopesApi = new EnvelopesApi(apiClient);
System.out.println("--->Checking if anything is null...");
if(envelopesApi == null )
System.out.println("Enveloper API is null!");
if(apiClient == null)
System.out.println("The apiClient is null!");
// Step 1. EnvelopeDocuments::get.
// Exceptions will be caught by the calling function
byte[] results = envelopesApi.getDocument(accountId, envelopeId, documentId);
String mimetype = ".pdf";
String docName = "success";
... etc.
When I go and test it with my rest client, I keep getting the following error:
Type Exception Report
Message Request processing failed; nested exception is
com.sun.jersey.api.client.ClientHandlerException
Description The server encountered an unexpected condition that
prevented it from fulfilling the request.
Exception
org.springframework.web.util.NestedServletException: Request
processing failed; nested exception is
com.sun.jersey.api.client.ClientHandlerException
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
Root Cause
com.sun.jersey.api.client.ClientHandlerException
com.docusign.esign.client.auth.OAuth.updateAccessToken(OAuth.java:111)
com.docusign.esign.client.auth.OAuth.applyToParams(OAuth.java:99)
com.docusign.esign.client.ApiClient.updateParamsForAuth(ApiClient.java:1209)
com.docusign.esign.client.ApiClient.getAPIResponse(ApiClient.java:1094)
com.docusign.esign.client.ApiClient.invokeAPI(ApiClient.java:1158)
com.docusign.esign.api.EnvelopesApi.getDocument(EnvelopesApi.java:2624)
com.docusign.esign.api.EnvelopesApi.getDocument(EnvelopesApi.java:2556)
delaware.gov.dti.ice.esignprototypev1.controller.DocusignController.doWork(DocusignController.java:82)
delaware.gov.dti.ice.esignprototypev1.controller.DocusignController.downloadDocument(DocusignController.java:52)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
Root Cause
java.lang.NullPointerException
com.docusign.esign.client.auth.OAuth.updateAccessToken(OAuth.java:109)
com.docusign.esign.client.auth.OAuth.applyToParams(OAuth.java:99)
com.docusign.esign.client.ApiClient.updateParamsForAuth(ApiClient.java:1209)
com.docusign.esign.client.ApiClient.getAPIResponse(ApiClient.java:1094)
com.docusign.esign.client.ApiClient.invokeAPI(ApiClient.java:1158)
com.docusign.esign.api.EnvelopesApi.getDocument(EnvelopesApi.java:2624)
com.docusign.esign.api.EnvelopesApi.getDocument(EnvelopesApi.java:2556)
delaware.gov.dti.ice.esignprototypev1.controller.DocusignController.doWork(DocusignController.java:82)
delaware.gov.dti.ice.esignprototypev1.controller.DocusignController.downloadDocument(DocusignController.java:52)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
I am using docusign-esign-java version 3.2.0, which is cucrently the latest. I am able to fetch the doucment when using the rest client, so I know that at least my account id, access token and document id (and template id) are correct. Also I am basing my solution from the following DocuSign refrence page Downloading Envelope Documents and using the provided downloaded code just for to doubleche to see if I am missing anything.
Furthermore I checked to see if the apiClient or the envelopesApi objects are null and they are not so I am getting the problem on the
byte[] results = envelopesApi.getDocument(accountId, envelopeId, documentId);
which for some reason, seems to give a null pointer exception. Any idea as to why?
I updated existing example without Spring to test 3.2.0 version, and it seems to work fine for me and it is not throwing any exception. If it is working w/o Spring then it will work with Spring as well, so does not look like any issue in SDK.
public void listDocuments(String envelopeId) throws ApiException, IOException {
this.checkToken();
EnvelopesApi envelopeApi = new EnvelopesApi(this.apiClient);
EnvelopeDocumentsResult envelopeDocumentsResult = envelopeApi.listDocuments(this.getAccountId(), envelopeId);
List<EnvelopeDocument> envelopeDocuments = envelopeDocumentsResult.getEnvelopeDocuments();
for (EnvelopeDocument envelopeDocument : envelopeDocuments) {
System.out.println("documentId " + envelopeDocument.getDocumentId());
envelopeApi.getDocument(this.getAccountId(), envelopeId, envelopeDocument.getDocumentId());
}
}
I have followed this blog and have created few microservices: Eureka-server,Auth-service,Zuul-service,Gallery-service,Image-service.
From the gallery service I wanted to invoke auth-service API using Feign-Client
The url doesn't require authentication but the client throws FeignException$Unauthorized
I'm using JWT tokens for authentication.
//AuthServerProxy.java
#FeignClient(name = "auth-service")
#RibbonClient(name = "auth-service")
public interface AuthServiceProxy {
#PostMapping("/auth/authenticate")
public ResponseEntity<?> authenticate(#RequestBody UserEntity userEntity);
#GetMapping("/auth/register")
public String test();
}
Controller - Gallery Service
#Autowired
AuthServiceProxy authServiceProxy;
#GetMapping("/test")
public String test(){
UserEntity userEntity = new UserEntity();
userEntity.setUsername("admin");
userEntity.setPassword("admin");
ResponseEntity<?> responseEntity = authServiceProxy.authenticate(userEntity);
System.out.println(responseEntity.getStatusCode());
return responseEntity.toString();
}
#GetMapping("/test/str")
public String testStr(){
return authServiceProxy.test();
}
Security Config - ZuulServer, Auth-Service
.antMatchers(HttpMethod.POST, "/auth/authenticate").permitAll()
This is the error log
ERROR 1123 --- [nio-8100-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.FeignException$Unauthorized: status 401 reading AuthServiceProxy#authenticate(UserEntity)] with root cause
feign.FeignException$Unauthorized: status 401 reading AuthServiceProxy#authenticate(UserEntity)
at feign.FeignException.errorStatus(FeignException.java:94) ~[feign-core-10.2.3.jar:na]
at feign.FeignException.errorStatus(FeignException.java:86) ~[feign-core-10.2.3.jar:na]
at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:93) ~[feign-core-10.2.3.jar:na]
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:149) ~[feign-core-10.2.3.jar:na]
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:78) ~[feign-core-10.2.3.jar:na]
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103) ~[feign-core-10.2.3.jar:na]
at com.sun.proxy.$Proxy101.authenticate(Unknown Source) ~[na:na]
at com.test.gallery.Controller.test(Controller.java:47) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_201]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_201]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_201]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_201]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
...
Any help much appreciated.
TIA
Looks like the Authentication header is not passing with FeignClient
try to add this config:
#Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
requestTemplate.header(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", details.getTokenValue()));
}
};
}
Feign is not aware of the Authorization that should be passed to the target service .Unfortunately, you need to handle this yourself.Below is a java class that can help
#Component
public class FeignClientInterceptor implements RequestInterceptor {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_TOKEN_TYPE = "Bearer";
#Override
public void apply(RequestTemplate template) {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
template.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER_TOKEN_TYPE, details.getTokenValue()));
}
}
It sounds like the problem could be that you don't have the #EnableResourceServer attached to your Auth-Service.
Without that annotation any endpoint that isn't apart of the spring security package (eg. /oauth/token, /oauth/check_token) will automatically require Authorization.
Furthermore you may need to add in a ResourceServerConfigurerAdapter similar to this to make sure that the resource endpoints are configured to permit all like so:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private final TokenStore tokenStore;
public ResourceServerConfig(TokenStore tokenStore) {
this.tokenStore = tokenStore;
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.POST).permitAll()
.and()
.logout().disable()
.csrf().disable();
}
}
*******EDIT*********
If you’re able to get an ok response from a request in the browser but not feign then your problem most likely is that your Feign client isn’t pointing to the correct endpoint. Normally you would expect a 404 error but since the API is secured you get a 401 because it doesn’t even allow you to know what’s a valid endpoint unless you’re authenticated or it’s an unsecured endpoint
If you have your AuthServiceProxy feign client use your zuul-server instead of the auth-service, you can then add logging to your zuul filter to see what both successful and unsuccessful requests looks like. From there make the necessary changes to have your proxy request match the request you made from the browser and you should be good to go
I have the below Controller.
#Controller
public class AppController {
#RequestMapping("/home")
#ResponseBody
public User home() {
User u = new User();
u.setUserId(10);
u.setUserName("Rahul");
return u;
}
}
when I comment out the annotation #ResponseBody I get an error while calling /home url.Why?
The message is below
javax.servlet.ServletException: Circular view path [home]: would
dispatch back to the current handler URL [/home] again. Check your
ViewResolver setup! (Hint: This may be the result of an unspecified
view, due to default view name generation.) at
org.springframework.web.servlet.view.InternalResourceView.prepareForRendering(InternalResourceView.java:209)
~[spring-webmvc-5.1.4.RELEASE.jar:5.1.4.RELEASE] at
org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:147)
~[spring-webmvc-5.1.4.RELEASE.jar:5.1.4.RELEASE] at
org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:316)
~[spring-webmvc-5.1.4.RELEASE.jar:5.1.4.RELEASE] at
org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1370)
~[spring-webmvc-5.1.4.RELEASE.jar:5.1.4.RELEASE] at
org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1116)
~[spring-webmvc-5.1.4.RELEASE.jar:5.1.4.RELEASE] at
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1055)
~[spring-webmvc-5.1.4.RELEASE.jar:5.1.4.RELEASE] at
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
~[spring-webmvc-5.1.4.RELEASE.jar:5.1.4.RELEASE] at
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
~[spring-webmvc-5.1.4.RELEASE.jar:5.1.4.RELEASE] at
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
~[spring-webmvc-5.1.4.RELEASE.jar:5.1.4.RELEASE] at
javax.servlet.http.HttpServlet.service(HttpServlet.java:645)
~[javax.servlet-api-4.0.1.jar:4.0.1] at
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
~[spring-webmvc-5.1.4.RELEASE.jar:5.1.4.RELEAS
But when I change my controller method to the below I don't get any error :-
Why?
#RequestMapping("/home")
//#ResponseBody // commented
public ResponseEntity<User> home(){
User u = new User();
u.setUserId(10);
u.setUserName("Raj");
return ResponseEntity.ok(u);
}
#Controller will not work stand-alone without #ResponseBody or ResponseEntity
ResponseEntity is like #ResponseBody but with status and headers.
Why we need #ResponseBody ?
Because, if any of your request mapping method, like home(), contains #ResponseBody annotation it gives an indication that a method return value should be bound to the web response body and if we don’t mention #ResponseBody, then the returned Object will be considered as one of the View and ViewResolver will start looking for respective view in the application.
For further details please refer: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-return-types