spring boot - How to load context configuration file - spring-mvc

I am trying to convert spring mvc app to spring boot. I used to deploy this application in tomcat and test. Now with spring boot I am trying to do the same thing but I am facing issues to load xml file configuration.
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<!-- Data sources -->
<Environment name="/source/schema" value="${schema}" type="java.lang.String" />
<Resource auth="Container" driverClass="org.postgresql.Driver"
factory="org.apache.naming.factory.BeanFactory"
idleConnectionTestPeriod="30" jdbcUrl="${url}"
maxAdministrativeTaskTime="0" maxConnectionAge="30" maxIdleTime="9" maxPoolSize="3" minPoolSize="2"
name="/source/DataSource" password="${password}"
preferredTestQuery="select 1" testConnectionOnCheckout="true" type="com.mchange.v2.c3p0.ComboPooledDataSource" user="${user}"/>
</Context>
This is my configuration file which I am trying to load. When I put
#ImportResource({"classpath:applicationContext.xml", "classpath:context.xml"})
I am able to load all the bean configuration from applicationcontext.xml but while loading context.xml it is giving
Caused by: org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'Context'.
How should I load these entries when deploying spring boot app in tomcat?

By default, JNDI is disabled in embedded Tomcat. You need to call Tomcat.enableNaming() to enable it.
If you can live by Java config,you can try below snippet to add JNDI and other configurations from context.xml using the java config.
#Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {
#Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}
};
}
Example :
#Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {
#Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}
#Override
protected void postProcessContext(Context context) {
ContextResource resource = new ContextResource();
resource.setName("jdbc/myDataSource");
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName", "your.db.Driver");
resource.setProperty("url", "jdbc:yourDb");
context.getNamingResources().addResource(resource);
}
};
}
#Bean(destroyMethod="")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("java:comp/env/jdbc/myDataSource");
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet();
return (DataSource)bean.getObject();
}
Have a look at this github link for related sample

context.xml should go into the /META-INF/ directory in your war files. It is instructions to the Tomcat server, there's no need to configure anything in Spring to try to load it.

Related

Multi-platform compilation: System.Configuration.ConfigurationManager.GetSection throws error on .NetCore

Background:
We are in the process of migrating .Net application to .Net Core.
As a strategy, we would like to keep the existing functionality intact on Full framework while migrating portion of the application to .Net Core. Full application would support .services over Net remoting and REST API whereas .Net core application will only support REST API services.
We have decided to keep the same code base for entire application and support compilation on multiple platforms (NetcoreApp2.1 and Net472).
There is a single application configuration file. Most of the components are dependent on the information stored in this file. Thus we would like to retain the single configuration file for both platforms.
I used System.Configuration.ConfigurationManager package to access configuration information.
Issue:
ConfigurationManager.GetSection(string) throws exception on .Net core platform whereas it works fine on Net472.
Error Message: Configuration system failed to initialize ---> System.Configuration.ConfigurationErrorsException: Unrecognized configuration section system.runtime.remoting
Work around tried so far:
ConfigurationManager.OpenExeConfiguration(configurationUserLevel).GetSection(string) works perfect on both the platforms for fetching the same section
Sample Code:
static MyConfigurationSection myConfigurationSettings { get; set; }
static void Main(string[] args)
{
LoadSettings();
}
private static void LoadSettings()
{
try
{
//Net472 : Works
//NetCoreApp2.1: Throws exception
myConfigurationSettings = ConfigurationManager.GetSection("myCustomSettings") as MyConfigurationSection;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
//Works on both platform
myConfigurationSettings = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).GetSection("myCustomSettings") as MyConfigurationSection;
Console.WriteLine(myConfigurationSettings.Applications.Count);
Console.ReadLine();
}
Here is configuration file
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="myCustomSettings" type="TestConfigManager.MyConfigurationSection, TestConfigManager" />
</configSections>
<myCustomSettings>
<applications/>
</myCustomSettings>
<system.runtime.remoting>
<application>
<channels>
<channel ref="tcp" port="1111" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
Unfortunately, accessing configuration works slightly differently in the Core Framework (and also .NET 5 and 6). Even with the help of the links below, it took me some time to find it out.
This is how it worked for me:
As preparation, go to NUGET package manager and import
Microsoft.Extensions.Configation,
Microsoft.Extensions.Configation.Json,
Microsoft.Extensions.Configation.Xml
and (optional) Microsoft.Windows.Compatibility
Depending on the type of config file, access it as follows:
App.Config
Example:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="myKey" value="myValue"/>
</appSettings>
</configuration>
Declare
public static AppSettingsSection AppConfig { get; private set; } = null;
Initialize it via
AppConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
.AppSettings;
Read any keys via:
var myValue = AppConfig.Settings["myKey"].Value;
appconfig.json
Example:
{
"AppSettings": {
"myKey": "myValue"
}
}
Declare
public static IConfigurationSection JsonConfig { get; private set; } = null;
Initialize it via
JsonConfig = new ConfigurationBuilder().AddJsonFile("appconfig.json",
optional: true, reloadOnChange: true).Build().GetSection("AppSettings");
Read any keys via:
var myValue = JsonConfig["myKey"];
Helpful links:
cant read app config in c-sharp
how to read appsettings values from json
Comparision between appSettings and ApplicationSettings

Serving Static resources from file system | Spring Boot Web

Using a Spring Boot web application I trying to serve my static resource from a file system folder outside my project.
Folder structure looks like:-
src
main
java
resources
test
java
resources
pom.xml
ext-resources (I want to keep my static resources here)
test.js
Spring Configuration:-
#SpringBootApplication
public class DemoStaticresourceApplication extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(DemoStaticresourceApplication.class, args);
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/test/**").addResourceLocations("file:///./ext-resources/")
.setCachePeriod(0);
}
}
Hitting 'http://localhost:9999/test/test.js' in my browser gives back a 404.
How should I configure ResourceHandlerRegistry to serve static resources from the above mentioned 'ext-resources' folder?
I should be able to switch cache on/off for dev/prod environment.
Thanks
UPDATE 1
Giving absolute file path works:-
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/test/**")
.addResourceLocations(
"file:///C:/Sambhav/Installations/workspace/demo-staticresource/ext-resources/")
.setCachePeriod(0);
}
How can I provide relative location? Absolute path will make my life tough during build & deploy process.
file:/// is an absolute URL pointing to the root of the filesystem and, therefore, file:///./ext-resources/ means that Spring Boot is looking for resources in a directory named ext-resources in the root.
Update your configuration to use something like file:ext-resources/ as the URL.
This is what I did in the WebConfig class, inside the addResourceHandlers method:
boolean devMode = this.env.acceptsProfiles("development");
String location;
if (devMode) {
String currentPath = new File(".").getAbsolutePath();
location = "file:///" + currentPath + "/client/src/";
} else {
location = "classpath:static/";
}
Spring Boot Maven Plugin can add extra directories to the classpath. In your case you could include that in your pom.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<folders>
<folder>${project.build.directory}/../ext-resources</folder>
</folders>
...
</configuration>
</plugin>
So that way you don't need inlcude any hard-code in your classes. Simply start your webapp with
mvn spring-boot:run
static resources (eg:html js css etc) can be placed in the same level directory of project or jar, named public. contents will be servered without additional config.

spring webapp without web.xml and without any spring xml config file

below is a piece of code taken from
https://github.com/spring-projects/spring-webflow-samples/blob/master/booking-mvc/src/main/java/org/springframework/webflow/samples/booking/config/DispatcherServletInitializer.java
public class DispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] {
SecurityConfig.class,
DataAccessConfig.class,
WebMvcConfig.class,
WebFlowConfig.class
};
}
....
}
There is no web.xml in this project. There are no spring xml config files, so also no context:component-scan xml tag.
How does spring know, that it should execute getRootConfigClasses from DispatcherServletInitializer ? Does spring scan for classes implementing WebApplicationInitializer ? If so, where is it configured to scan for such classes ?
Is there some default web-fragment.xml in META-INF in some spring jar ? If so in which jar is it ? In spring-web-4.0.5.release.jar there is only < name > spring_web < name > < distributable/> in spring-webmvc.jar there is no web-fragment.xml.

Spring 3.1 ReloadableResourceBundleMessageSource and Apache Tiles 2.2.2

What seemed to be a simple task has turned out to be a few hours of suffering.
I am building a Spring 3.1 MVC application on the JavaEE 6 and Servlet 3.0.1 api without a web.xml file. I have a WebMvcConfiguration class like this fragment:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "be.collectortools.collectorsite")
public class MvcConfig extends WebMvcConfigurationSupport {
#Bean
public ReloadableResourceBundleMessageSource messageSourceBean() {
String[] basenames = {"classpath:messages"};
ReloadableResourceBundleMessageSource resourceBundle = new ReloadableResourceBundleMessageSource();
resourceBundle.setBasenames(basenames);
resourceBundle.setDefaultEncoding("UTF-8");
return resourceBundle;
}
}
I have successfully setup Apache Tiles 2.2.2 together with 2 basic controllers.
Now I would like to add ResourceBundles to the working Spring/Tiles application and I can't get them to work.
After searching I found some this that might go wrong:
Do I use fmt:message key="application.header" or should I use spring:message code="application.header" in my JSP pages? The first ignores not found values the second throws errors.
I use ReloadableResourceBundleMessageSource which should be 'better' or at least newer then ResourceBundleMessageSource is this ok?
ReloadableResourceBundleMessageSource loads files from more locations so I have specified classpath:
I placed the messages.properties file in the src/main/resources folder
Is it still correct that, when not adding a locale to the end of a bundle's name, this is used as a (default) fallback? Either way adding the "en_US" locale doesn't help.
The error:
root cause
javax.servlet.jsp.JspTagException: No message found under code 'application.header' for locale 'en_US'.
org.springframework.web.servlet.tags.MessageTag.doStartTagInternal(MessageTag.java:184)
also the war file is not being run inside Eclipse I deploy it manually to my local tomcat 7.0.23. This also allows me to see the deployed file structure more easily and gives me better control.
I have no clue what is I am doing wrong any help would be appreciated.
The MessageSource bean has to be named messageSource not messageSourceBean - if you change your #Bean to the following it should resolve the messages correctly:
#Bean
public ReloadableResourceBundleMessageSource messageSource() {
String[] basenames = {"classpath:messages"};
ReloadableResourceBundleMessageSource resourceBundle = new ReloadableResourceBundleMessageSource();
resourceBundle.setBasenames(basenames);
resourceBundle.setDefaultEncoding("UTF-8");
return resourceBundle;
}

Restart/ReInit a servlet

I want to restart a servlet (declared in web.xml, when JBoss is running) simply because its init-param points to a file which content has changed (i.e. providers.fac below has been modified).
If there is a way to reload the init-param without restarting the servlet, it will be good too.
I suppose I can modify the servlet to add a request param and function to restart itself ?
Is there any other option?
<servlet>
<servlet-name>coverage</servlet-name>
<servlet-class>coverageServlet</servlet-class>
<init-param>
<param-name>ConfigUrl</param-name>
<param-value>file:///C:/coverage/providers.fac</param-value>
</init-param>
<init-param>
<param-name>CacheDir</param-name>
<param-value>coverage</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Environment:
Servlet Api 2.4
JBoss 4.2
Spring Framework 2.5
If you are in jboss you can simply restart a servlet by altering the web.xml file if your servlet is exploded. On linux a touch would do.
Not sure what format your config file is but if you are trying to reload automatically a property configuration file I would have a look at the commons configuration lib that supports this out of the box(FileChangedReloadingStrategy)
If you are planning to restart your servlet automatically and many many times in a day/week you should make sure your permgen is good enough to handle the servlet reloads. There were instances where I had done this in production and burnt myself down with a lot of PermGen errors.
2 options:
Add an extra check on doGet() or doPost() which reloads the file when a certain request parameter is been set while an admin user is logged in and provide an admin screen which invokes that request.
Rewrite the servlet (or refactor the part to ServletContextListener) to store it in ServletContext instead of as an instance variable of the servlet and have some admin screen which reloads the file in ServletContext.
I would separate these concerns by pulling the file management out of the servlet and putting it into a JBoss JMX ServiceMBean. The MBean can take care of watching the file for changes and reloading when necessary, and can also expose the required operations [to the calling servlet]. This will allow you not to have to reload and re-init the servlet (or the WAR) which are fairly heavyweight operations.
I will invent a couple of operations for the FileManager:
public interface FileManagerMBean extends org.jboss.system.ServiceMBean {
public void setFileName(String fileName);
public void setCheckFrequency(long freq);
public String getCoverageData(......);
public String getProviderData(......);
}
The implementation might be (in the same package please :) )
public class FileManager extends org.jboss.system.ServiceMBeanSupport implements FileManagerMBean {
public void setFileName(String fileName) { .... }
public void setCheckFrequency(long freq) { .... }
public String getCoverageData(......) { /* impl */ }
public String getProviderData(......) { /* impl */ }
public void startService() throws Exception {
/* Start a file watcher thread */
}
public void stopService() throws Exception {
/* Stop the file watcher thread */
}
}
Your servlet might look like this:
// A ref to the MBean
FileManagerMBean fileMgr = null;
// The JMX MBean's ObjectName
ObjectName fileMgrOn = org.jboss.mx.util.ObjectNameFactory.create("portoalet.com:service=FileManager");
public void init() {
// Get the JBoss MBeanServer
MBeanServer server = org.jboss.mx.util.MBeanServerLocator.locateJBoss();
// Create an MBeanInvoker for the service
fileMgr = (FileManagerMBean)javax.management.MBeanServerInvocationHandler.newProxyInstance(server, fileMgrOn,FileManagerMBean.class, false);
}
Now you can use the fileMgr instance to make calls to your FileManager MBean, which should be thread safe unless you synchronize access to fileMgr.
I realize this looks a bit over-engineered, but you really should separate the functions of the servlet from the functions of managing the file.

Resources