spring boot - load context xml outside classpath and listeners - spring-boot

I am converting my camel spring application to use spring boot latest version 1.4.0.
My application context xml is outside my WAR , to add flexibility. But I am unable to get it loaded in the project. If I add it inside my project classpath and use #ImportResource it works fine. How do i Get it loaded from outside.
My web.xml (old one)
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Archetype Created Web Application</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>file:///${camel.config.location}</param-value>
<description> location of spring xml files</description>
</context-param>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>file:///${log4j.config.location}</param-value>
</context-param>
<context-param>
<param-name>log4jRefreshInterval</param-name>
<param-value>10000</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
</web-app>
I have implemented the below application class after searching thru a example but the context.xml is not loaded . I have added it as a resource Loader. I used onStartup too but still noting gets loaded only spring boot loads up.
#Configuration
#EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
#ComponentScan
public class DLBootApplication extends SpringBootServletInitializer {
public static void main(String[] args) throws Exception {
final ConfigurableEnvironment environment = new StandardServletEnvironment();
initialize(environment);
final ResourceLoader resourceLoader = createResourceLoader();
final ApplicationContext ctx = new SpringApplicationBuilder(DLBootApplication.class)
.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class).environment(environment)
.resourceLoader(resourceLoader).run(args);
}
public static void initialize(ConfigurableEnvironment environment) {
final String camelConfig = environment.getProperty(Constants.DL_CAMEL_CONFIG_LOCATION);
final String log4jConfig = environment.getProperty(Constants.DL_LOG4J_CONFIG_LOCATION);
if (StringUtils.isEmpty(camelConfig)) {
throw new IllegalStateException(Constants.DL_CAMEL_CONFIG_LOCATION + " is empty");
}
if (StringUtils.isEmpty(log4jConfig)) {
throw new IllegalStateException(Constants.DL_LOG4J_CONFIG_LOCATION + " is empty");
}
System.setProperty(Constants.DL_CAMEL_CONFIG_LOCATION, camelConfig);
System.setProperty(Constants.DL_LOG4J_CONFIG_LOCATION, log4jConfig);
}
public static ResourceLoader createResourceLoader() {
final FileSystemResourceLoader resLoader = new FileSystemResourceLoader();
resLoader.getResource(System.getProperty(Constants.DL_CAMEL_CONFIG_LOCATION));
return resLoader;
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
final ConfigurableEnvironment environment = new StandardServletEnvironment();
initialize(environment);
final ResourceLoader resourceLoader = createResourceLoader();
return application.sources(DLBootApplication.class).environment(environment).resourceLoader(resourceLoader);
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Create the 'root' Spring application context
final AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(DLBootApplication.class);
// Manage the lifecycle of the root application context
servletContext.addListener(new ContextLoaderListener(rootContext));
servletContext.addListener(new Log4jConfigListener());
}
}

got it working using #Importresorce and the file location from a environment variable.

Related

How to allow GET method for endpoint programmatically?

I am loading a .war file and add it as web app to the embedded Tomcat server.
#Bean
public EmbeddedServletContainerFactory servletContainerFactory() {
LOGGER.info("Adding web app");
return new TomcatEmbeddedServletContainerFactory() {
#Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
String appHome = System.getProperty(Environment.APP_HOME);
String targetFileName = "web-0.0.1-SNAPSHOT.war";
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(targetFileName);
LOGGER.info(System.getProperty("user.name"));
LOGGER.debug("Loading WAR from " + appHome);
File target = new File(Paths.get(appHome, targetFileName).toString());
try {
LOGGER.info(String.format("Copy %s to %s", targetFileName, target.getAbsoluteFile().toPath()));
java.nio.file.Files.copy(resourceAsStream, target.getAbsoluteFile().toPath(), StandardCopyOption.REPLACE_EXISTING);
Context context = tomcat.addWebapp("/", target.getAbsolutePath());
context.setParentClassLoader(getClass().getClassLoader());
} catch (ServletException ex) {
throw new IllegalStateException("Failed to add webapp.", ex);
} catch (Exception e) {
throw new IllegalStateException("Unknown error while trying to load webapp.", e);
}
return super.getTomcatEmbeddedServletContainer(tomcat);
}
};
}
This is working so far but if I access http://localhost:8080/web I am getting
2017-03-04 11:18:59.588 WARN 29234 --- [nio-8080-exec-2] o.s.web.servlet.PageNotFound : Request method 'GET' not supported
and the response
Allow: POST
Content-Length: 0
Date: Sat, 04 Mar 2017 10:26:16 GMT
I am sure all I have to do is to allow the GET method on /web and hopefully the static web content provided from the loaded war file will be accessible via web browser.
How/where can I configure the endpoint such that it allows GET requests?
I tried to introduce a WebController as described in this tutorial.
#Controller
public class WebController {
private final static Logger LOGGER = Logger.getLogger(WebController.class);
#RequestMapping(value = "/web", method = RequestMethod.GET)
public String index() {
LOGGER.info("INDEX !");
return "index";
}
}
In the log output I can see that this is getting mapped correctly:
RequestMappingHandlerMapping : Mapped "{[/web],methods=[GET]}" onto public java.lang.String org.ema.server.spring.controller.dl4j.WebController.index()
but it does not change the fact that I cannot visit the website.
I've also configured a InternalResourceViewResolver:
#Configuration
#EnableWebMvc
public class MvcConfiguration extends WebMvcConfigurerAdapter {
private final static Logger LOGGER = Logger.getLogger(MvcConfiguration.class);
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
LOGGER.info("configureViewResolvers()");
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setSuffix(".html");
registry.viewResolver(resolver);
}
#Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
web.xml
Since I configure everything in pure Java, this file does not define a lot:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>Easy Model Access Server</display-name>
<listener>
<listener-class>org.ema.server.ServerEntryPoint</listener-class>
</listener>
<context-param>
<param-name>log4j-config-location</param-name>
<param-value>WEB-INF/classes/log4j.properties</param-value>
</context-param>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/web/*.html</url-pattern>
</servlet-mapping>
</web-app>
Reproduce
If you want to reproduce this you can simply checkout the entire code from github. All you need to do this:
mkdir ~/.ema
git clone https://github.com/silentsnooc/easy-model-access
cd easy-model-access/ema-server
mvn clean install
java -jar server/target/server-*.jar
This will clone, build and run the server.
The directory ~/.ema directory is required at the moment. It is where the WAR is being copied as the server starts.
My guess is that your web.xml maps any path to the Spring DispatcherServlet, something like:
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Because of <url-pattern>/</url-pattern> any request must be handled by a Spring controller, for this reason your static files are not served by Tomcat. Also a pattern like /*.html would have same effect.
If you have only a few pages you might add one or more mapping to the predefined default servlet for them, before the mapping of Spring (and also before Spring Security if you use it):
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>index.html</url-pattern>
</servlet-mapping>
You may also use <url-pattern>*.html</url-pattern> or, if your resources are under the web path and there are only static resources there: <url-pattern>/web/*</url-pattern>
Maybe all this is done instead in Java code in the org.ema.server.ServerEntryPoint that you have as a listener in web.xml
I think the mapping I wrote up in web.xml is done in your case in method getServletMappings of class org.ema.server.spring.config.AppInitializer, I changed it to use a more strict pattern /rest-api/* instead than /, not sure pattern is correct and everything else works, but now http://127.0.0.1:8080/index.html works
#Override
protected String[] getServletMappings() {
return new String[] { "/rest-api/*" };
}
as I see the url: http://localhost:8080/web is wrong.
You can try: http://localhost:8080/[name-of-war-file]/web

Spring boot convert web.xml listener

I'm trying to convert my project to Spring Boot project (executable jar file with Jetty embedded).
All works with a standard example but I want migrate my old web.xml to Spring Boot.
I migrated Servlet and Filters but I don't understand how migrate filters as this:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<listener>
<listener-class>org.granite.config.GraniteConfigListener</listener-class>
</listener>
<listener>
<listener-class>org.granite.gravity.websocket.GravityWebSocketDeployer</listener-class>
</listener>
I created my #SpringBootApplication class and I wrote inside all the configuration:
#Bean
#Order(1)
public FilterRegistrationBean springSecurityFilterChain() {
FilterRegistrationBean filterRegBean = new FilterRegistrationBean();
DelegatingFilterProxy delegatingFilterProxy = new DelegatingFilterProxy();
filterRegBean.setFilter(delegatingFilterProxy);
List<String> urlPatterns = new ArrayList<String>();
urlPatterns.add("/*");
filterRegBean.setUrlPatterns(urlPatterns);
return filterRegBean;
}
Someone can explain me how Listeners should be converted?
Spring Boot will automatically register any #Beans of the following types with the servlet container:
ServletContextAttributeListener
ServletRequestListener
ServletRequestAttributeListener
HttpSessionAttributeListener
HttpSessionListener
ServletContextListener
For example, to register GravityWebSocketDeployer which is a ServletContextListener add a #Bean method to your configuration class:
#Bean
public GravityWebSocketDeployer gravityWebSocketDeployer() {
return new GravityWebSocketDeployer();
}
For RequestContext read this
#Bean
#ConditionalOnMissingBean(RequestContextListener.class)
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
For the other listener is register automatically when you use spring-boot as this link implies.
For your own listeners.
public class MyAdditionListeners extends SpringBootServletInitializer {
protected final Log logger = LogFactory.getLog(getClass());
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
WebApplicationContext rootAppContext = createRootApplicationContext(servletContext);
if (rootAppContext != null) {
servletContext.addListener(new YourListenerHere());
}
else {
this.logger.debug("No ContextLoaderListener registered, as "
+ "createRootApplicationContext() did not "
+ "return an application context");
}
}
Finally there is a link in which you can find some information about listeners and SpringApplication class. Read section
Also Spring Boot will automatically register any #Bean extend of HttpServlet;
#Bean
public ServletRegistrationBean axisServletRegistrationBean() {
ServletRegistrationBean registration = new ServletRegistrationBean(new AxisServlet(), "/services/*");
registration.addUrlMappings("*.jws");
return registration;
}

Adding a Filter and Init Params Progmatically

I need to copy the contents of a web.xml to the WebAppInitializer.class (Java Configuration Class). I have copied the YahooFilter Class from web.xml (see code) but I am not sure how to add the init-params pragmatically.
I have pasted the web.xml and snippet of the Java Configuration class below. Can somebody take a look and provide some feedback?
<web-app>
<display-name>sample</display-Aname>
<filter>
<filter-name>YOSFilter</filter-name>
<filter-class>com.yahoo.yos.YahooFilter</filter-class>
<!--
optional param -
underlying oauth client class
possible values:
net.oauth.client.URLConnectionClient (default)
net.oauth.client.httpclient3.HttpClient3
net.oauth.client.httpclient4.HttpClient4
-->
<init-param>
<param-name>oauthConnectionClass</param-name>
<param-value>net.oauth.client.httpclient4.HttpClient4</param-value>
</init-param>
<!--
optional param -
redirect end-user if an access token is not found, set to false if you
are only making two-legged oauth calls e.g. oauth calls without an
access token to retrieve public information
defauts to true
-->
<init-param>
<param-name>redirect</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!--
The URL where the filter is mapped to will redirect the user to Yahoo for
authorization if an OAuth authorization token has not been obtained for the
user. Should correspond to your callback url
-->
<filter-mapping>
<filter-name>YOSFilter</filter-name>
<url-pattern>/login.jsp</url-pattern>
</filter-mapping>
</web-app>
Java Config Class
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
...
#Override
protected void registerDispatcherServlet(ServletContext servletContext) {
super.registerDispatcherServlet(servletContext);
servletContext.addListener(new HttpSessionEventPublisher());
// servletContext.addListener(new RequestContextListener());
}
#Override
protected Filter[] getServletFilters() {
DelegatingFilterProxy delegatingFilterProxy = new DelegatingFilterProxy();
delegatingFilterProxy.setTargetBeanName("springSecurityFilterChain");
// FilterConfig filterConfig = delegatingFilterProxy.getFilterConfig();
YahooFilter yosFilter = new YahooFilter();
return new Filter[] {delegatingFilterProxy,yosFilter};
}
}
Try overriding onStartup() method and programatically register your filter with ServletContext like this:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
FilterRegistration yahooFilter = servletContext.addFilter("yahooFilter", new YahooFilter());
yahooFilter.setInitParameter("oauthConnectionClass", "net.oauth.client.httpclient4.HttpClient4");
yahooFilter.setInitParameter("redirect", "true");
}

How to configure Jersey with Spring using only annotations

I have a Servlet 3.0 web app that uses both Spring and Jersey. I currently have it set up using the SpringServlet configured as a filter in web.xml, and the resource classes annotated with both #Path and #Component. Here's the web.xml snippet:
<filter>
<filter-name>jersey-serlvet</filter-name>
<filter-class>
com.sun.jersey.spi.spring.container.servlet.SpringServlet
</filter-class>
<init-param>
<param-name>
com.sun.jersey.config.property.packages
</param-name>
<param-value>com.foo;com.bar</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.feature.FilterForwardOn404</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jersey-serlvet</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
This setup works, but I really want to get this set up with annotations only - no web.xml config. My first attempt at this was to remove the above SpringServlet configuration and create a class that extends Application. Here's a snippet of that:
#ApplicationPath("/*")
public class MyApplication extends PackagesResourceConfig {
public MyApplication() {
super("com.foo;com.bar");
HashMap<String, Object> settings = new HashMap<String, Object>(1);
settings.put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
this.setPropertiesAndFeatures(settings);
}
}
This works in that the JAX-RS resources are registered and I can hit them at their URLs, but they throw NullPointerExceptions when they try and use their autowired properties... this makes sense because I'm guessing the resources are now being loaded by Jersey and are not Spring managed beans, therefore no autowiring.
Despite a fair bit of searching around I cannot find any way of loading the Jersey resources as Spring beans with annotations only. Is there such a way? I don't really want to have to write a bunch of code for the resources to manually fetch the Spring context and invoke the DI if I can help it.
If annotations-only isn't going to work, then I can live with the filter config in web.xml if I can specify an Application class to load instead of a list of packages to scan. If I can get rid of the package list in there and just specify an Application class instance then I'll be content.
Obviously it would be great if someone had a definitive answer for me but I'd also be grateful for any pointers or hints of where else I could look or things to try.
Thanks,
Matt
Below is part of my app, which uses Servlet 3.0, Spring, Jersey 1.8 and it has no web.xml:
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
final AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation("com.myapp.config");
final FilterRegistration.Dynamic characterEncodingFilter = servletContext.addFilter("characterEncodingFilter", new CharacterEncodingFilter());
characterEncodingFilter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
characterEncodingFilter.setInitParameter("encoding", "UTF-8");
characterEncodingFilter.setInitParameter("forceEncoding", "true");
final FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy());
springSecurityFilterChain.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
servletContext.addListener(new ContextLoaderListener(context));
servletContext.setInitParameter("spring.profiles.default", "production");
final SpringServlet servlet = new SpringServlet();
final ServletRegistration.Dynamic appServlet = servletContext.addServlet("appServlet", servlet);
appServlet.setInitParameter("com.sun.jersey.config.property.packages", "com.myapp.api");
appServlet.setInitParameter("com.sun.jersey.spi.container.ContainerRequestFilters", "com.myapp.api.SizeLimitFilter");
appServlet.setLoadOnStartup(1);
final Set<String> mappingConflicts = appServlet.addMapping("/api/*");
if (!mappingConflicts.isEmpty()) {
throw new IllegalStateException("'appServlet' cannot be mapped to '/' under Tomcat versions <= 7.0.14");
}
}
}
I haven't been able to get my ideal result but I have been able to make some progress, so I'll post here in case it helps anyone else. I was able to use the Spring Servlet to specify my application class, thereby removing the package list from the web.xml.
The web.xml changes required are in the init params (the filter mapping is not shown but is still required):
<filter>
<filter-name>jersey-serlvet</filter-name>
<filter-class>
com.sun.jersey.spi.spring.container.servlet.SpringServlet
</filter-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name> <!-- Specify application class here -->
<param-value>com.foo.MyApplication</param-value>
</init-param>
</filter>
And then in the application class I had to change the way I called the super constructor slightly:
public MyApplication() {
super("com.foo", "com.bar"); // Pass in packages as separate params
HashMap<String, Object> settings = new HashMap<String, Object>(1);
settings.put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
this.setPropertiesAndFeatures(settings);
}
Still not exactly what I was after but at least this pulls a little more config into Java code and out of the web.xml, which is important for me as I'm trying to hide this detail.
Two options spring to mind (no pun intended).
Maybe you could extend SpringServlet with your own class and add appropriate servlet 3.0 annotations to it.
Going along with your approach of switching from the SpringServlet to an Application class, you could solve the no-autowiring problem by enabling Spring build-time or load-time bytecode weaving. That enables Spring to inject objects instantiated by anywhere instead of only objects created by Spring. See "Using AspectJ to dependency inject domain objects with Spring".
First of all, in a servlet 3.0 container you don't really need a web.xml.
But with Jersey 2.0 you can set a flag to scan the whole web app for annotated resources:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>jersey</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.servlet.provider.webapp</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Spring will be enabled automatically if you include this jar:
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-spring3</artifactId>
<version>2.3.1</version>
</dependency>
I used Jersey with my previously made project using SpringMVC. I based my code on the Spring's official documentation.
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
// Don't create the Listener that Jersey uses to create.
// There can only be one linstener
servletContext.setInitParameter("contextConfigLocation", "<NONE>");
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// Add app config packages
context.setConfigLocation("config.package");
// Add listener to the context
servletContext.addListener(new ContextLoaderListener(context));
// Replacing:
// <servlet-name>ServletName</servlet-name>
// <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
// <init-param>
// <param-name>com.sun.jersey.config.property.packages</param-name>
// <param-value>webservices.packages</param-value>
// </init-param>
// <load-on-startup>1</load-on-startup>
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
ServletRegistration.Dynamic appServlet = servletContext.addServlet("ServletName", new DispatcherServlet(dispatcherContext));
appServlet.setInitParameter("com.sun.jersey.config.property.packages", "org.sunnycake.aton.controller");
appServlet.setLoadOnStartup(1);
appServlet.addMapping("/RootApp");
}
}
The configuration classes in config.package are:
// Specifies that there will be bean methods annotated with #Bean tag
// and will be managed by Spring
#Configuration
// Equivalent to context:component-scan base-package="..." in the xml, states
// where to find the beans controlled by Spring
#ComponentScan(basePackages = "config.package")
public class AppConfig {
/**
* Where will the project views be.
*
* #return ViewResolver como el XML
*/
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
return viewResolver;
}
}
Hibernate configuration
// Specifies that there will be bean methods annotated with #Bean tag
// and will be managed by Spring
#Configuration
// Equivalent to Spring's tx in the xml
#EnableTransactionManagement
// Equivalent to context:component-scan base-package="..." in the xml, states
// where to find the beans controlled by Spring
#ComponentScan({"config.package"})
// Here it can be stated some Spring properties with a properties file
#PropertySource(value = {"classpath:aplicacion.properties"})
public class HibernateConfig {
/**
* Inyected by Spring based on the .properties file in the
* \#PropertySource tag.
*/
#Autowired
private Environment environment;
/**
* Here it's created a Session Factory, equivalent to the Spring's config file one.
*
* #return Spring Session factory
*/
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
// Uses the datasource
sessionFactory.setDataSource(dataSource());
// Indicates where are the POJOs (DTO)
sessionFactory.setPackagesToScan(new String[]{"dto.package"});
// Se asignan las propiedades de Hibernate
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
/**
* Propiedades de la base de datos (Según environment)
*
* #return Nuevo DataSource (Configuración de la base de datos)
*/
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
return dataSource;
}
/**
* Hibernate properties
*
* #return Properties set with the configuration
*/
private Properties hibernateProperties() {
Properties properties = new Properties();
// Dialect (Mysql, postgresql, ...)
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
// Show SQL query
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
return properties;
}
/**
* Inyected by sessionFactory
*/
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}
}
This is a full example. First of all - don't use any web.xml. Use only code below.
Rest resource:
#Path("hello")
public class HelloResource {
#GET
#Produces(MediaType.TEXT_PLAIN)
public Response hello() {
String output = "Hello World!";
return Response.status(200).entity(output).build();
}
}
Rest application (note "core" in package name)
#ApplicationPath("rest")
public class RestApplication extends javax.ws.rs.core.Application {
public RestApplication() {
}
#Override public Set<Class<?>> getClasses() {
return Set.of(
HelloResource.class
);
}
}
Spring web configuration.
#Configuration
#EnableWebMvc
#ComponentScan(basePackageClasses = {
})
public class WebConfig implements WebMvcConfigurer {
private static final Logger logger = LoggerFactory.getLogger(WebConfig.class);
#Autowired
private ApplicationContext applicationContext;
public WebConfig() {
}
}
Spring initializer
//This #Order is required!!!
#Order(Ordered.HIGHEST_PRECEDENCE)
public class MyWebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
var ctx = new AnnotationConfigWebApplicationContext();
//spring WebMvcConfigurer
ctx.register(WebConfig.class);
ctx.setServletContext(servletContext);
//Spring servlet
var servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.setLoadOnStartup(1);
servlet.addMapping("/");
// Register Jersey 2.0 servlet
ServletRegistration.Dynamic jerseyServlet = servletContext.addServlet("jerseyServlet",
"org.glassfish.jersey.servlet.ServletContainer");
//note "javax.ws.rs.Application" doesn't have "core"
jerseyServlet.setInitParameter("javax.ws.rs.Application", RestApplication.class.getName());
jerseyServlet.addMapping("/rest/*");
jerseyServlet.setLoadOnStartup(1);
}
}
And it must work on, for example, http://127.0.0.1:8080/rest/hello

How to set active spring 3.1 environment profile via a properites file and not via an env variable or system property

We use the new environment profiles feature of spring 3.1. We currently set the active profile by setting the environment variable spring.profiles.active=xxxxx on the server to which we deploy the application.
We think this is a suboptimal solution as the war file we want to deploy should just have an additional properties file which sets the environment in which the spring app context should load so the deployment is not dependent on some env var set on the server.
I tried to figure out how to do that and found:
ConfigurableEnvironment.setActiveProfiles()
which I can use to programmatically set the profile but then I still don't know where and when to execute this code. Somewhere where the spring context loads up? Can I load the parameter I want to pass to the method from a properties file?
UPDATE: I just found at docs which I might be able to implement to set the active profile?
In web.xml
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>profileName</param-value>
</context-param>
Using WebApplicationInitializer
This approach is used when you don't have a web.xml file in Servlet 3.0 environment and are bootstrapping the Spring completely from Java:
class SpringInitializer extends WebApplicationInitializer {
void onStartup(ServletContext container) {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.getEnvironment().setActiveProfiles("profileName");
rootContext.register(SpringConfiguration.class);
container.addListener(new ContextLoaderListener(rootContext));
}
}
Where SpringConfiguration class is annotated with #Configuration.
The answer from Thomasz is valid as long as the profile name can be provided statically in the web.xml or one uses the new XML-less configuration type where one could programmatically load the profile to set from a properties file.
As we still use the XML version I investigated further and found the following nice solution where you implement your own ApplicationContextInitializer where you just add a new PropertySource with a properties file to the list of sources to search for environment specific configuration settings. in the example below one could set the spring.profiles.active property in the env.properties file.
public class P13nApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static Logger LOG = LoggerFactory.getLogger(P13nApplicationContextInitializer.class);
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
try {
environment.getPropertySources().addFirst(new ResourcePropertySource("classpath:env.properties"));
LOG.info("env.properties loaded");
} catch (IOException e) {
// it's ok if the file is not there. we will just log that info.
LOG.info("didn't find env.properties in classpath so not loading it in the AppContextInitialized");
}
}
}
You then need to add that initializer as a parameter to the ContextLoaderListener of spring as follows to your web.xml:
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>somepackage.P13nApplicationContextInitializer</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
You can also apply it to DispatcherServlet:
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextInitializerClasses</param-name>
<param-value>somepackage.P13nApplicationContextInitializer</param-value>
</init-param>
</servlet>
For some reason only one way works for me
public class ActiveProfileConfiguration implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent sce) {
System.setProperty(AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, "dev");
System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "dev");
}
....
<listener>
<listener-class>somepackahe.ActiveProfileConfiguration</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Here is a variation on the P13nApplicationContextInitializer approach. However, this time we obtain the path to env properties from JNDI. In my case I set a JNDI global environment variable as coacorrect/spring-profile = file:/tmp/env.properties
In tomcat/tomee server.xml add this: <Environment name="coacorrect/spring-profile" type="java.lang.String" value="/opt/WebSphere/props"/>
Further, in tomcat/tomee, add to the WAR's META-INF/context.xml <ResourceLink global="coacorrect/spring-profile" name="coacorrect/spring-profile" type="java.lang.String"/>
In any container, add appropriate in web.xml
public class SpringProfileApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>{
public static final Logger log = LoggerFactory.getLogger(SpringProfileApplicationContextInitializer.class);
private static final String profileJNDIName="coacorrect/spring-profile";
private static final String failsafeProfile="remote-coac-dbserver";
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
try {
InitialContext ic = new InitialContext();
Object r1 = ic.lookup(profileJNDIName);
if (r1 == null) {
// try the tomcat variant of JNDI lookups in case we are on tomcat/tomee
r1 = ic.lookup("java:comp/env/"+profileJNDIName);
}
if (r1 == null) {
log.error("Unable to locate JNDI environment variable {}", profileJNDIName);
return;
}
String profilePath=(String)r1;
log.debug("Found JNDI env variable {} = {}",r1);
environment.getPropertySources().addFirst(new ResourcePropertySource(profilePath.trim()));
log.debug("Loaded COAC dbprofile path. Profiles defined {} ", Arrays.asList(environment.getDefaultProfiles()));
} catch (IOException e) {
// it's ok if the file is not there. we will just log that info.
log.warn("Could not load spring-profile, defaulting to {} spring profile",failsafeProfile);
environment.setDefaultProfiles(failsafeProfile);
} catch (NamingException ne) {
log.error("Could not locate JNDI variable {}, defaulting to {} spring profile.",profileJNDIName,failsafeProfile);
environment.setDefaultProfiles(failsafeProfile);
}
}
}

Resources