Spring Boot - SOAP endpoint conflict with HandlerMapping / Dispatcher - spring

I have a spring boot project with thymeleaft rest + soap.
I have a page that does:
Show the front end
+ Rest Requests
+ Soap requests.
The problem originates from "Soap" when I have to create the endpoint.
When I add this endpoint to the configuration:
#Bean
public ServletRegistrationBean<CXFServlet> dispatcherServlet() {
return new ServletRegistrationBean<CXFServlet>(new CXFServlet(), "/soap-api/*");
}
#Bean
#Primary
public DispatcherServletPath dispatcherServletPathProvider() {
return () -> "";
}
I am using a soap with apache cfx. The cfx configuration I use for my project is here
I have found a slightly unexpected error.
If I run this config inside my config I get all these problems:
config inside my config
I get an error of the type:
Error creating bean with name 'resourceHandlerMapping' defined in
class path resource
[org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]:
Bean instantiation via factory method failed; nested exception is
org.springframework.beans.BeanInstantiationException: Failed to
instantiate [org.springframework.web.servlet.HandlerMapping]: Factory
method 'resourceHandlerMapping' threw exception; nested exception is
java.lang.IllegalStateException: No ServletContext set
I tried with #EnableWebMvc but I was getting a problem from:
Error creating bean with name 'resourceHandlerMapping' defined in
class path resource
[org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class]
At this point I have tried: add the path for the thymeleaft
ClassLoaderTemplateResolver secondaryTemplateResolver = new ClassLoaderTemplateResolver();
But it does not work. I have also tried with basic mapping:
UrlBasedViewResolver resolve = new UrlBasedViewResolver();
resolve.setPrefix("templates/");
resolve.setSuffix(".html");
But it doesn't work either.
config outside my config
The strange thing is that if I set that configuration aside in another
java file, no problem arises. But when I access the page I get an
error:
ERROR 10788 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[...] :
Custom error page [/error] could not be dispatched correctly
So I thought mapping the traditional way like spring, in another java configuration does:
#Bean
public UrlBasedViewResolver viewResolver() {
UrlBasedViewResolver resolver = new UrlBasedViewResolver();
//resolver.setPrefix("/WEB-INF/view/");
//resolver.setSuffix(".jsp");
// THYMELEAFT
resolver.setPrefix("templates/");
resolver.setSuffix(".html");
resolver.setViewClass(JstlView.class);
return resolver;
}
// RESOURCES
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/", "/resources/")
.setCachePeriod(3600)
.resourceChain(true)
.addResolver(new PathResourceResolver());
}
#Bean
public ResourceBundleThemeSource themeSource() {
ResourceBundleThemeSource themeSource
= new ResourceBundleThemeSource();
themeSource.setDefaultEncoding("UTF-8");
themeSource.setBasenamePrefix("themes.");
return themeSource;
}
It doesn't work either. I am currently at this point. Is there any solution for problem 2?
At this point I can successfully load the SOAP, but the other pages in
the browser show up as 404. and in the console I get: Custom error
page [/error] could not be dispatched correctly
From what I understand it makes a mess with the mapped routes
and I understand is that when I register a servlet "the previous or current" is lost and it is not mapped with the ServletRegistrationBean..
Is there any way to fix this? Do I have to map all the routes by hand with the ClassLoaderTemplateResolver or the UrlBasedViewResolver? I've tried it, but it throws the same error.
In case of changing while advancing "a different error comes out every time". That's why I'm looking for a way to directly solve the problem.
Note: I am using the apache.cxf plugin

I have found a way to make it work by declaring it in another way.
I think every day I'm understanding less how Spring/Spring Boot works. Is it time to return to JEE?
1 - It seems that if you leave the dispatcher empty (obviously it will fail with an empty arrow function) but Spring requires you to create a dispatcher when you declare ServletRegistrationBean. Thinking that: "that dispatcher "could" get the route automatically with an empty arrow function". But it doesn't.
// #Bean
// public ServletRegistrationBean<CXFServlet> dispatcherServlet() {
// return new ServletRegistrationBean<CXFServlet>(new CXFServlet(), "/soap-api/*");
// }
// #Bean
// #Primary
// public DispatcherServletPath dispatcherServletPathProvider() {
// return () -> "";
// }
2 - If I declare it a little differently, it is able to correctly detect the mappings
#Bean
public ServletRegistrationBean servletRegistrationBean() {
CXFServlet cxfServlet = new CXFServlet();
ServletRegistrationBean servletRegistration =
//new ServletRegistrationBean(cxfServlet, ServiceConstants.URL_PATH_SERVICES + "/*");
new ServletRegistrationBean(cxfServlet, "/soap-api/*");
return servletRegistration;
}
Problem source: here

Related

Spring MVC installation

I would like to create instalation process for the first run of my Spring MVC app. Something like wordpress has (when you first run it, you need to specify DB connection and your first admin account... etc.
I have tried it with spring but the spring won't start because when DataSource Bean is not connected, it will simply fail to start. It always fail when transaction manager bean is beeing created:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'txManager' defined in SpringWebConfig: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Property 'sessionFactory' is required|#]
Is there any way how to start the spring/hibernate app without transaction manager and then load it on-the-fly when user configures his db access details ? I know there is way to do it with application.properties but I want to create a simple install process just like in wordpress so it is most convenient for non-tech users.
EDIT 1: My current code:
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = {"test"}) //package scan
public class SpringWebConfig implements WebMvcConfigurer {
#Bean(name = "dataSource")
public BasicDataSource dataSource() {
if (Config.getInstance().isInstalled()) {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://" + Config.getInstance().getDburl() + "/" + Config.getInstance().getDbname());
ds.setUsername(Config.getInstance().getDbuser());
ds.setPassword(Config.getInstance().getDbpass());
this.ds = ds;
}
return ds;
}
#Bean
public HibernateTransactionManager txManager() {
if (sessionFactory() == null) {
return new HibernateTransactionManager();
}
else return new HibernateTransactionManager(sessionFactory());
}
#Bean
public SessionFactory sessionFactory() {
try {
LocalSessionFactoryBuilder builder = new LocalSessionFactoryBuilder(dataSource());
builder.scanPackages(dbEntity).addProperties(getHibernateProperties());
return builder.buildSessionFactory();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Adding #Lazy to all 3 #Beans solves the issue and hibernate is not requiered at the startup of Spring MVC (configured by class that implements WebMvcConfigurer), this allows to display install page for the user so he can define DB access details.

Understanding the instance variable names and the methods to create it using Spring #Bean annotation

I have written a simple Spring Boot Application, which I would later extend to build a Spring REST client. I have a working code; I have tried to change a few instance variable names and method names and playing around.
Code:
#SpringBootApplication
public class RestClientApplication {
public static void main(String[] args) {
SpringApplication.run(RestClientApplication.class, args);
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
RestClientApplication.class)) {
System.out.println(" Getting RestTemplateBuilder : " + ctx.getBean("restTemplateBuilder"));
System.out.println(" Getting RestTemplate : " + ctx.getBean("restTemplate"));
}
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.build();
}
#Bean
public CommandLineRunner runner() {
return args -> { SOP("hello"); }
}
}
Observations:
The instance variable names follow the camel-case notation, as
expected. So, restTemplate and restTemplateBuilder works.
While creating a RestTemplate instance through restTemplate() method, I tried changing the name of the argument to builder. It works.
While creating a RestTemplate instance through restTemplate() method, I tried changing the name of the method to a random one and I get an exception that "No bean named 'restTemplate' available".
CommandLineRunner interface is implemented through a lambda expression. Accessing commandLineRunner throws an exception.
Question
Why do I see the results mentioned in point #2 and #3?
While creating a RestTemplate instance through restTemplate() method,
I tried changing the name of the argument to builder. It works.
This works because, By default spring autowire's by type. So it searches for a bean with type RestTemplateBuilder and it finds it and hence no error.
While creating a RestTemplate instance through restTemplate() method,
I tried changing the name of the method to a random one and I get an
exception that "No bean named 'restTemplate' available".
You are getting an exception not because you changed the method name, but because of this
ctx.getBean("restTemplate")
Because by default #Bean uses method name as name of the bean. (check this). So the name of the bean of type RestTemplate which is returned by your random method is name of your random method. Hence when you try to get a bean with name restTemplate, it throws exception.
But if you were to Autowire a bean of type RestTemplate, it would still work, because Spring will Autowire by type by default and it knows a bean of type RestTemplate(name as random method name).

Spring boot how to custom HttpMessageConverter

Back-end, Spring boot project(v1.3.0.RELEASE), supply Rest JSON Api to fron-end, just now encountered an error:
Infinite recursion (StackOverflowError)
I decide to change to a custom FastJsonHttpMessageConverter, and code is below
#Bean
public HttpMessageConverter httpMessageConverter() {
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
return fastJsonHttpMessageConverter;
}
but it does not work, in real it uses a default HttpMessageConverter. Although does not have above error, the output is not as I expected. e.g.
suppliers: [
{
$ref: "$.value"
}
]
Now change above code
#Bean
public HttpMessageConverter mappingJackson2HttpMessageConverter() {
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
return fastJsonHttpMessageConverter;
}
This time it works, I want to know why the method name have to be mappingJackson2HttpMessageConverter? If use another method name how to configure it?
After seeing this offical document, I know how to customize converters.
#Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = new FastJsonHttpMessageConverter();
return new HttpMessageConverters(additional);
}
A Revise to my main post, actually below code does not work.
#Bean
public HttpMessageConverter mappingJackson2HttpMessageConverter() {
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
return fastJsonHttpMessageConverter;
}
Spring boot never enter this method if you set breakpoint inside it.
And below code also works.
#SpringBootApplication
public class FooApplication extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(FooApplication.class, args);
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(new FastJsonHttpMessageConverter());
}
}
Spring boot says (https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html#howto-customize-the-responsebody-rendering):
If a bean you add is of a type that would have been included by default anyway (like MappingJackson2HttpMessageConverter for JSON conversions) then it will replace the default value.
The bean you are adding is not of the same type, so the above does not happen. Your converter goes somewhere in the list of converters (probably the end), and the first suitable converter (the old one) does the job.
Beans produced by the Java configuration have the name of the method, so when you create a second bean named mappingJackson2HttpMessageConverter, it overrides the one created by spring boot's JacksonHttpMessageConvertersConfiguration and takes it place.
Instead of adding a converter bean, you might prefer to override the whole list of converters:
As in normal MVC usage, any WebMvcConfigurerAdapter beans that you provide can also contribute converters by overriding the configureMessageConverters method,

Setting up Custom HandlerExceptionResolver in Spring MVC

I am trying to override WebMvcConfigurerAdapter.configureHandlerExceptionResolvers() and provide my own ExceptionHandlerExceptionResolver to Spring MVC. The motive behind this is to provide Custom Content Negotiation in such a way that if the user requests for any garbage in "Accept" header, I can return him a JSON response with "media not supported exception". I was partially able to acheive the configuration using the bellow setup.
#Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager(new ErrorContentNegotiation());
ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = exceptionHandlerExceptionResolver();
exceptionHandlerExceptionResolver.setContentNegotiationManager(contentNegotiationManager);
exceptionHandlerExceptionResolver.afterPropertiesSet();
exceptionResolvers.add(exceptionHandlerExceptionResolver);
}
#Bean
public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(getHttpMessageConverter());
ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = new ExceptionHandlerExceptionResolver();
exceptionHandlerExceptionResolver.setMessageConverters(messageConverters);
return exceptionHandlerExceptionResolver;
}
public class ErrorContentNegotiationStrategy implements ContentNegotiationStrategy {
#Override
public List<MediaType> resolveMediaTypes(final NativeWebRequest webRequest) {
return Lists.newArrayList(Globals.JSON);
}
}
I am getting this exception when the spring starts up.
No qualifying bean of type [org.springframework.web.accept.ContentNegotiationStrategy] is defined: expected single matching bean but found 2: errorContentNegotiationStrategy,mvcContentNegotiationManager
Doesn't work when I add a #Qualifier annotation to my ErrorContentNegotiationStrategy class and give it a unique name. Throws the same Exception.
If I remove #Compoenent annotation and leave the code as is, then ErrorContentNegotiationStrategy() method in ErrorContentNegotiaionStrategy is not getting called.
Did anyone face this issue ?
Add the #Primary annotation to your ErrorContentNegotiationStrategy class:
Indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency. If exactly one 'primary' bean exists among the candidates, it will be the autowired value.
This should at least solve the exception during startup.
After debugging the issue, I found that I was trying to load 2 beans of same type ( which is what the error message says. One of the bean was actual implementation and other one was a Mock for test case. Since both resided in the same package #component scanned the base package and couldn't decide which one to load. I resolved the issue by using #profile, which helps in loading beans based on the profile you load. I used 2 profiles, one for testing and one for development.

Spring Boot 1.2.5.RELEASE & Spring Security 4.0.2.RELEASE - How to load configuration file before security context initialization?

I'm facing a problem with Spring: I'm migrating from Spring Security ver. 3.2.7.RELEASE to 4.0.2.RELEASE. Everything was working fine in older version, however a problem occured when it came to loading DataSource.
Let me describe the architecture:
Application is secured with both SAML and LDAP mechanisms (SAML configuration is pretty similar to config given here: https://github.com/vdenotaris/spring-boot-security-saml-sample/blob/master/src/main/java/com/vdenotaris/spring/boot/security/saml/web/config/WebSecurityConfig.java).
They both need to connect to database in order to get some required data. We use MyBatis with Spring Mybatis to get needed data. That's, where the problem begins.
My DAO configuration class looks like this:
#Configuration
#EnableConfigurationProperties
#MapperScan(basePackages = { "pl.myapp" })
public class DaoConfiguration {
#Bean
#Primary
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#Primary
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
#Bean
#Primary
public SqlSessionFactoryBean sqlSessionFactoryBean() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
// some stuff happens here
return sqlSessionFactoryBean;
}
#Bean
#Primary
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
#Bean
#ConfigurationProperties(prefix = "liquibase.datasource")
#ConditionalOnProperty(name="liquibase.enabled")
public DataSource liquibaseDataSource() {
DataSource liquiDataSource = DataSourceBuilder.create().build();
return liquiDataSource;
}
}
In previous version it worked like a charm, but now it has a problem loading mappers, resulting in Bean creation exception on FactoryBean type check: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'someMapper' defined in file [<filename>]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required
over and over again (it's not my problem, it's a known Spring/MyBatis bug).
I did some debugging and discovered something interesting: it looks like DaoConfiguration is not treated like a configuration here! I mean: if I add
#Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
return sqlSessionFactoryBean().getObject();
}
to this config, "normal" call of #Bean annotated method should result in calling proper interceptor, here it lacks this funcionality.
My prediction is that: this config class has not been properly wrapped yet and Spring Security already needs beans produced by it.
Is there any solution to properly load this configuration before Spring Security is initialized? Or am I just wrong and missing something (maybe not so) obvious?

Resources