HttpRequestMethodNotSupportedException: Request method 'POST' not supported - spring

Creating a unit test with MockMvc I am running into:
HttpRequestMethodNotSupportedException: Request method 'POST' not supported
Which causes the test case to fail expecting a '200' but getting a '405'. Some of the additional things you may see in the Junit are for Spring Rest Docs and can be ignored. Such as the #Rule or the .andDo() on the mockMvc Call. I have followed the following documentation Spring Rest Docs - Path Parameter and cannot seem to get it working.
Here is the Controller:
#RestController("/transferObjects")
public class TransferObjectController {
#RequestMapping(method=RequestMethod.GET, produces="application/json")
public List<TransferObject> getTransferObjects(){
// do some magic
return null;
}
#RequestMapping(value = "/{name}", method=RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
public void addTransferObject(#PathVariable("name")String name){
// do magic
}
#RequestMapping(value = "/{name}", method=RequestMethod.DELETE)
#ResponseStatus(HttpStatus.OK)
public void deleteTransferObject(#PathVariable("name")String name){
// do magic
}
Here is the Junit Class:
public class TransferObjectControllerTest {
#Rule
public RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
this.mockMvc = MockMvcBuilders.standaloneSetup(new TransferObjectController())
.apply(documentationConfiguration(this.restDocumentation))
.build();
}
#Test
public void testAddTransferObject() throws Exception {
this.mockMvc.perform(post("/transferObjects/{name}", "hi"))
.andExpect(status().isOk()).andDo(document("transferObject",
pathParameters(
parameterWithName("name").description("The name of the new Transfer Object to be created."))));
}
And here is the console while running the test:
11:20:48.148 [main] INFO o.s.w.s.m.m.a.RequestMappingHandlerAdapter - Looking for #ControllerAdvice: org.springframework.test.web.servlet.setup.StubWebApplicationContext#5ab785fe
11:20:48.205 [main] DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Looking for exception mappings: org.springframework.test.web.servlet.setup.StubWebApplicationContext#5ab785fe
11:20:48.283 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Initializing servlet ''
11:20:48.307 [main] DEBUG o.s.w.c.s.StandardServletEnvironment - Adding [servletConfigInitParams] PropertySource with lowest search precedence
11:20:48.307 [main] DEBUG o.s.w.c.s.StandardServletEnvironment - Adding [servletContextInitParams] PropertySource with lowest search precedence
11:20:48.312 [main] DEBUG o.s.w.c.s.StandardServletEnvironment - Adding [systemProperties] PropertySource with lowest search precedence
11:20:48.312 [main] DEBUG o.s.w.c.s.StandardServletEnvironment - Adding [systemEnvironment] PropertySource with lowest search precedence
11:20:48.313 [main] DEBUG o.s.w.c.s.StandardServletEnvironment - Initialized StandardServletEnvironment with PropertySources [servletConfigInitParams,servletContextInitParams,systemProperties,systemEnvironment]
11:20:48.313 [main] INFO o.s.mock.web.MockServletContext - Initializing Spring FrameworkServlet ''
11:20:48.313 [main] INFO o.s.t.w.s.TestDispatcherServlet - FrameworkServlet '': initialization started
11:20:48.318 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Unable to locate MultipartResolver with name 'multipartResolver': no multipart request handling provided
11:20:48.318 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Using LocaleResolver [org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver#63238bf4]
11:20:48.318 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Using ThemeResolver [org.springframework.web.servlet.theme.FixedThemeResolver#32b97305]
11:20:48.319 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Using RequestToViewNameTranslator [org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator#2d2e6747]
11:20:48.319 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Using FlashMapManager [org.springframework.web.servlet.support.SessionFlashMapManager#417e7d7d]
11:20:48.319 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Published WebApplicationContext of servlet '' as ServletContext attribute with name [org.springframework.web.servlet.FrameworkServlet.CONTEXT.]
11:20:48.319 [main] INFO o.s.t.w.s.TestDispatcherServlet - FrameworkServlet '': initialization completed in 6 ms
11:20:48.319 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Servlet '' configured successfully
11:20:48.361 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - DispatcherServlet with name '' processing POST request for [/transferObjects/hi]
11:20:48.364 [main] DEBUG o.s.t.w.s.s.StandaloneMockMvcBuilder$StaticRequestMappingHandlerMapping - Looking up handler method for path /transferObjects/hi
11:20:48.368 [main] DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolving exception from handler [null]: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported
11:20:48.369 [main] DEBUG o.s.w.s.m.a.ResponseStatusExceptionResolver - Resolving exception from handler [null]: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported
11:20:48.369 [main] DEBUG o.s.w.s.m.s.DefaultHandlerExceptionResolver - Resolving exception from handler [null]: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported
11:20:48.369 [main] WARN o.s.web.servlet.PageNotFound - Request method 'POST' not supported
11:20:48.370 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Null ModelAndView returned to DispatcherServlet with name '': assuming HandlerAdapter completed request handling
11:20:48.370 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - Successfully completed request

It appears I was able to fix this problem.
Taking a closer look at the console while running the test I noticed:
Mapped "{[],methods=[GET],produces=[application/json]}" onto public java.util.List<common.to.TransferObject> sf.common.controller.TransferObjectController.getTransferObjects()
Mapped "{[/{name}],methods=[POST]}" onto public void sf.common.controller.TransferObjectController.addTransferObject(java.lang.String)
Mapped "{[/{name}],methods=[DELETE]}" onto public void sf.common.controller.TransferObjectController.deleteTransferObject(java.lang.String)
this shows that it is mapping the controller request mappings to the specific RequestMapping the method holds. #RestController("/transferObjects") is not actually defining the mapping as a parent. For me to do that I must include #RequestMapping("/transferObjects") underneath the #RestController.
So by changing the parent mapping I can now use the following test case:
#Test
public void testAddTransferObject() throws Exception {
this.mockMvc.perform(post("/transferObjects/{name}", "hi"))
.andExpect(status().isOk()).andDo(document("transferObject",
pathParameters(
parameterWithName("name").description("The name of the new Transfer Object to be created."))));
}

Related

How to disable hawt.io authentication?

I used spring boot, hawt.io, camel to test hawt.io dashboard
plugins {
id 'org.springframework.boot' version '1.5.10.RELEASE'
}
repositories {
mavenCentral()
flatDir {
dirs 'lib'
}
}
dependencies {
// Spring actuator, log4j2
compile("org.springframework.boot:spring-boot-starter-log4j2")
//Spring web
compile("org.springframework.boot:spring-boot-starter-web"){
exclude module: "spring-boot-starter-tomcat"
}
compile("org.springframework.boot:spring-boot-starter-jetty")
compile("org.eclipse.jetty:jetty-jaas")
compile("org.eclipse.jetty:jetty-http")
compile("org.springframework.boot:spring-boot-actuator")
//hawtio
compile("io.hawt:hawtio-springboot:1.5.10")
compile("io.hawt:hawtio-core:1.5.10")
and i had disabled authentication via
hawtio.authenticationEnabled=false
Here is the log:
18:00:13.489 [main] DEBUG ConfigManager - Property noCredentials401 is set to value false
18:00:13.490 [main] DEBUG ConfigManager - Property realm is set to value karaf
18:00:13.490 [main] DEBUG ConfigManager - Property role is set to value null
18:00:13.490 [main] DEBUG ConfigManager - Property roles is set to value null
18:00:13.490 [main] DEBUG ConfigManager - Property rolePrincipalClasses is set to value
18:00:13.490 [main] DEBUG ConfigManager - Property authenticationEnabled is set to value false
18:00:13.490 [main] DEBUG ConfigManager - Property noCredentials401 is set to value false
18:00:13.490 [main] DEBUG ConfigManager - Property authenticationContainerDiscoveryClasses is set to value io.hawt.web.tomcat.TomcatAuthenticationContainerDiscovery
18:00:13.490 [main] INFO AuthenticationFilter - Starting hawtio authentication filter, JAAS authentication disabled
18:00:13.500 [main] DEBUG ConfigManager - Property sessionTimeout is set to value 1800
18:00:13.500 [main] INFO LoginServlet - hawtio login is using 1800 sec. HttpSession timeout
When i open url http://localhost:8091/hawtio/index.html, it always be redirected to http://localhost:8091/hawtio/index.html#/login
How can i disable authentication?
According https://github.com/hawtio/hawtio/issues/1963, the issue should be fixed on 6 Dec 2015, but it's still there.
And according those 404 errors, it seems all requests are handled by spring mvc DispatcherServlet, and those servlets registered in HawtioManagementContextConfiguration are not worked as expected.
18:07:52.821 [qtp1016881733-22] DEBUG DispatcherServlet - DispatcherServlet with name 'dispatcherServlet' processing GET request for [/hawtio/keycloak/enabled]
18:07:52.821 [qtp1016881733-22] DEBUG RequestMappingHandlerMapping - Looking up handler method for path /hawtio/keycloak/enabled
18:07:52.821 [qtp1016881733-22] DEBUG RequestMappingHandlerMapping - Did not find handler method for [/hawtio/keycloak/enabled]
18:07:52.821 [qtp1016881733-22] DEBUG SimpleUrlHandlerMapping - Matching patterns for request [/hawtio/keycloak/enabled] are [/hawtio/**, /**]
18:07:52.821 [qtp1016881733-22] DEBUG SimpleUrlHandlerMapping - URI Template variables for request [/hawtio/keycloak/enabled] are {}
18:07:52.822 [qtp1016881733-22] DEBUG SimpleUrlHandlerMapping - Mapping [/hawtio/keycloak/enabled] to HandlerExecutionChain with handler [ResourceHttpRequestHandler [locations=[ServletContext resource [/], ServletContext resource [/app/], class path resource [hawtio-static/], class path resource [hawtio-static/app/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver#244d7ca5]]] and 1 interceptor
18:07:52.822 [qtp1016881733-22] DEBUG DispatcherServlet - Last-Modified value for [/hawtio/keycloak/enabled] is: -1
18:07:52.824 [qtp1016881733-22] DEBUG DispatcherServlet - DispatcherServlet with name 'dispatcherServlet' processing GET request for [/error]
18:07:52.824 [qtp1016881733-22] DEBUG RequestMappingHandlerMapping - Looking up handler method for path /error
18:07:52.825 [qtp1016881733-22] DEBUG RequestMappingHandlerMapping - Returning handler method [public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)]
18:07:52.825 [qtp1016881733-22] DEBUG DispatcherServlet - Last-Modified value for [/error] is: -1
18:07:52.833 [qtp1016881733-22] DEBUG ContentNegotiatingViewResolver - Requested media types are [text/html, text/html;q=0.8] based on Accept header types and producible media types [text/html])
18:07:52.833 [qtp1016881733-22] DEBUG BeanNameViewResolver - No matching bean found for view name 'error.html'
18:07:52.838 [qtp1016881733-22] DEBUG ContentNegotiatingViewResolver - Returning [org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelView#150fc7a7] based on requested media type 'text/html'
18:07:52.838 [qtp1016881733-22] DEBUG DispatcherServlet - Rendering view [org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelView#150fc7a7] in DispatcherServlet with name 'dispatcherServlet'
18:07:52.886 [qtp1016881733-22] DEBUG DispatcherServlet - Successfully completed request
18:07:52.887 [qtp1016881733-22] DEBUG DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
18:07:52.887 [qtp1016881733-22] DEBUG DispatcherServlet - Successfully completed request
18:07:52.965 [qtp1016881733-47] DEBUG DispatcherServlet - DispatcherServlet with name 'dispatcherServlet' processing GET request for [/favicon.ico]
18:07:52.965 [qtp1016881733-47] DEBUG SimpleUrlHandlerMapping - Matching patterns for request [/favicon.ico] are [/**/favicon.ico]
18:07:52.965 [qtp1016881733-47] DEBUG SimpleUrlHandlerMapping - URI Template variables for request [/favicon.ico] are {}
18:07:52.965 [qtp1016881733-47] DEBUG SimpleUrlHandlerMapping - Mapping [/favicon.ico] to HandlerExecutionChain with handler [ResourceHttpRequestHandler [locations=[ServletContext resource [/], class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/], class path resource []], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver#ec04917]]] and 1 interceptor
18:07:52.965 [qtp1016881733-47] DEBUG DispatcherServlet - Last-Modified value for [/favicon.ico] is: -1
18:07:52.969 [qtp1016881733-47] DEBUG DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
18:07:52.969 [qtp1016881733-47] DEBUG DispatcherServlet - Successfully completed request
Mostly the reason is that you just forget the final required step to use Hawtio with Spring Boot. You need this line in your application.properties:
endpoints.jolokia.sensitive = false
Without this setting Jolokia endpoint always returns 401 for unauthenticated requests, thus causing redirects to the login page.
You can also refer to a working example of unauthenticated Hawtio with Spring Boot here:
https://github.com/hawtio/hawtio/tree/master/hawtio-sample-springboot
By the way, Hawtio 2.0 will be released very soon.
Finally, i found the root cause is that
management.port != server.port
I referenced this one SpringBootCamelStarter, i also used 8095 for management port and 8091 for server port, and i used the normal server port 8091 to access it, that's the issue, i should use management port 8095 to access hawtio dashboard. here is the clear description:https://github.com/hawtio/hawtio/tree/2.x/examples/springboot

Spring boot do not change HTTP code in case of exception

I have trouble controlling the HTTP response code of my Spring Boot Rest server. The controller advice change the header www-authenticate but I keep getting 404 not found (tested using Postman).
Here is my code (made to generate 401 all the time).
The Configuration class:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
public SecurityConfig()
{
super(false);
}
#Override
protected AuthenticationManager authenticationManager() throws Exception
{
return new ProviderManager(Arrays.asList((AuthenticationProvider) new CustomAuthenticationProvider()));
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.httpBasic().authenticationEntryPoint(new MyAuthenticationEntryPoint());
http.httpBasic()
.and().authorizeRequests().anyRequest().hasAuthority("USER")
.and().csrf().disable();
}
}
The controller advice to indicate what to do in case of exception (actually trigger according to my console and the WWW-Authenticate value):
#ControllerAdvice
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint
{
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException
{
System.out.println("there");
response.setHeader("WWW-Authenticate", "Unauthorized test");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
}
The authentication provider that will always throw a BadCredentialsException.
public class CustomAuthenticationProvider implements AuthenticationProvider
{
public CustomAuthenticationProvider()
{
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
throw new BadCredentialsException("Bad credentials exception");
}
#Override
public boolean supports(Class<?> pClass)
{
return (pClass == UsernamePasswordAuthenticationToken.class);
}
}
Any idea what I did wrong?
Stacktrace:
..57,398 [DEBUG](o.s.web.servlet.DispatcherServlet) - DispatcherServlet with name 'dispatcherServlet' processing POST request for [/error]
..57,399 [TRACE](o.s.web.servlet.DispatcherServlet) - Testing handler map [org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#1d9af731] in DispatcherServlet with name 'dispatcherServlet'
..57,401 [TRACE](o.s.web.servlet.handler.SimpleUrlHandlerMapping) - No handler mapping found for [/error]
..57,401 [TRACE](o.s.web.servlet.DispatcherServlet) - Testing handler map [springfox.documentation.spring.web.PropertySourcedRequestMappingHandlerMapping#445058e8] in DispatcherServlet with name 'dispatcherServlet'
..57,404 [TRACE](o.s.web.servlet.DispatcherServlet) - Testing handler map [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#5423a17] in DispatcherServlet with name 'dispatcherServlet'
..57,405 [DEBUG](o.s.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping) - Looking up handler method for path /error
..57,412 [DEBUG](o.s.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping) - Did not find handler method for [/error]
..57,412 [TRACE](o.s.web.servlet.DispatcherServlet) - Testing handler map [org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WelcomePageHandlerMapping#347b370c] in DispatcherServlet with name 'dispatcherServlet'
..57,413 [TRACE](o.s.web.servlet.DispatcherServlet) - Testing handler map [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping#356fa0d1] in DispatcherServlet with name 'dispatcherServlet'
..57,413 [TRACE](o.s.web.servlet.handler.BeanNameUrlHandlerMapping) - No handler mapping found for [/error]
..57,413 [TRACE](o.s.web.servlet.DispatcherServlet) - Testing handler map [org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#31533eb1] in DispatcherServlet with name 'dispatcherServlet'
..57,413 [DEBUG](o.s.web.servlet.handler.SimpleUrlHandlerMapping) - Matching patterns for request [/error] are [/**]
..57,414 [DEBUG](o.s.web.servlet.handler.SimpleUrlHandlerMapping) - URI Template variables for request [/error] are {}
..57,416 [DEBUG](o.s.web.servlet.handler.SimpleUrlHandlerMapping) - Mapping [/error] to HandlerExecutionChain with handler [ResourceHttpRequestHandler [locations=[ServletContext resource [/], class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver#3c6fb501]]] and 1 interceptor
..57,418 [TRACE](o.s.web.servlet.DispatcherServlet) - Testing handler adapter [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#7ecb9e17]
..57,418 [TRACE](o.s.web.servlet.DispatcherServlet) - Testing handler adapter [org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter#4dac40b]
..57,427 [TRACE](o.s.web.servlet.resource.ResourceHttpRequestHandler) - Applying "invalid path" checks to path: error
..57,429 [TRACE](o.s.web.servlet.resource.PathResourceResolver) - Resolving resource for request path "error"
..57,429 [TRACE](o.s.web.servlet.resource.PathResourceResolver) - Checking location: ServletContext resource [/]
..57,429 [TRACE](o.s.web.servlet.resource.PathResourceResolver) - No match for location: ServletContext resource [/]
..57,429 [TRACE](o.s.web.servlet.resource.PathResourceResolver) - Checking location: class path resource [META-INF/resources/]
..57,430 [TRACE](o.s.web.servlet.resource.PathResourceResolver) - No match for location: class path resource [META-INF/resources/]
..57,430 [TRACE](o.s.web.servlet.resource.PathResourceResolver) - Checking location: class path resource [resources/]
..57,431 [TRACE](o.s.web.servlet.resource.PathResourceResolver) - No match for location: class path resource [resources/]
..57,431 [TRACE](o.s.web.servlet.resource.PathResourceResolver) - Checking location: class path resource [static/]
..57,432 [TRACE](o.s.web.servlet.resource.PathResourceResolver) - No match for location: class path resource [static/]
..57,432 [TRACE](o.s.web.servlet.resource.PathResourceResolver) - Checking location: class path resource [public/]
..57,432 [TRACE](o.s.web.servlet.resource.PathResourceResolver) - No match for location: class path resource [public/]
..57,432 [TRACE](o.s.web.servlet.resource.ResourceHttpRequestHandler) - No matching resource found - returning 404
..57,432 [DEBUG](o.s.web.servlet.DispatcherServlet) - Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
..57,432 [TRACE](o.s.web.servlet.DispatcherServlet) - Cleared thread-bound request context: org.apache.catalina.core.ApplicationHttpRequest#268be1ed
..57,432 [DEBUG](o.s.web.servlet.DispatcherServlet) - Successfully completed request
..57,433 [TRACE](o.s.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext) - Publishing event in org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext#40ef3420: ServletRequestHandledEvent: url=[/error]; client=[0:0:0:0:0:0:0:1]; method=[POST]; servlet=[dispatcherServlet]; session=[null]; user=[null]; time=[37ms]; status=[OK]
..57,433 [DEBUG](o.s.beans.factory.support.DefaultListableBeanFactory) - Returning cached instance of singleton bean 'delegatingApplicationListener'

Spring transactions - isolation and propagation understanding

I am exploring spring transactions and after going through the spring docs and the following link i still have few questions. If I have a parent method with Propogation.REQUIRED which is calling 3 methods with Propogation.REQUIRES_NEW as follows:
//#Transactional(isolation=Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public Contact saveContactInSteps(Contact contact, String newFirstName, String newLastName, Date bDate) throws Exception {
contact.setFirstName(newFirstName);
try {
updateContactFirstName(contact);
} catch (Exception e) {
LOG.error("Contact first name could not be saved !!");
e.printStackTrace();
throw e;
}
contact.setLastName(newLastName);
try {
updateContactLastName(contact);
} catch (Exception e) {
LOG.error("Contact Last name could not be saved !!");
e.printStackTrace();
throw e;
}
contact.setBirthDate(bDate);
updateContactBday(contact);
return contact;
}
public Contact saveById(Contact contact) {
sessionFactory.getCurrentSession().saveOrUpdate(contact);
LOG.info("Contact " + contact + " saved successfully");
return contact;
}
//#Transactional(isolation=Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public Contact updateContactFirstName(Contact contact) throws Exception {
throw new Exception("Cannot update first name");
//sessionFactory.getCurrentSession().saveOrUpdate(contact);
//LOG.info("Contact First name saved successfully");
//return contact;
}
//#Transactional(isolation=Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public Contact updateContactLastName(Contact contact) throws Exception {
throw new Exception("Cannot update last name");
//sessionFactory.getCurrentSession().saveOrUpdate(contact);
//LOG.info("Contact " + contact + " saved successfully");
//return contact;
}
//#Transactional(isolation=Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public Contact updateContactBday(Contact contact) {
sessionFactory.getCurrentSession().saveOrUpdate(contact);
LOG.info("Contact Birth Date saved successfully");
return contact;
}
Whenever i process the parent method i.e. saveContactInSteps, even the last name is getting updated in database. I am not sure how that is happening. If i throw the exception from first child method nothing gets updated. Is it because of the fact that i am setting the attributes i.e. names and birthdate in the parent method ? Even if i am setting the attributes in the parent method, i am not letting the parent method complete as well. Does the isolation level play any part here ? I believe it's for concurrent transactions ? Is there something i am missing ?
EDIT 1:
After going through the article i did realize what i was missing. I needed AOP proxies in place which i have added as follows:
<aop:config>
<aop:pointcut
expression="execution(* org.pack.spring.transactions.ContactService.*(..))"
id="updateOperation" />
<aop:advisor pointcut-ref="updateOperation" advice-ref="saveUpdateAdvice" />
</aop:config>
<tx:advice id="saveUpdateAdvice">
<tx:attributes>
<tx:method name="update*" propagation="REQUIRES_NEW" isolation="DEFAULT" rollback-for="Exception"/>
<tx:method name="save*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
Log output is as follows:
01:23:57.535 [main] DEBUG o.s.t.a.AnnotationTransactionAttributeSource - Adding transactional method 'ContactService.save' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
01:23:57.550 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.transaction.interceptor.TransactionInterceptor#0'
01:23:57.552 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating shared instance of singleton bean 'saveUpdateAdvice'
01:23:57.552 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean 'saveUpdateAdvice'
01:23:57.552 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Eagerly caching bean 'saveUpdateAdvice' to allow for resolving potential circular references
01:23:57.553 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'transactionManager'
01:23:57.553 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Creating instance of bean '(inner bean)#56ac5c80'
01:23:57.553 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
01:23:57.553 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
01:23:57.557 [main] DEBUG o.s.t.i.NameMatchTransactionAttributeSource - Adding transactional method [update*] with attribute [PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT,-Exception]
01:23:57.557 [main] DEBUG o.s.t.i.NameMatchTransactionAttributeSource - Adding transactional method [save*] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-Exception]
01:23:57.557 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
01:23:57.557 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
01:23:57.557 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
01:23:57.557 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
01:23:57.558 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Finished creating instance of bean '(inner bean)#56ac5c80'
01:23:57.558 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Invoking afterPropertiesSet() on bean with name 'saveUpdateAdvice'
01:23:57.558 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Finished creating instance of bean 'saveUpdateAdvice'
01:23:57.568 [main] DEBUG o.s.a.a.a.AspectJAwareAdvisorAutoProxyCreator - Creating implicit proxy for bean 'springTxContactService' with 0 common interceptors and 3 specific interceptors
01:23:57.575 [main] DEBUG o.s.aop.framework.CglibAopProxy - Creating CGLIB proxy: target source is SingletonTargetSource for target object [org.pack.ch9.spring.transactions.hibernate.home.ContactService#598260a6]
01:23:57.654 [main] DEBUG o.s.aop.framework.CglibAopProxy - Unable to apply any optimisations to advised method: public org.pack.ch9.spring.transactions.hibernate.home.Contact org.pack.ch9.spring.transactions.hibernate.home.ContactService.save(org.pack.ch9.spring.transactions.hibernate.home.Contact)
01:23:57.654 [main] DEBUG o.s.t.a.AnnotationTransactionAttributeSource - Adding transactional method 'ContactService.saveContactInSteps' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
01:23:57.655 [main] DEBUG o.s.aop.framework.CglibAopProxy - Unable to apply any optimisations to advised method: public org.pack.ch9.spring.transactions.hibernate.home.Contact org.pack.ch9.spring.transactions.hibernate.home.ContactService.saveContactInSteps(org.pack.ch9.spring.transactions.hibernate.home.Contact,java.lang.String,java.lang.String,java.util.Date) throws java.lang.Exception
01:23:57.655 [main] DEBUG o.s.t.a.AnnotationTransactionAttributeSource - Adding transactional method 'ContactService.findAll' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly; ''
01:23:57.656 [main] DEBUG o.s.aop.framework.CglibAopProxy - Unable to apply any optimisations to advised method: public java.util.List org.pack.ch9.spring.transactions.hibernate.home.ContactService.findAll()
01:23:57.656 [main] DEBUG o.s.t.a.AnnotationTransactionAttributeSource - Adding transactional method 'ContactService.findById' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly; ''
01:23:57.656 [main] DEBUG o.s.aop.framework.CglibAopProxy - Unable to apply any optimisations to advised method: public org.pack.ch9.spring.transactions.hibernate.home.Contact org.pack.ch9.spring.transactions.hibernate.home.ContactService.findById(java.lang.Long)
01:23:57.657 [main] DEBUG o.s.t.a.AnnotationTransactionAttributeSource - Adding transactional method 'ContactService.saveById' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
01:23:57.657 [main] DEBUG o.s.aop.framework.CglibAopProxy - Unable to apply any optimisations to advised method: public org.pack.ch9.spring.transactions.hibernate.home.Contact org.pack.ch9.spring.transactions.hibernate.home.ContactService.saveById(org.pack.ch9.spring.transactions.hibernate.home.Contact)
01:23:57.657 [main] DEBUG o.s.t.a.AnnotationTransactionAttributeSource - Adding transactional method 'ContactService.updateContactFirstName' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
01:23:57.657 [main] DEBUG o.s.aop.framework.CglibAopProxy - Unable to apply any optimisations to advised method: public org.pack.ch9.spring.transactions.hibernate.home.Contact org.pack.ch9.spring.transactions.hibernate.home.ContactService.updateContactFirstName(org.pack.ch9.spring.transactions.hibernate.home.Contact) throws java.lang.Exception
01:23:57.658 [main] DEBUG o.s.t.a.AnnotationTransactionAttributeSource - Adding transactional method 'ContactService.updateContactLastName' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
01:23:57.658 [main] DEBUG o.s.aop.framework.CglibAopProxy - Unable to apply any optimisations to advised method: public org.pack.ch9.spring.transactions.hibernate.home.Contact org.pack.ch9.spring.transactions.hibernate.home.ContactService.updateContactLastName(org.pack.ch9.spring.transactions.hibernate.home.Contact) throws java.lang.Exception
01:23:57.658 [main] DEBUG o.s.t.a.AnnotationTransactionAttributeSource - Adding transactional method 'ContactService.updateContactBday' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
01:23:57.658 [main] DEBUG o.s.aop.framework.CglibAopProxy - Unable to apply any optimisations to advised method: public org.pack.ch9.spring.transactions.hibernate.home.Contact org.pack.ch9.spring.transactions.hibernate.home.ContactService.updateContactBday(org.pack.ch9.spring.transactions.hibernate.home.Contact)
01:23:57.659 [main] DEBUG o.s.t.a.AnnotationTransactionAttributeSource - Adding transactional method 'ContactService.setSessionFactory' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
01:23:57.660 [main] DEBUG o.s.aop.framework.CglibAopProxy - Unable to apply any optimisations to advised method: public void org.pack.ch9.spring.transactions.hibernate.home.ContactService.setSessionFactory(org.hibernate.SessionFactory)
01:23:57.661 [main] DEBUG o.s.t.a.AnnotationTransactionAttributeSource - Adding transactional method 'ContactService.getSessionFactory' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
01:23:57.661 [main] DEBUG o.s.aop.framework.CglibAopProxy - Unable to apply any optimisations to advised method: public org.hibernate.SessionFactory org.pack.ch9.spring.transactions.hibernate.home.ContactService.getSessionFactory()
01:23:57.661 [main] DEBUG o.s.aop.framework.CglibAopProxy - Found 'equals' method: public boolean java.lang.Object.equals(java.lang.Object)
01:23:57.662 [main] DEBUG o.s.aop.framework.CglibAopProxy - Unable to apply any optimisations to advised method: public java.lang.String java.lang.Object.toString()
01:23:57.698 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Finished creating instance of bean 'org.springframework.context.event.internalEventListenerFactory'
01:23:57.698 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
01:23:57.698 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'saveUpdateAdvice'
01:23:57.698 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'sessionFactory'
01:23:57.698 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'hibernateProperties'
01:23:57.698 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor'
01:23:57.698 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor'
01:23:57.699 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionalEventListenerFactory'
01:23:57.699 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
01:23:57.746 [main] DEBUG o.s.c.s.GenericXmlApplicationContext - Unable to locate LifecycleProcessor with name 'lifecycleProcessor': using default [org.springframework.context.support.DefaultLifecycleProcessor#21d1b321]
01:23:57.747 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'lifecycleProcessor'
01:23:57.750 [main] DEBUG o.s.c.e.PropertySourcesPropertyResolver - Could not find key 'spring.liveBeansView.mbeanDomain' in any property source
01:23:57.752 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'springTxContactService'
01:23:57.755 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'transactionManager'
01:23:57.762 [main] DEBUG o.s.o.h.HibernateTransactionManager - Creating new transaction with name [org.pack.ch9.spring.transactions.hibernate.home.ContactService.findById]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly; ''
01:23:57.962 [main] DEBUG o.s.o.h.HibernateTransactionManager - Opened new Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=ExecutableList{size=0} updates=ExecutableList{size=0} deletions=ExecutableList{size=0} orphanRemovals=ExecutableList{size=0} collectionCreations=ExecutableList{size=0} collectionRemovals=ExecutableList{size=0} collectionUpdates=ExecutableList{size=0} collectionQueuedOps=ExecutableList{size=0} unresolvedInsertDependencies=null])] for Hibernate transaction
01:23:57.964 [main] DEBUG o.s.o.h.HibernateTransactionManager - Preparing JDBC Connection of Hibernate Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=ExecutableList{size=0} updates=ExecutableList{size=0} deletions=ExecutableList{size=0} orphanRemovals=ExecutableList{size=0} collectionCreations=ExecutableList{size=0} collectionRemovals=ExecutableList{size=0} collectionUpdates=ExecutableList{size=0} collectionQueuedOps=ExecutableList{size=0} unresolvedInsertDependencies=null])]
01:23:57.965 [main] DEBUG o.s.j.d.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:mysql://localhost:3306/springorm]
01:23:57.977 [main] DEBUG o.s.jdbc.datasource.DataSourceUtils - Setting JDBC Connection [com.mysql.jdbc.JDBC4Connection#2a685eba] read-only
01:23:57.983 [main] DEBUG o.s.o.h.HibernateTransactionManager - Exposing Hibernate transaction as JDBC transaction [com.mysql.jdbc.JDBC4Connection#2a685eba]
Hibernate: select distinct contact0_.ID as ID1_0_0_, contacttel1_.ID as ID1_2_1_, hobby3_.HOBBY_ID as HOBBY_ID1_3_2_, contact0_.BIRTH_DATE as BIRTH_DA2_0_0_, contact0_.FIRST_NAME as FIRST_NA3_0_0_, contact0_.LAST_NAME as LAST_NAM4_0_0_, contact0_.VERSION as VERSION5_0_0_, contacttel1_.CONTACT_ID as CONTACT_5_2_1_, contacttel1_.TEL_NUMBER as TEL_NUMB2_2_1_, contacttel1_.TEL_TYPE as TEL_TYPE3_2_1_, contacttel1_.VERSION as VERSION4_2_1_, contacttel1_.CONTACT_ID as CONTACT_5_2_0__, contacttel1_.ID as ID1_2_0__, hobbies2_.CONTACT_ID as CONTACT_2_1_1__, hobbies2_.HOBBY_ID as HOBBY_ID1_1_1__ from contact contact0_ left outer join contact_tel_detail contacttel1_ on contact0_.ID=contacttel1_.CONTACT_ID left outer join contact_hobby_detail hobbies2_ on contact0_.ID=hobbies2_.CONTACT_ID left outer join hobby hobby3_ on hobbies2_.HOBBY_ID=hobby3_.HOBBY_ID where contact0_.ID=?
01:23:58.099 [main] DEBUG o.s.o.h.HibernateTransactionManager - Initiating transaction commit
01:23:58.101 [main] DEBUG o.s.o.h.HibernateTransactionManager - Committing Hibernate transaction on Session [SessionImpl(PersistenceContext[entityKeys=[EntityKey[org.pack.ch9.spring.transactions.hibernate.home.Contact#6], EntityKey[org.pack.ch9.spring.transactions.hibernate.home.ContactTelDetail#6], EntityKey[org.pack.ch9.spring.transactions.hibernate.home.ContactTelDetail#7]],collectionKeys=[CollectionKey[org.pack.ch9.spring.transactions.hibernate.home.Contact.contactTelDetails#6], CollectionKey[org.pack.ch9.spring.transactions.hibernate.home.Contact.hobbies#6]]];ActionQueue[insertions=ExecutableList{size=0} updates=ExecutableList{size=0} deletions=ExecutableList{size=0} orphanRemovals=ExecutableList{size=0} collectionCreations=ExecutableList{size=0} collectionRemovals=ExecutableList{size=0} collectionUpdates=ExecutableList{size=0} collectionQueuedOps=ExecutableList{size=0} unresolvedInsertDependencies=null])]
01:23:58.103 [main] DEBUG o.s.jdbc.datasource.DataSourceUtils - Resetting read-only flag of JDBC Connection [com.mysql.jdbc.JDBC4Connection#2a685eba]
01:23:58.103 [main] DEBUG o.s.o.h.HibernateTransactionManager - Closing Hibernate Session [SessionImpl(PersistenceContext[entityKeys=[EntityKey[org.pack.ch9.spring.transactions.hibernate.home.Contact#6], EntityKey[org.pack.ch9.spring.transactions.hibernate.home.ContactTelDetail#6], EntityKey[org.pack.ch9.spring.transactions.hibernate.home.ContactTelDetail#7]],collectionKeys=[CollectionKey[org.pack.ch9.spring.transactions.hibernate.home.Contact.contactTelDetails#6], CollectionKey[org.pack.ch9.spring.transactions.hibernate.home.Contact.hobbies#6]]];ActionQueue[insertions=ExecutableList{size=0} updates=ExecutableList{size=0} deletions=ExecutableList{size=0} orphanRemovals=ExecutableList{size=0} collectionCreations=ExecutableList{size=0} collectionRemovals=ExecutableList{size=0} collectionUpdates=ExecutableList{size=0} collectionQueuedOps=ExecutableList{size=0} unresolvedInsertDependencies=null])] after transaction
01:23:58.117 [main] DEBUG o.s.o.h.HibernateTransactionManager - Creating new transaction with name [org.pack.ch9.spring.transactions.hibernate.home.ContactService.saveContactInSteps]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
01:23:58.117 [main] DEBUG o.s.o.h.HibernateTransactionManager - Opened new Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=ExecutableList{size=0} updates=ExecutableList{size=0} deletions=ExecutableList{size=0} orphanRemovals=ExecutableList{size=0} collectionCreations=ExecutableList{size=0} collectionRemovals=ExecutableList{size=0} collectionUpdates=ExecutableList{size=0} collectionQueuedOps=ExecutableList{size=0} unresolvedInsertDependencies=null])] for Hibernate transaction
01:23:58.117 [main] DEBUG o.s.o.h.HibernateTransactionManager - Preparing JDBC Connection of Hibernate Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=ExecutableList{size=0} updates=ExecutableList{size=0} deletions=ExecutableList{size=0} orphanRemovals=ExecutableList{size=0} collectionCreations=ExecutableList{size=0} collectionRemovals=ExecutableList{size=0} collectionUpdates=ExecutableList{size=0} collectionQueuedOps=ExecutableList{size=0} unresolvedInsertDependencies=null])]
01:23:58.117 [main] DEBUG o.s.j.d.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:mysql://localhost:3306/springorm]
01:23:58.127 [main] DEBUG o.s.o.h.HibernateTransactionManager - Exposing Hibernate transaction as JDBC transaction [com.mysql.jdbc.JDBC4Connection#1daf3b44]
01:23:58.127 [main] DEBUG o.s.o.h.HibernateTransactionManager - Found thread-bound Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=ExecutableList{size=0} updates=ExecutableList{size=0} deletions=ExecutableList{size=0} orphanRemovals=ExecutableList{size=0} collectionCreations=ExecutableList{size=0} collectionRemovals=ExecutableList{size=0} collectionUpdates=ExecutableList{size=0} collectionQueuedOps=ExecutableList{size=0} unresolvedInsertDependencies=null])] for Hibernate transaction
01:23:58.127 [main] DEBUG o.s.o.h.HibernateTransactionManager - Participating in existing transaction
01:23:58.127 [main] ERROR o.p.c.s.t.h.home.ContactService - Contact First name could not be saved !!
01:23:58.127 [main] ERROR o.p.c.s.t.h.home.ContactService - Contact Last name could not be saved !!
01:23:58.137 [main] INFO o.p.c.s.t.h.home.ContactService - Contact Birth Date saved successfully
01:23:58.138 [main] DEBUG o.s.o.h.HibernateTransactionManager - Initiating transaction commit
01:23:58.138 [main] DEBUG o.s.o.h.HibernateTransactionManager - Committing Hibernate transaction on Session [SessionImpl(PersistenceContext[entityKeys=[EntityKey[org.pack.ch9.spring.transactions.hibernate.home.Contact#6], EntityKey[org.pack.ch9.spring.transactions.hibernate.home.ContactTelDetail#6], EntityKey[org.pack.ch9.spring.transactions.hibernate.home.ContactTelDetail#7]],collectionKeys=[CollectionKey[org.pack.ch9.spring.transactions.hibernate.home.Contact.contactTelDetails#6], CollectionKey[org.pack.ch9.spring.transactions.hibernate.home.Contact.hobbies#6]]];ActionQueue[insertions=ExecutableList{size=0} updates=ExecutableList{size=0} deletions=ExecutableList{size=0} orphanRemovals=ExecutableList{size=0} collectionCreations=ExecutableList{size=0} collectionRemovals=ExecutableList{size=0} collectionUpdates=ExecutableList{size=0} collectionQueuedOps=ExecutableList{size=0} unresolvedInsertDependencies=null])]
Note - I have removed the #Transactional annotations from the methods as i am relying on proxy to achieve the same.
From the logs, it seems that the proxies are getting created but again as pointed out in the comment, will the 3 methods invoked from saveContactInSteps not have a transaction against them as mentioned in the documentation :
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with #Transactional. Also, the proxy must be fully initialized to provide the expected behaviour so you should not rely on this feature in your initialization code, i.e. #PostConstruct
Is there a way to get around this ? Is AspectJ the only solution ?

Difficulty in configuring consumer side contract cloud tests

I'm trying to use spring cloud contract to test my REST API. I'm following the instructions described is this tutorial https://cloud.spring.io/spring-cloud-contract/
I'm writing my single base test class using groovy.
My Rest Controller class:
#RestController
#RequestMapping(headers = arrayOf("X-API-Version=v2"))
class FileControllerV2(val storageService: StorageService) {
#PostMapping("/contracts.upload")
fun upload(#RequestParam file: MultipartFile, principal: Principal): FileNameResponse {
return storageService.save(file, principal.name)
}
#GetMapping("/contracts.download/{filename:.+}")
fun download(#PathVariable filename: String): ResponseEntity<*> {
return storageService.download(filename)
}
#GetMapping("/contracts.info/{filename:.+}")
fun info(#PathVariable filename: String): ResponseEntity<*> {
return storageService.info(filename)
}
}
My Contract Stub
package contracts.upload
import org.springframework.cloud.contract.spec.Contract
Contract.make {
request {
method 'POST'
url '/contracts.upload'
multipart(
file: named(
name: $(c(regex(nonEmpty())), p('filename')),
content: $(c(regex(nonEmpty())), p('content'))),
principal : $(regex('.+'))
)
headers {
contentType('multipart/form-data; boundary=----WebKitFormBoundaryBTX23kTw0Z5a5bsF')
header('X-API-Version','v2')
}
}
response {
status 200
body([
filename: 'filename'
])
headers {
contentType('application/json')
}
}
}
And my Base class:
class UploadBase extends Specification {
private StorageService storageService = Mock()
def setup() {
given:
RestAssuredMockMvc.standaloneSetup(new FileControllerV2(storageService))
}
}
And after building I've got following stacktrace
15:49:59.550 [Test worker] DEBUG org.springframework.core.env.StandardEnvironment - Adding [systemProperties] PropertySource with lowest search precedence
15:49:59.554 [Test worker] DEBUG org.springframework.core.env.StandardEnvironment - Adding [systemEnvironment] PropertySource with lowest search precedence
15:49:59.554 [Test worker] DEBUG org.springframework.core.env.StandardEnvironment - Initialized StandardEnvironment with PropertySources [systemProperties,systemEnvironment]
15:49:59.681 [Test worker] DEBUG org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder$StaticRequestMappingHandlerMapping - Looking for request mappings in application context: org.springframework.test.web.servlet.setup.StubWebApplicationContext#5c4dadd7
15:49:59.712 [Test worker] DEBUG org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder$StaticRequestMappingHandlerMapping - 3 request handler methods found on class com.project.fstorage.storage.FileControllerV2: {public org.springframework.http.ResponseEntity com.project.fstorage.storage.FileControllerV2.download(java.lang.String)={[/contracts.download/{filename:.+}],methods=[GET],headers=[X-API-Version=v2]}, public com.project.fstorage.response.FileNameResponse com.project.fstorage.storage.FileControllerV2.upload(org.springframework.web.multipart.MultipartFile,java.security.Principal)={[/contracts.upload],methods=[POST],headers=[X-API-Version=v2]}, public org.springframework.http.ResponseEntity com.project.fstorage.storage.FileControllerV2.info(java.lang.String)={[/contracts.info/{filename:.+}],methods=[GET],headers=[X-API-Version=v2]}}
15:49:59.723 [Test worker] INFO org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder$StaticRequestMappingHandlerMapping - Mapped "{[/contracts.download/{filename:.+}],methods=[GET],headers=[X-API-Version=v2]}" onto public org.springframework.http.ResponseEntity<?> com.project.fstorage.storage.FileControllerV2.download(java.lang.String)
15:49:59.725 [Test worker] INFO org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder$StaticRequestMappingHandlerMapping - Mapped "{[/contracts.upload],methods=[POST],headers=[X-API-Version=v2]}" onto public com.project.fstorage.response.FileNameResponse com.project.fstorage.storage.FileControllerV2.upload(org.springframework.web.multipart.MultipartFile,java.security.Principal)
15:49:59.725 [Test worker] INFO org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder$StaticRequestMappingHandlerMapping - Mapped "{[/contracts.info/{filename:.+}],methods=[GET],headers=[X-API-Version=v2]}" onto public org.springframework.http.ResponseEntity<?> com.project.fstorage.storage.FileControllerV2.info(java.lang.String)
15:49:59.994 [Test worker] DEBUG org.jboss.logging - Logging Provider: org.jboss.logging.Slf4jLoggerProvider
15:49:59.995 [Test worker] INFO org.hibernate.validator.internal.util.Version - HV000001: Hibernate Validator 5.3.5.Final
15:50:00.015 [Test worker] DEBUG org.hibernate.validator.internal.engine.resolver.DefaultTraversableResolver - Found javax.persistence.Persistence on classpath containing 'getPersistenceUtil'. Assuming JPA 2 environment. Trying to instantiate JPA aware TraversableResolver
15:50:00.016 [Test worker] DEBUG org.hibernate.validator.internal.engine.resolver.DefaultTraversableResolver - Instantiated JPA aware TraversableResolver of type org.hibernate.validator.internal.engine.resolver.JPATraversableResolver.
15:50:00.025 [Test worker] DEBUG org.hibernate.validator.internal.engine.ConfigurationImpl - Setting custom MessageInterpolator of type org.springframework.validation.beanvalidation.LocaleContextMessageInterpolator
15:50:00.026 [Test worker] DEBUG org.hibernate.validator.internal.engine.ConfigurationImpl - Setting custom ParameterNameProvider of type com.sun.proxy.$Proxy36
15:50:00.028 [Test worker] DEBUG org.hibernate.validator.internal.xml.ValidationXmlParser - Trying to load META-INF/validation.xml for XML based Validator configuration.
15:50:00.029 [Test worker] DEBUG org.hibernate.validator.internal.xml.ResourceLoaderHelper - Trying to load META-INF/validation.xml via TCCL
15:50:00.029 [Test worker] DEBUG org.hibernate.validator.internal.xml.ResourceLoaderHelper - Trying to load META-INF/validation.xml via Hibernate Validator's class loader
15:50:00.030 [Test worker] DEBUG org.hibernate.validator.internal.xml.ValidationXmlParser - No META-INF/validation.xml found. Using annotation based configuration only.
15:50:00.068 [Test worker] INFO org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter - Looking for #ControllerAdvice: org.springframework.test.web.servlet.setup.StubWebApplicationContext#5c4dadd7
15:50:00.096 [Test worker] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Looking for exception mappings: org.springframework.test.web.servlet.setup.StubWebApplicationContext#5c4dadd7
15:50:00.113 [Test worker] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Initializing servlet ''
15:50:00.122 [Test worker] DEBUG org.springframework.web.context.support.StandardServletEnvironment - Adding [servletConfigInitParams] PropertySource with lowest search precedence
15:50:00.122 [Test worker] DEBUG org.springframework.web.context.support.StandardServletEnvironment - Adding [servletContextInitParams] PropertySource with lowest search precedence
15:50:00.125 [Test worker] DEBUG org.springframework.web.context.support.StandardServletEnvironment - Adding [systemProperties] PropertySource with lowest search precedence
15:50:00.125 [Test worker] DEBUG org.springframework.web.context.support.StandardServletEnvironment - Adding [systemEnvironment] PropertySource with lowest search precedence
15:50:00.125 [Test worker] DEBUG org.springframework.web.context.support.StandardServletEnvironment - Initialized StandardServletEnvironment with PropertySources [servletConfigInitParams,servletContextInitParams,systemProperties,systemEnvironment]
15:50:00.125 [Test worker] INFO org.springframework.mock.web.MockServletContext - Initializing Spring FrameworkServlet ''
15:50:00.125 [Test worker] INFO org.springframework.test.web.servlet.TestDispatcherServlet - FrameworkServlet '': initialization started
15:50:00.127 [Test worker] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Unable to locate MultipartResolver with name 'multipartResolver': no multipart request handling provided
15:50:00.127 [Test worker] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Using LocaleResolver [org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver#186a2f7]
15:50:00.128 [Test worker] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Using ThemeResolver [org.springframework.web.servlet.theme.FixedThemeResolver#71cabba8]
15:50:00.128 [Test worker] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Using RequestToViewNameTranslator [org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator#53aff6f3]
15:50:00.128 [Test worker] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Using FlashMapManager [org.springframework.web.servlet.support.SessionFlashMapManager#5138be3e]
15:50:00.128 [Test worker] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Published WebApplicationContext of servlet '' as ServletContext attribute with name [org.springframework.web.servlet.FrameworkServlet.CONTEXT.]
15:50:00.128 [Test worker] INFO org.springframework.test.web.servlet.TestDispatcherServlet - FrameworkServlet '': initialization completed in 3 ms
15:50:00.128 [Test worker] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Servlet '' configured successfully
15:50:00.175 [Test worker] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - DispatcherServlet with name '' processing POST request for [/contracts.upload]
15:50:00.177 [Test worker] DEBUG org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder$StaticRequestMappingHandlerMapping - Looking up handler method for path /contracts.upload
15:50:00.179 [Test worker] DEBUG org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder$StaticRequestMappingHandlerMapping - Did not find handler method for [/contracts.upload]
15:50:00.179 [Test worker] WARN org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [/contracts.upload] in DispatcherServlet with name ''
15:50:00.179 [Test worker] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Successfully completed request
Condition not satisfied:
response.statusCode == 200
| | |
| 404 false
io.restassured.module.mockmvc.internal.MockMvcRestAssuredResponseImpl#77920871
Condition not satisfied:
response.statusCode == 200
| | |
| 404 false
io.restassured.module.mockmvc.internal.MockMvcRestAssuredResponseImpl#77920871
at org.springframework.cloud.contract.verifier.tests.UploadSpec.validate_shouldSaveFile(UploadSpec.groovy:25)
I don't know what can be the cause of not recognising these endpoints.
Edited
I've added generated tests
package org.springframework.cloud.contract.verifier.tests
import com.jayway.jsonpath.DocumentContext
import com.jayway.jsonpath.JsonPath
import com.project.fstorage.UploadBase
import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson
import static io.restassured.module.mockmvc.RestAssuredMockMvc.*
import static org.springframework.cloud.contract.verifier.assertion.SpringCloudContractAssertions.assertThat
class UploadSpec extends UploadBase {
def validate_shouldSaveFile() throws Exception {
given:
def request = given()
.header("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundaryBTX23kTw0Z5a5bsF")
.header("X-API-Version", "v2")
.multiPart('file', 'filename', 'content'.bytes)
.param('principal', 'レ')
when:
def response = given().spec(request)
.post("/contracts.upload")
then:
response.statusCode == 200
response.header('Content-Type') ==~ java.util.regex.Pattern.compile('application/json.*')
and:
DocumentContext parsedJson = JsonPath.parse(response.body.asString())
assertThatJson(parsedJson).field("['filename']").isEqualTo("filename")
}
}
In case of using Security it's better to set up the normal context to rest assured or add the filter manually.
Example of setting up the web application context
#Autowired
private WebApplicationContext context;
#Before
public void setup() {
RestAssuredMockMvc.webAppContextSetup(context);
}

What is wrong in this Spring Boot Swagger configuration to obtain the documentation of my REST API? Why I can't access to the documentation?

I am working on a Spring Boot application and I am trying to configure Swagger to automatically generate my REST service documentation.
I am following this tutorial: http://www.baeldung.com/swagger-2-documentation-for-spring-rest-api
But I am finding some difficulties to do it.
So basically I have created the following Java configuration class into my Spring Boot project:
#Configuration
#EnableSwagger2
public class Config {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
I think that this is the minimal configuration to generate the Swagger documentation of all my REST service. I have put this configuration class at the same level of the Application class (the class that contains the main() method that start my project). I think that the Config class is ok because I have tryed to put a brack point into the api() method and I can see that it enter in this method at the project startup so I think that this configuration is loaded.
Then in the previous tutorial it say that to verify that the my REST API Swagger documentation is generated I have to perform a GET request to this URL: http://localhost:8080/spring-security-rest/api/v2/api-docs
I think that this URL is related to the example project and not to my project.
So I tried to use: http://localhost:8080/api-docs
But doing in this way I am obtaining this error message:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sat Jan 14 16:05:23 CET 2017
There was an unexpected error (type=Not Found, status=404).
No message available
And in the IDE console I have this message:
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:865)] [http-nio-8080-exec-4] DispatcherServlet - DispatcherServlet with name 'dispatcherServlet' processing GET request for [/swagger-ui.html]
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:310)] [http-nio-8080-exec-4] RequestMappingHandlerMapping - Looking up handler method for path /swagger-ui.html
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:320)] [http-nio-8080-exec-4] RequestMappingHandlerMapping - Did not find handler method for [/swagger-ui.html]
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.lookupHandler(AbstractUrlHandlerMapping.java:190)] [http-nio-8080-exec-4] SimpleUrlHandlerMapping - Matching patterns for request [/swagger-ui.html] are [/**]
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.lookupHandler(AbstractUrlHandlerMapping.java:219)] [http-nio-8080-exec-4] SimpleUrlHandlerMapping - URI Template variables for request [/swagger-ui.html] are {}
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.getHandlerInternal(AbstractUrlHandlerMapping.java:140)] [http-nio-8080-exec-4] SimpleUrlHandlerMapping - Mapping [/swagger-ui.html] to HandlerExecutionChain with handler [ResourceHttpRequestHandler [locations=[ServletContext resource [/], class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver#4e671ef]]] and 1 interceptor
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:951)] [http-nio-8080-exec-4] DispatcherServlet - Last-Modified value for [/swagger-ui.html] is: -1
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1044)] [http-nio-8080-exec-4] DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1000)] [http-nio-8080-exec-4] DispatcherServlet - Successfully completed request
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:865)] [http-nio-8080-exec-4] DispatcherServlet - DispatcherServlet with name 'dispatcherServlet' processing GET request for [/error]
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:310)] [http-nio-8080-exec-4] RequestMappingHandlerMapping - Looking up handler method for path /error
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:317)] [http-nio-8080-exec-4] RequestMappingHandlerMapping - Returning handler method [public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)]
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:951)] [http-nio-8080-exec-4] DispatcherServlet - Last-Modified value for [/error] is: -1
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.view.ContentNegotiatingViewResolver.getMediaTypes(ContentNegotiatingViewResolver.java:263)] [http-nio-8080-exec-4] ContentNegotiatingViewResolver - Requested media types are [text/html, text/html;q=0.8] based on Accept header types and producible media types [text/html])
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.view.BeanNameViewResolver.resolveViewName(BeanNameViewResolver.java:74)] [http-nio-8080-exec-4] BeanNameViewResolver - No matching bean found for view name 'error.html'
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.view.ContentNegotiatingViewResolver.getBestView(ContentNegotiatingViewResolver.java:338)] [http-nio-8080-exec-4] ContentNegotiatingViewResolver - Returning [org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelView#50e8ed74] based on requested media type 'text/html'
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1251)] [http-nio-8080-exec-4] DispatcherServlet - Rendering view [org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelView#50e8ed74] in DispatcherServlet with name 'dispatcherServlet'
[DEBUG] 2017-01-14 16:40:05 [org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1000)] [http-nio-8080-exec-4] DispatcherServlet - Successfully completed request
The thing that seems me strange is that I have configured nothing related to the URL for my documentation (I don't know if there is a standard URL).
Some further details that maybe could are important to find a solution.
My REST API are implemented in Spring Boot by Controller class that handles request like this:
package com.myapp.controller.room;
#RestController
#RequestMapping("/Room")
public class RoomController {
private static final Logger log = LoggerFactory.getLogger(RoomController.class);
#Autowired
private RoomService roomService;
#Autowired
private RoomMediaService roomMediaService;
public RoomController(){
log.debug("RoomController init");
}
/**
* Ritorna la tipologia di stanza associata ad una stanza
* #param id dellla stanza di cui si intende reperire le informazioni relative alla sua tipologia
* #return RoomTipologyDTO contenente le informazioni relative alla tipologia di stanza
* #throws DataAccessException
*/
#RequestMapping(value = "/{id}/RoomTipology",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<RoomTipologyDTO> getRoomTipologyByRoomId(#PathVariable Long id) throws DataAccessException{
log.debug("getRoomTipologyByRoomId START");
RoomTipologyDTO result = roomService.getRoomTipologyByRoom(id);
log.debug("getRoomTipologyByRoomId END");
return ResponseEntity.ok(result);
}
....................................................................
....................................................................
....................................................................
}
This is the Application class that contains the main() method that start my application:
#SpringBootApplication
#EntityScan("com.betrivius.domain")
#ComponentScan(lazyInit = true)
#EnableAutoConfiguration
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
//context.close();
log.info("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = context.getBeanDefinitionNames();
Arrays.sort(beanNames);
log.info("_______________________________________________________");
for (String beanName : beanNames) {
log.info(beanName);
}
log.info("main() END");
}
}
An example of an URL of one o my service is something like this:
http://localhost:8080/RoomRate/1/RoomRateOptionList
So what is the correct URL to generate my Swagger documentation? Or what am I missing? How can I fix this issue?
Try accessing swagger docs on http://localhost:8080/v2/api-docs. it should work. i think you are using version 2 of swagger documentation.

Resources