Spring Boot Swagger HTML documentation is not getting displayed - spring-boot

I am setting up a REST API / Micro-services project using Spring Boot. I am also trying to enable swagger documentation. When I run spring boot application, I am able to see browse to the localhost:8080/v2/api-docs and it returns API documentation. However, when I attempt browsing http://localhost:8080/documentation/swagger-ui.html or http://localhost:8080/swagger-ui.html browser does not display swagger UI documentation.
I have included following dependencies in the pom file to enable documentation :
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
Also, I have create following swagger config class:
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket api(){
return new Docket(DocumentationType.SWAGGER_2);
}
}
The response I am seeing when trying to access http://localhost:8080/servlet-context :
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Fri Dec 21 03:16:26 GMT 2018
There was an unexpected error (type=Not Found, status=404).
No message available
Server Logs :
2018-12-21 03:25:14.294 DEBUG 4049 --- [ restartedMain] o.s.c.e.PropertySourcesPropertyResolver : Found key 'spring.liveBeansView.mbeanDomain' in PropertySource 'systemProperties' with value of type String
2018-12-21 03:25:14.351 INFO 4049 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2018-12-21 03:25:14.355 INFO 4049 --- [ restartedMain] c.xxxx.springboot.demo.DemoApplication : Started DemoApplication in 3.951 seconds (JVM running for 4.723)
2018-12-21 03:25:14.373 DEBUG 4049 --- [ restartedMain] o.s.boot.devtools.restart.Restarter : Creating new Restarter for thread Thread[main,5,main]
2018-12-21 03:25:14.373 DEBUG 4049 --- [ restartedMain] o.s.boot.devtools.restart.Restarter : Immediately restarting application
2018-12-21 03:25:14.373 DEBUG 4049 --- [ restartedMain] o.s.boot.devtools.restart.Restarter : Created RestartClassLoader org.springframework.boot.devtools.restart.classloader.RestartClassLoader#1b3b349
2018-12-21 03:25:14.373 DEBUG 4049 --- [ restartedMain] o.s.boot.devtools.restart.Restarter : Starting application com.xxxx.springboot.demo.DemoApplication with URLs [file:/Users/XXXXX/XXXXX/SpringMicroservices/SpringBoot/demo/target/classes/]
2018-12-21 03:25:14.763 DEBUG 4049 --- [on(2)-127.0.0.1] o.s.c.e.PropertySourcesPropertyResolver : Found key 'local.server.port' in PropertySource 'server.ports' with value of type Integer
2018-12-21 03:25:14.995 INFO 4049 --- [on(2)-127.0.0.1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2018-12-21 03:25:14.995 INFO 4049 --- [on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2018-12-21 03:25:14.995 DEBUG 4049 --- [on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Detected StandardServletMultipartResolver
2018-12-21 03:25:14.995 DEBUG 4049 --- [on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet : Detected AcceptHeaderLocaleResolver
2018-12-21 03:25:15.003 DEBUG 4049 --- [on(2)-127.0.0.1] o.s.web.servlet.DispatcherServlet : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
Please find below one of the controllers:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import javax.validation.Valid;
import java.net.URI;
import java.util.List;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
#RestController
public class UserResource {
#Autowired
private UserDaoService service;
#GetMapping("/users")
public List<User> retrieveAllUsers(){
return service.findAll();
}
#GetMapping("/users/{id}")
public Resource<User> retrieveUser(#PathVariable int id){
User user = service.findOne(id);
if(user==null)
throw new UserNotFoundEception("id-" + id);
Resource<User> resource = new Resource<User>(user);
ControllerLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllUsers());
resource.add(linkTo.withRel("all-users"));
return resource;
}
#PostMapping("/users")
public ResponseEntity<Object> CreateUser(#Valid #RequestBody User user){
User savedUser = service.save(user);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest().path("/{id}")
.buildAndExpand(savedUser.getId())
.toUri();
return ResponseEntity.created(location).build();
}
#DeleteMapping("/users/{id}")
public void deleteUser(#PathVariable int id){
User user = service.deleteById(id);
if(user==null)
throw new UserNotFoundEception("id-" + id);
}
}

package com.example.config
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.common.base.Predicate;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
#Configuration
#EnableSwagger2
public class SwaggerConfig {
public #Bean Docket restApi() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().paths(paths()).build();
}
/**
* Url for documentation.
*
* #return
*/
private Predicate<String> paths() {
return regex("/basepackage of your restcontroller/*");
}
/**
* Description your application
*
* #return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("App title").description("App description")
.termsOfServiceUrl("").license("").licenseUrl("").version("1.0").build();
}
}
Here is an example of controller :
#Api(value = "membre")
#RestController
public class ExampleControleur {
#RequestMapping(value = "/find", method = RequestMethod.GET, produces = {
MediaType.APPLICATION_JSON_VALUE }, consumes = { MediaType.APPLICATION_JSON_VALUE })
#ApiOperation(httpMethod = "GET", value = "example text", notes = "note text ")
#ResponseBody
#ApiResponses(value = { #ApiResponse(code = 200, message = "OK !"),
#ApiResponse(code = 422, message = "description."),
#ApiResponse(code = 401, message = "description"),
#ApiResponse(code = 403, message = "description"),
#ApiResponse(code = 404, message = "description"),
#ApiResponse(code = 412, message = "description."),
#ApiResponse(code = 500, message = "description.") })
#ApiImplicitParams({
#ApiImplicitParam(name = "params1", value = "description.", required = true, dataType = "string", paramType = "query", defaultValue = "op"),
#ApiImplicitParam(name = "params2", value = "description.", required = true, dataType = "string", paramType = "query")})
public ResponseEntity<MyObject> getObjet(#RequestParam(value = "params1", required = true) Params1 params1,
#RequestParam(value = "params2", required = true) String param2){
}
}
You can test with this class, but i suppose that you have written all the swagger annotations in your controllers !
You can add to your main class #ComponentScan(basePackages={"the package of your config swagger class"})
Th acces url must be http://localhost:port/context-path if you have it/swagger-ui.html

Related

#BeforeAdvise is not working for same method names in Spring BOOT

I am learning Spring AOP and tried to call the #BeforeAdvise for two Different methods in different classes. But the log added in the first call is getting printed only. Please check the code below and let me know what is not correct here.
MainClass
package com.example.aspects;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.example.aspects.AopClasses.DemoConfig;
#SpringBootApplication
public class AspectsApplication {
public static void main(String[] args) {
SpringApplication.run(AspectsApplication.class, args);
}
}
AspectClass
package com.example.aspects.Acpects;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class MyLoggingDemo {
private static final Logger logger = LogManager.getLogger(MyLoggingDemo.class);
#Before("execution(public String test())")
public void beforeCheck() {
logger.info("Before Check !!!!!!!!!!!! "+System.currentTimeMillis() );
}
}
ConfigClass
package com.example.aspects.AopClasses;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#Configuration
#EnableAspectJAutoProxy
#ComponentScan("com.example.aspects")
public class DemoConfig {
}
TestClass- Where the first call is made to the function
package com.example.aspects.AopClasses;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/")
public class tesTClass {
private static final Logger logger = LogManager.getLogger(tesTClass.class);
MemberShipDAO dao = new MemberShipDAO();
#RequestMapping(value = "/")
public String test() {
logger.info("Hey I am here");
return "HEy There I am Working !!";
}
#RequestMapping(value ="/test")
public String testing() {
logger.info("MemberShipDAO Call");
return dao.test(); // method with same name called here again
}
}
MemberShipDAO - Declared the test method again here
package com.example.aspects.AopClasses;
import org.springframework.stereotype.Component;
#Component
public class MemberShipDAO {
public String test() {
return "HEy!!";
}
}
Console
2022-01-18 11:37:52.794 INFO 8032 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-01-18 11:37:52.796 INFO 8032 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-01-18 11:37:52.802 INFO 8032 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 5 ms
2022-01-18 11:37:52.811 INFO 8032 --- [nio-8080-exec-1] c.example.aspects.Acpects.MyLoggingDemo : Before Check !!!!!!!!!!!! 1642486072811
2022-01-18 11:37:52.854 INFO 8032 --- [nio-8080-exec-1] c.example.aspects.AopClasses.tesTClass : Hey I am here
2022-01-18 11:37:57.383 INFO 8032 --- [nio-8080-exec-3] c.example.aspects.AopClasses.tesTClass : MemberShipDAO Call
Change this
MemberShipDAO dao = new MemberShipDAO();
to this
#Autowired MemberShipDAO dao;
Spring's magic for the AOP happens only when you use the instance created by Spring. Behind the scene, Spring instantiated your dao and wrapped it with a proxy that makes a call to your aspect.

Why is AOP Logging not working in my project

I'm wasting a lot of time right now with AOP logging setup.
I don't know why AOP isn't working in my project.
I think I've done all the settings I can.
Please let me know if you guys have a solutions.
Thank you.
application.java
#EnableAspectJAutoProxy
#SpringBootApplication
#ComponentScan(basePackages = "com.demo.apiservice")
#MapperScan("com.demo.apiservice.mapper")
public class ApiServiceApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(ApiServiceApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(ApiServiceApplication.class, args);
}
#Bean
public ModelMapper modelMapper() {
return new CustmizedModelMapper();
}
#Bean
public AopLoggingConfig loggingAspect(){
return new AopLoggingConfig();
}
}
build.gradle
configurations {
all {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
}
}
dependencies {
//implementation 'org.springframework.boot:spring-boot-starter-security:2.5.5'
implementation 'org.springframework.boot:spring-boot-starter-web:2.5.5'
implementation 'org.springframework.boot:spring-boot-starter-log4j2:2.5.5'
implementation 'org.springframework.boot:spring-boot-starter-mail:2.5.5'
implementation 'org.springframework.boot:spring-boot-starter-aop:2.5.5'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3'
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.1.3'
implementation 'org.springframework.boot:spring-boot-starter-data-rest:2.5.5'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.projectlombok:lombok:1.18.8'
implementation 'org.modelmapper:modelmapper:2.3.8'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.5.5'
implementation 'org.springframework.boot:spring-boot-starter-jdbc:2.5.5'
implementation 'com.h2database:h2:1.4.200'
implementation 'org.springframework.boot:spring-boot-configuration-processor:2.5.5'
implementation 'org.springframework.security:spring-security-core:5.4.2'
implementation 'com.fasterxml.jackson.core:jackson-core:2.12.3'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.12.3'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.3'
implementation 'org.apache.httpcomponents:httpclient:4.5.13'
implementation 'com.nimbusds:nimbus-jose-jwt:9.7'
implementation 'io.springfox:springfox-swagger2:3.0.0'
implementation 'io.springfox:springfox-swagger-ui:2.9.2'
implementation 'joda-time:joda-time:2.10.10'
implementation 'io.jsonwebtoken:jjwt-api:0.11.1'
implementation 'javax.inject:javax.inject:1'
implementation 'com.googlecode.json-simple:json-simple:1.1'
implementation 'de.mkammerer:argon2-jvm:2.7'
implementation 'org.bouncycastle:bcprov-jdk15on:1.68'
implementation 'org.apache.maven.plugins:maven-surefire-plugin:2.22.0'
implementation 'javax.validation:validation-api:2.0.1.Final'
implementation 'org.postgresql:postgresql:42.1.4'
implementation 'org.hibernate:hibernate-gradle-plugin:5.6.1.Final'
implementation 'com.jayway.jsonpath:json-path:2.6.0'
compileOnly 'org.projectlombok:lombok:1.18.8'
testCompileOnly 'org.projectlombok:lombok:1.18.8'
runtimeOnly 'org.springframework.boot:spring-boot-devtools:2.5.5'
annotationProcessor 'org.projectlombok:lombok:1.18.8'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.8'
testImplementation 'org.springframework.boot:spring-boot-starter-test:2.5.5'
testImplementation 'org.springframework.security:spring-security-test:5.5.2'
}
AopLoggingComponent.java
package com.demo.apiservice.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
#Component
#Aspect
public class AopLoggingConfig {
Logger logger = LoggerFactory.getLogger(AopLoggingConfig.class);
static String name = "";
static String type = "";
/**
* AspectJ is applied only to a specific class/method in package.
*/
#Around("execution(* com.demo.apiservice.customer.*Controller.*(..))")
public Object logPrint(ProceedingJoinPoint joinPoint) throws Throwable {
type = joinPoint.getSignature().getDeclaringTypeName();
if (type.indexOf("Controller") -1) {
name = "Controller \t: ";
}
else if (type.indexOf("Service") -1) {
name = "ServiceImpl \t: ";
}
else if (type.indexOf("DAO") -1) {
name = "DAO \t\t: ";
}
logger.debug(name + type + ".######################### " + joinPoint.getSignature().getName() + "()");
return joinPoint.proceed();
}
}
controller.java
package com.demo.apiservice.customer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.demo.apiservice.constant.Constants;
import com.demo.apiservice.customer.service.CustomerService;
import com.demo.apiservice.customer.service.impl.CustomerServiceImpl;
import com.demo.apiservice.request.CustomerRequest;
import com.demo.apiservice.request.LoginRequest;
import com.demo.apiservice.response.GetCustomerResponse;
import com.demo.apiservice.response.SuccessResponse;
import com.demo.apiservice.utils.ResponseHandler;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import javax.validation.Valid;
#Slf4j
#RestController
#Api(tags = "Customers APIs")
#RequestMapping("/apiservice/v1/customers")
public class CustomerController {
#Autowired
private CustomerService customerService;
#GetMapping
#ApiOperation(value = "List of Customers API")
#ApiResponses(value = {
#ApiResponse(code = 400, message = Constants.BAD_REQUEST),
#ApiResponse(code = 403, message = Constants.ACCESS_DENIED)})
public ResponseEntity<Object retrieveAll() {
log.info("Start of CustomerController::retrieveAll method");
return customerService.retrieveAll();
}
}
application.yml
logging:
level:
org:
springframework.web: DEBUG
hibernat: DEBUG
com:
atoz_develop:
mybatissample:
repository: TRACE
mybatis:
mapper-locations: classpath:/mappers/*.xml
type-aliases-package: com.demo.apiservice.entity
configuration:
map-underscore-to-camel-case: 'true'
debug: 'true'
spring:
datasource:
driver-class-name: org.postgresql.Driver
username: postgres
url: jdbc:postgresql://localhost:5432/postgres
platform: postgres
password: postgres
jpa:
generate-ddl: 'false'
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQL10Dialect
format_sql: 'true'
hibernate:
ddl-auto: update
show-sql: 'true'
stack trace log
2021-11-17 16:05:19.992 DEBUG 23300 --- [nio-8080-exec-8] o.s.w.s.DispatcherServlet : GET /apiservice/v1/customers, parameters={} 2021-11-17 16:05:19.992 DEBUG 23300 --- [nio-8080-exec-8] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.demo.apiservice.customer.CustomerController#retrieveAll()
2021-11-17 16:05:19.993 INFO 23300 --- [nio-8080-exec-8] c.l.a.c.CustomerController : Start of CustomerController::retrieveAll method
2021-11-17 16:05:19.993 INFO 23300 --- [nio-8080-exec-8] c.l.a.c.s.i.CustomerServiceImpl : Inside of the CustomerServiceImpl :: retrieveAll method
2021-11-17 16:05:19.996 INFO 23300 --- [nio-8080-exec-8] c.l.a.c.s.i.CustomerServiceImpl : End of the CustomerServiceImpl :: retrieveAll method
2021-11-17 16:05:19.996 DEBUG 23300 --- [nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using application/xhtml+xml, given [text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] and supported [application/json, application/*+json, application/json, application/*+json, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8]
2021-11-17 16:05:19.996 DEBUG 23300 --- [nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{data=[Customer(email=test1#test.com, password=null, customerId=null, storeName=eos, firstname=test1 (truncated)...]
2021-11-17 16:05:19.998 DEBUG 23300 --- [nio-8080-exec-8] o.s.w.s.DispatcherServlet : Completed 200 OK
Your aspect is triggered. I added an explicit controller method call in order to check:
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(ApiServiceApplication.class, args);
context.getBean(CustomerController.class).retrieveAll();
}
Then I fixed some typos in your code like the one mentioned in my previous comment.
Probably your problem was that you simply did not see the log output because you forgot the log configuration for your application package com.demo.apiservice:
logging:
level:
org:
springframework.web: DEBUG
hibernate: DEBUG
com:
atoz_develop:
mybatissample:
repository: TRACE
demo.apiservice: DEBUG
BTW, I also corrected your typo hibernat to hibernate, but that is unrelated to the problem at hand.
Then I see this in the log:
[ restartedMain] o.s.b.w.e.t.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
[ restartedMain] c.d.a.ApiServiceApplication : Started ApiServiceApplication in 5.101 seconds (JVM running for 6.117)
[ restartedMain] c.d.a.c.AopLoggingConfig : Controller : com.demo.apiservice.customer.CustomerController.######################### retrieveAll()
[ restartedMain] c.d.a.c.AopLoggingConfig : Controller : com.demo.apiservice.customer.CustomerController.######################### retrieveAll()
[ restartedMain] c.d.a.c.CustomerController : Start of CustomerController::retrieveAll method
Do you see the problem? You get duplicate logging, because the aspect is picked up once by component scan and instantiated one more time as a bean in your application configuration. So you need to remove this part from ApiServiceApplication:
#Bean
public AopLoggingConfig loggingAspect() {
return new AopLoggingConfig();
}
Now the duplicate logging is gone.
Next, maybe you want to simplify your aspect a bit and simply log joinPoint or joinPoint.getSignature(). You also want to make name and type local variables, because the static fields are not thread-safe. Instead, probably you want a static logger in the aspect.
#Component
#Aspect
public class AopLoggingConfig {
private static Logger logger = LoggerFactory.getLogger(AopLoggingConfig.class);
#Around("execution(* com.demo.apiservice.customer.*Controller.*(..))")
public Object logPrint(ProceedingJoinPoint joinPoint) throws Throwable {
String type = joinPoint.getSignature().getDeclaringTypeName();
String name = "";
if (type.contains("Controller")) {
name = "Controller \t: ";
}
else if (type.contains("Service")) {
name = "ServiceImpl \t: ";
}
else if (type.contains("DAO")) {
name = "DAO \t\t: ";
}
logger.debug(name + joinPoint.getSignature());
return joinPoint.proceed();
}
}
The log line becomes:
Controller : ResponseEntity com.demo.apiservice.customer.CustomerController.retrieveAll()
But actually, both the package name and the class name indicate that we are dealing with a controller, DAO or service. So why bother with the if-else stuff in the first place? Why make a simple matter complicated and the aspect slower? Besides, if you only want to log something and not influence the control flow, the method parameters or the return value, a simple #Before advice would do, the expensive #Around is unnecessary.
#Component
#Aspect
public class AopLoggingConfig {
private static Logger logger = LoggerFactory.getLogger(AopLoggingConfig.class);
#Before("execution(* com.demo.apiservice.customer.*Controller.*(..))")
public void logPrint(JoinPoint joinPoint) {
logger.debug(joinPoint.getSignature().toString());
}
}
The log line becomes:
ResponseEntity com.demo.apiservice.customer.CustomerController.retrieveAll()
Isn't that enough?
Or even simpler:
logger.debug(joinPoint.toString());
Log:
execution(ResponseEntity com.demo.apiservice.customer.CustomerController.retrieveAll())
Keep it simple!
The following should work:
package com.demo.apiservice.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
#Component
#Aspect
public class AopLoggingConfig {
Logger logger = LoggerFactory.getLogger(AopLoggingConfig.class);
static String name = "";
static String type = "";
#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
public void controllerClassMethods() {}
#Around("controllerClassMethods()")
public Object logPrint(ProceedingJoinPoint joinPoint) throws Throwable {
type = joinPoint.getSignature().getDeclaringTypeName();
if (type.indexOf("Controller") -1) {
name = "Controller \t: ";
}
else if (type.indexOf("Service") -1) {
name = "ServiceImpl \t: ";
}
else if (type.indexOf("DAO") -1) {
name = "DAO \t\t: ";
}
logger.debug(name + type + ".######################### " + joinPoint.getSignature().getName() + "()");
return joinPoint.proceed();
}
}
This will match all the methods in all classes annotated with #RestController.

Spring-WS SOAP server with existing WSDL not finding endpoint

I am trying to develop a quick SOAP server to use during testing using Spring-Boot and Spring-WS. It is contract first as I have existing WSDL and XSD files and have my java classes generated at build time from them. However, when I send a request through using either curl or SOAPUI, I get the following error:
$ curl --header "content-type: text/xml" -d #request.xml http://localhost:8080/ws
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring xml:lang="en">No adapter for endpoint [public org.csapi.schema.parlayx.bpxg.common.v3_1.ChargingInformation com.meanwhileinhell.mock.soap.MockVolumeChargingEndpoint.getAmount(java.lang.String,long,java.util.List<org.csapi.schema.parlayx.payment.v3_0.Property>) throws org.csapi.wsdl.parlayx.common.v3_0.faults.ServiceException,org.csapi.wsdl.parlayx.common.v3_0.faults.PolicyException]: Is your endpoint annotated with #Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
(Highlights: No adapter for endpoint [...] Is your endpoint annotated with #Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?
The answer to this question is yes, my endpoint is annotated with #Endpoint. Full setup below:
build.gradle
plugins {
id 'org.springframework.boot'
}
configurations {
all {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-log4j2'
}
}
version = '0.0.1-SNAPSHOT'
dependencies {
compile("org.springframework.boot:spring-boot-starter-web-services")
compile("wsdl4j:wsdl4j:1.6.1")
compile("org.glassfish.jaxb:jaxb-xjc:2.2.11")
compile('javax.xml.soap:javax.xml.soap-api:1.4.0')
compile('com.sun.xml.messaging.saaj:saaj-impl:1.5.1')
compile project(':meanwhile-in-hell-volume-charging-v3-1-wsdl')
testCompile("org.springframework.boot:spring-boot-starter-test")
}
MockSoapConfig.java
package com.meanwhileinhell.mock.soap.config;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
#EnableWs
#Configuration
public class MockSoapConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean messageDispatcherServlet(final ApplicationContext applicationContext) {
MessageDispatcherServlet messageDispatcherServlet = new MessageDispatcherServlet();
messageDispatcherServlet.setApplicationContext(applicationContext);
messageDispatcherServlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(messageDispatcherServlet, "/ws/*");
}
}
MockVolumeChargingEndpoint.java
package com.meanwhileinhell.mock.soap;
import java.util.List;
import org.csapi.schema.parlayx.bpxg.common.v3_1.ChargingInformation;
import org.csapi.schema.parlayx.bpxg.common.v3_1.ObjectFactory;
import org.csapi.schema.parlayx.payment.v3_0.Property;
import org.csapi.wsdl.parlayx.common.v3_0.faults.PolicyException;
import org.csapi.wsdl.parlayx.common.v3_0.faults.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
import generated.org.csapi.wsdl.parlayx.bpxg.payment.volume_charging.v3_1._interface.VolumeCharging;
#Endpoint
public class MockVolumeChargingEndpoint implements VolumeCharging {
private static final Logger logger = LoggerFactory.getLogger(MockVolumeChargingEndpoint.class);
private static final String NAMESPACE = "http://www.csapi.org/wsdl/parlayx/bpxg/payment/volume_charging/v3_1/service";
#Autowired
public MockVolumeChargingEndpoint() {
}
#Override
#PayloadRoot(namespace = NAMESPACE, localPart = "getAmount")
#ResponsePayload
public ChargingInformation getAmount(final String endUserIdentifier,
final long volume,
final List<Property> parameters) throws
ServiceException,
PolicyException {
logger.debug("GetAmount for endUserIdentifier=[{}], volume=[{}], properties=[{}]",
endUserIdentifier,
volume,
parameters);
ObjectFactory objectFactory = new ObjectFactory();
return objectFactory.createChargingInformation();
}
}
This is the generated interface that I am implementing:
VolumeCharging.java
package generated.org.csapi.wsdl.parlayx.bpxg.payment.volume_charging.v3_1._interface;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;
/**
* This class was generated by Apache CXF 3.3.2
* 2020-01-27T09:24:18.615Z
* Generated source version: 3.3.2
*
*/
#WebService(targetNamespace = "http://www.csapi.org/wsdl/parlayx/bpxg/payment/volume_charging/v3_1/interface", name = "VolumeCharging")
#XmlSeeAlso({org.csapi.schema.parlayx.payment.volume_charging.v3_1.local.ObjectFactory.class, org.csapi.schema.parlayx.bpxg.payment.volume_charging.v3_1.local.ObjectFactory.class, org.csapi.schema.parlayx.bpxg.common.v3_1.ObjectFactory.class, org.csapi.schema.parlayx.common.v3_1.ObjectFactory.class, org.csapi.schema.parlayx.payment.v3_0.ObjectFactory.class})
public interface VolumeCharging {
#WebMethod
#RequestWrapper(localName = "getAmount", targetNamespace = "http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local", className = "org.csapi.schema.parlayx.bpxg.payment.volume_charging.v3_1.local.GetAmount")
#ResponseWrapper(localName = "getAmountResponse", targetNamespace = "http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local", className = "org.csapi.schema.parlayx.bpxg.payment.volume_charging.v3_1.local.GetAmountResponse")
#WebResult(name = "result", targetNamespace = "http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local")
public org.csapi.schema.parlayx.bpxg.common.v3_1.ChargingInformation getAmount(
#WebParam(name = "endUserIdentifier", targetNamespace = "http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local")
java.lang.String endUserIdentifier,
#WebParam(name = "volume", targetNamespace = "http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local")
long volume,
#WebParam(name = "parameters", targetNamespace = "http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local")
java.util.List<org.csapi.schema.parlayx.payment.v3_0.Property> parameters
) throws org.csapi.wsdl.parlayx.common.v3_0.faults.ServiceException, org.csapi.wsdl.parlayx.common.v3_0.faults.PolicyException;
}
This is the SOAP packet that I am sending in my curl/SOAPUI request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:mock="http://www.csapi.org/wsdl/parlayx/bpxg/payment/volume_charging/v3_1/service">
<soapenv:Header/>
<soapenv:Body>
<mock:getAmount>
<mock:endUserIdentifier>TestUserId</mock:endUserIdentifier>
<mock:volume>123123</mock:volume>
<mock:parameters>
<name>ParameterName</name>
<value>ParameterValue</value>
</mock:parameters>
</mock:getAmount>
</soapenv:Body>
</soapenv:Envelope>
I just see why it's not hitting my endpoint. If I change the name getAmount in my request packet to something that is definitely not implemented, I get the message:
No endpoint mapping found for [SaajSoapMessage {http://www.csapi.org/wsdl/parlayx/bpxg/payment/volume_charging/v3_1/service}getTheWeather]
Turning on DEBUG logging for Spring WebServices, I see that my endpoints are being mapped to my endpoint method ok:
2020-01-28 16:36:35.881 DEBUG 72581 --- [ main] yloadRootAnnotationMethodEndpointMapping : Mapped [{http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local}getAmount] onto endpoint [public org.csapi.schema.parlayx.bpxg.common.v3_1.ChargingInformation com.meanwhileinhell.mock.soap.MockVolumeChargingEndpoint.getAmount(java.lang.String,long,java.util.List<org.csapi.schema.parlayx.payment.v3_0.Property>)]
2020-01-28 16:36:58.316 DEBUG 72581 --- [nio-8080-exec-1] .WebServiceMessageReceiverHandlerAdapter : Accepting incoming [org.springframework.ws.transport.http.HttpServletConnection#27ef75ac] at [http://localhost:8080/ws]
2020-01-28 16:36:58.481 DEBUG 72581 --- [nio-8080-exec-1] o.s.ws.server.MessageTracing.received : Received request [SaajSoapMessage {http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local}getAmount]
2020-01-28 16:36:58.541 DEBUG 72581 --- [nio-8080-exec-1] yloadRootAnnotationMethodEndpointMapping : Looking up endpoint for [{http://www.csapi.org/schema/parlayx/bpxg/payment/volume_charging/v3_1/local}getAmount]
2020-01-28 16:36:58.541 DEBUG 72581 --- [nio-8080-exec-1] o.s.w.soap.server.SoapMessageDispatcher : Endpoint mapping [org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping#623a8092] maps request to endpoint [public org.csapi.schema.parlayx.bpxg.common.v3_1.ChargingInformation com.meanwhileinhell.mock.soap.MockVolumeChargingEndpoint.getAmount(java.lang.String,long,java.util.List<org.csapi.schema.parlayx.payment.v3_0.Property>)]
2020-01-28 16:36:58.544 DEBUG 72581 --- [nio-8080-exec-1] o.s.w.soap.server.SoapMessageDispatcher : Testing endpoint adapter [org.springframework.ws.server.endpoint.adapter.DefaultMethodEndpointAdapter#3571b748]
2020-01-28 16:36:58.547 DEBUG 72581 --- [nio-8080-exec-1] s.e.SoapFaultAnnotationExceptionResolver : Resolving exception from endpoint [public org.csapi.schema.parlayx.bpxg.common.v3_1.ChargingInformation com.meanwhileinhell.mock.soap.MockVolumeChargingEndpoint.getAmount(java.lang.String,long,java.util.List<org.csapi.schema.parlayx.payment.v3_0.Property>)]: java.lang.IllegalStateException: No adapter for endpoint [public org.csapi.schema.parlayx.bpxg.common.v3_1.ChargingInformation com.meanwhileinhell.mock.soap.MockVolumeChargingEndpoint.getAmount(java.lang.String,long,java.util.List<org.csapi.schema.parlayx.payment.v3_0.Property>)]: Is your endpoint annotated with #Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?
So it would seem that my endpoint is getting mapped ok, but there is some issue with the endpoint adapter?
Needed to wrap my payloads in JAXBElement type for Spring to get the message handler adapters to support the endpoint.
#PayloadRoot(namespace = NAMESPACE, localPart = "getAmount")
#ResponsePayload
public JAXBElement<GetAmountResponse> getAmount(#RequestPayload JAXBElement<GetAmount> request) {
...
}

Spring - How to build a junit test for a soap service

I'm following the spring guide to create a hello world soap ws. The link below :
https://spring.io/guides/gs/producing-web-service/
I successfully make it work. When i run this command line :
curl --header "content-type: text/xml" -d
#src/test/resources/request.xml http://localhost:8080/ws/coutries.wsdl
I get this response.
<SOAP-ENV:Header/><SOAP-ENV:Body><ns2:getCountryResponse xmlns:ns2="http://spring.io/guides/gs-producing-web-service"><ns2:country><ns2:name>Spain</ns2:name><ns2:population>46704314</ns2:population><ns2:capital>Madrid</ns2:capital><ns2:currency>EUR</ns2:currency></ns2:country></ns2:getCountryResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
Now i'm trying to create a junit test for this service (the controller layer) but it doesn't work.
Here is my unit test :
#RunWith(SpringRunner.class)
#WebMvcTest(CountryEndpoint.class)
#ContextConfiguration(classes = {CountryRepository.class, WebServiceConfig.class})
public class CountryEndpointTest {
private final String URI = "http://localhost:8080/ws/countries.wsdl";
#Autowired
private MockMvc mockMvc;
#Test
public void test() throws Exception {
mockMvc.perform(
get(URI)
.accept(MediaType.TEXT_XML)
.contentType(MediaType.TEXT_XML)
.content(request)
)
.andDo(print())
.andExpect(status().isOk());
}
static String request = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"\n" +
" xmlns:gs=\"http://spring.io/guides/gs-producing-web-service\">\n" +
" <soapenv:Header/>\n" +
" <soapenv:Body>\n" +
" <gs:getCountryRequest>\n" +
" <gs:name>Spain</gs:name>\n" +
" </gs:getCountryRequest>\n" +
" </soapenv:Body>\n" +
"</soapenv:Envelope>";
}
here's the error :
MockHttpServletResponse:
Status = 404
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status
Expected :200
Actual :404
I changed the log level to debug and i found this :
2020-01-27 18:04:11.880 INFO 32723 --- [ main] c.s.t.e.s.endpoint.CountryEndpointTest : Started CountryEndpointTest in 1.295 seconds (JVM running for 1.686)
2020-01-27 18:04:11.925 DEBUG 32723 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /ws/countries.wsdl
2020-01-27 18:04:11.929 DEBUG 32723 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Did not find handler method for [/ws/countries.wsdl]
2020-01-27 18:04:11.930 DEBUG 32723 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Matching patterns for request [/ws/countries.wsdl] are [/**]
2020-01-27 18:04:11.930 DEBUG 32723 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : URI Template variables for request [/ws/countries.wsdl] are {}
2020-01-27 18:04:11.931 DEBUG 32723 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapping [/ws/countries.wsdl] 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#c7a977f]]] and 1 interceptor
I tried another solution (below) but it doesn't work either.
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {WebServiceConfig.class, CountryRepository.class})
public class CountryEndpointTest {
private final String URI = "http://localhost:8080/ws/countries.wsdl";
private MockMvc mockMvc;
#Autowired
CountryRepository countryRepository;
#Before
public void setup() {
this.mockMvc = standaloneSetup(new CountryEndpoint(countryRepository)).build();
}
Spring doc says :
https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/html/boot-features-testing.html
By default, #SpringBootTest will not start a server.
You need to define
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
to RUN the server.
I tried with mockserver, but I can't access to the endpoint (even with WebEnvironment.DEFINED_PORT)
So I did it as follow :
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWebTestClient
public class FacturationEndpointTest {
#Autowired
private WebTestClient webClient;
#Test
public void testWSDL() throws Exception {
this.webClient.get().uri("/test_service/services.wsdl")
.exchange().expectStatus().isOk();
}
You need to add the following dependency in your pom.xml if you want to use WebTestClient like me :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency>
Please change the GET method to POST.
mockMvc.perform(
postURI) // <-- This line!!!
.accept(MediaType.TEXT_XML)
.contentType(MediaType.TEXT_XML)
.content(request)
if you are using a spring ws framework to implements your endpoints, please see spring-ws-test. you will find a MockWebServiceClient that mocks a client and tests your endpoint. I propose you to see this example : https://memorynotfound.com/spring-ws-server-side-integration-testing/
this works only for spring web service and not for CXF web services.

Kotlin testing authentication to /actuator api

I have a ActuatorSecurity class which I use for authentication for /actuator actions.
package com.netapp.qronicle.config
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.crypto.factory.PasswordEncoderFactories
#Configuration
#EnableWebSecurity
class ActuatorSecurity : WebSecurityConfigurerAdapter() {
#Value("\${security.user.actuator-username}")
private val actuatorUsername: String? = null
#Value("\${security.user.actuator-password}")
private val actuatorPassword: String? = null
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.csrf().disable().requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.httpBasic()
}
#Throws(Exception::class)
override fun configure(auth: AuthenticationManagerBuilder) {
val passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
val encodedPassword = passwordEncoder.encode(actuatorPassword)
auth.inMemoryAuthentication()
.withUser(actuatorUsername).password(encodedPassword).roles("USER")
}
#Bean
#Throws(Exception::class)
override fun authenticationManagerBean(): AuthenticationManager {
// ALTHOUGH THIS SEEMS LIKE USELESS CODE,
// IT'S REQUIRED TO PREVENT SPRING BOOT AUTO-CONFIGURATION
return super.authenticationManagerBean()
}
}
I have everything configured in my application.properties file
# spring boot actuator access control
management.endpoints.web.exposure.include=*
security.user.actuator-username=admin
security.user.actuator-password=admin123
I would like to just to do basic authentication api tests for /actuator/** but have not been able to do so, here is my test class
package com.netapp.qronicle.web
import com.netapp.qronicle.config.ActuatorSecurity
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import javax.inject.Inject
#ExtendWith(SpringExtension::class)
#WebMvcTest(ActuatorSecurity::class)
#ContextConfiguration(classes = [ActuatorSecurity::class])
class ActuatorTest {
#Inject
lateinit var mockMvc: MockMvc
#Test
fun `Basic authentication actuator test`() {
val result = mockMvc.perform(
MockMvcRequestBuilders.get("/actuator"))
.andExpect(MockMvcResultMatchers.status().isOk)
Assertions.assertNotNull(result)
}
}
The error thrown is:
2019-02-26 17:07:26.062 INFO 34766 --- [ main] com.netapp.qronicle.web.ActuatorTest : Starting ActuatorTest on jmasson-mac-0 with PID 34766 (started by jonma in /Users/jonma/Development/java/report-generator)
2019-02-26 17:07:26.099 INFO 34766 --- [ main] com.netapp.qronicle.web.ActuatorTest : No active profile set, falling back to default profiles: default
2019-02-26 17:07:29.324 INFO 34766 --- [ main] com.netapp.qronicle.web.ActuatorTest : Started ActuatorTest in 4.468 seconds (JVM running for 6.427)
MockHttpServletRequest:
HTTP Method = GET
Request URI = /actuator
Parameters = {}
Headers = {}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = org.springframework.web.servlet.resource.ResourceHttpRequestHandler
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []

Resources