How to load Spring resource bundle from tomcat conf directory - spring

I am trying to load the properties files located in the tomcat conf folder but the code below ends up causing a Missing Resource exception.If I use a property placeholder I can load properties files from tomcat conf fine.
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename">
<value>file:${catalina.base}/conf/messages</value>
</property>
</bean>

If you're trying to externalize your resources and use ResourceBundle this is how I did it using ClassLoader.
private static ClassLoader loader;
private static void setUp()
{
String path = System.getProperty("catalina.base");
File file = new File(path +"/conf/error_messages");
URL[] urls = new URL[0];
try {
urls = new URL[]{file.toURI().toURL()};
} catch (MalformedURLException e) {
e.printStackTrace();
}
loader = new URLClassLoader(urls);
}
Now when I need to load the correct messages: ex: errors_en.properties located outside the application.
ResourceBundle.getBundle("errors", requestLocale, loader);

Related

Override spring logback path after app startup in runtime

I have a spring boot application that uses spring logback for logging. My application is being deployed in an external tomcat. By default the path taken up by the log file resides in tomcat's directory rather than webapp roots sub directory.
Eg: instead of creating the logs inside tomcat -> logs -> app.log
I would like it to be under tomcat -> webapps -> spring-app -> logs -> app.log
Since the path is only known at runtime, I have declared a logback-configuration as app-logback-spring.xml used springProperty to populate the path.
public class LoggingInitializer implements ApplicationContextInitializer{
#Override
public void initialize(ConfigurableApplicationContext appContext) {
try {
URI uri = appContext.getResource("/").getURI();
File file = new File(uri);
Map<String, Object> properties = new HashMap<>();
properties.put("app-root.path", file.getAbsolutePath());
properties.put("app.log.path", file.getAbsolutePath()+"/logs");
System.out.println("props "+properties);
MapPropertySource mapPropSrc = new MapPropertySource("custom", properties);
appContext.getEnvironment().getPropertySources().addFirst(mapPropSrc);
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
JoranConfigurator configurator = new JoranConfigurator();
File configFile = new File(file.getAbsolutePath()+File.separator+"WEB-INF"+File.separator
+"classes"+File.separator+"app-logback-spring.xml");
InputStream configStream = Files.newInputStream(configFile.toPath());
configurator.setContext(loggerContext);
configurator.doConfigure(configStream); // loads logback file
configStream.close();
} catch (IOException e) {
throw new CustomException("Could not initialize application", e);
} catch (JoranException e) {
throw new CustomException("Error configuring logger", e);
}
}
}
<configuration>
<springProperty scope="context" name="logpath" source="app.log.path"/>
<appender name="RollingFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logpath}/app.log</file>
---
---
</configuration>
However, during the initialization I suppose the logpath is not recognized. I infer it based on a folder that is created under tomcat with name logpath_IS_UNDEFINED and the app.log goes under it.
The app-root.path that I set is available for services with correct value. I know I can write to app-logback-spring.xml before reading it, but that looks like a convoluted way.
Is it because when the logger is loading, the spring property is not available is it?

Can the Spring active profiles be set through a property file?

I want to be able to read the active profiles from a property file so that different environments (dev,prod etc) can be configured with different profiles in a Spring MVC based web application. I know that the active profiles can be set through JVM params or system properties. But I would like to do it through a property file instead. The point is that I dont know the active profile statically and instead want to read it from a properties file. It looks like this is not possible. For eg., if I had 'spring.profiles.active=dev' in application.properties, and allow it to be overridden in override.properties like so:
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<value>classpath:/application.properties</value>
<value>file:/overrides.properties</value>
</list>
</property>
</bean>
the profile is not being picked up in the environment. I guess this is because the active profiles are being checked before bean initialization, and therefore do not honor the property being set in a properties file. The only other option I see is to implement an ApplicationContextInitializer that will load those property files in order of priority(override.properties first if it exists, else application.properties) and set the value in context.getEnvironment(). Is there a better way to do it from properties files?
One solution to do it is to read necessary property file with specified profile "manually" - without spring - and set profile at context initialization:
1) Write simple properties loader:
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Properties;
public class PropertiesLoader
{
private static Properties props;
public static String getActiveProfile()
{
if (props == null)
{
props = initProperties();
}
return props.getProperty("profile");
}
private static Properties initProperties()
{
String propertiesFile = "app.properties";
try (Reader in = new FileReader(propertiesFile))
{
props = new Properties();
props.load(in);
}
catch (IOException e)
{
System.out.println("Error while reading properties file: " + e.getMessage());
return null;
}
return props;
}
}
2) Read profile from properties file and set it during Spring container initialization (example with Java-based configuration):
public static void main(String[] args)
{
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles(PropertiesLoader.getActiveProfile());
ctx.register(AppConfig.class);
ctx.refresh();
// you application is running ...
ctx.close();
}

How to make empty configuration if file not found?

Using Apache Commons Configurations 1.9, how to avoid ConfigurationException upon loading a configuration file if the provided file cannot be found?
The Spring app context resembles:
<bean name="foo.config" class="org.apache.commons.configuration.PropertiesConfiguration" init-method="load">
<property name="fileName" value="foo.properties" />
</bean>
However my config file is optional, so I want to make sure the application starts correctly even the file doesn't exist.
How can I achieve this with Commons Configurations? A FactoryBean works, but is there another way?
if (!file.exists()) return new PropertiesConfiguration();
Or using try/catch syntax using an XML configuration:
import org.apache.commons.configuration2.XMLConfiguration;
import org.apache.commons.configuration2.builder.fluent.Configurations;
public class Workspace {
private final XMLConfiguration mConfig;
public Workspace() {
final var configs = new Configurations();
XMLConfiguration config;
try {
config = configs.xml( "filename.xml" );
} catch( final Exception e ) {
config = new XMLConfiguration();
}
mConfig = config;
}
Using a regular properties configuration will work the same way.

loading a file from classpath

I have a line of code that is : File file = new File(getFile()) in a java class HandleData.java
Method - getFile() takes the value of the property fileName. And fileName is injected through application_context.xml with a bean section of the class - HandleData as below:
<bean id="dataHandler" class="com.profile.transaction.HandleData">
<property name="fileName" value="DataFile.xml"></property>
</bean>
I build the project successfully and checked that - DataFile.xml is present in WEB-INF/classes. And the HandleData.class is present in WEB-INF/classes/com/profile/transacon
But when I run it it throws me filenotfound exception.
If I inject the absolute path (C:\MyProjectWorkspace\DataProject\target\ProfileService\WEB-INF\classes\DataFile.xml it finds the file successfully.).
Could someone help in figuring out the proper path to be injected so that the file is taken from the classpath ?
While injecting a File is generally the preferred approach, you can also leverage Spring's ResourceLoader for dynamic loading of resources.
Generally this is as simple as injecting the ResourceLoader into your Spring bean:
#Autowired
private ResourceLoader resourceLoader;
Then to load from the classpath:
resourceLoader.getResource("classpath:myfile.txt");
Since OP is injecting Only the fileName through spring, still want to create the File Object through code ,
You should Use ClassLoadeer to read the file
Try this
InputStream is = HandleData.class.getClassLoader().getResourceAsStream(getFile()));
Edit
Heres the remainder of code , to read the file
BufferedInputStream bf = new BufferedInputStream(is);
DataInputStream dis = new DataInputStream(bf);
while (dis.available() != 0) {
System.out.println(dis.readLine());
}
Edit 2
Since you want it as File Object, to get hold of the FileInputStream
try this
FileInputStream fisTargetFile = new FileInputStream(new File(HandleData.class.getClassLoader().getResource(getFile()).getFile()));
You should have:
<property name="fileName" value="classpath:DataFile.xml" />
And it should be injected as a org.springframework.core.io.Resource similar to this answer

Spring: import a module with specified environment

Is there anything that can achieve the equivalent of the below:
<import resource="a.xml">
<prop name="key" value="a"/>
</import>
<import resource="a.xml">
<prop name="key" value="b"/>
</import>
Such that the beans defined in resouce a would see the property key with two different values? The intention would be that this would be used to name the beans in the imports such that resource a.xml would appear:
<bean id="${key}"/>
And hence the application would have two beans named a and b now available with the same definition but as distinct instances. I know about prototype scope; it is not intended for this reason, there will be many objects created with interdepednencies that are not actually prototypes. Currently I am simply copying a.xml, creating b.xml and renaming all the beans using the equivalent of a sed command. I feel there must be a better way.
I suppose that PropertyPlaceholderConfigurers work on a per container basis, so you can't achieve this with xml imports.
Re The application would have two beans named a and b now available with the same definition but as distinct instances
I think you should consider creating additional application contexts(ClassPathXmlApplicationContext for example) manually, using your current application context as the parent application context.
So your many objects created with interdependencies sets will reside in its own container each.
However, in this case you will not be able to reference b-beans from a-container.
update you can postprocess the bean definitions(add new ones) manually by registering a BeanDefinitionRegistryPostProcessor specialized bean, but this solution also does not seem to be easy.
OK, here's my rough attempt to import xml file manually:
disclaimer: I'm very bad java io programmer actually so double check the resource related code :-)
public class CustomXmlImporter implements BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
private Map<String, String> properties;
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
public Map<String, String> getProperties() {
return properties;
}
private void readXml(XmlBeanDefinitionReader reader) {
InputStream inputStream;
try {
inputStream = new ClassPathResource(this.classpathXmlLocation).getInputStream();
} catch (IOException e1) {
throw new AssertionError();
}
try {
Scanner sc = new Scanner(inputStream);
try {
sc.useDelimiter("\\A");
if (!sc.hasNext())
throw new AssertionError();
String entireXml = sc.next();
PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("${",
"}", null, false);
Properties props = new Properties();
props.putAll(this.properties);
String newXml = helper.replacePlaceholders(entireXml, props);
reader.loadBeanDefinitions(new ByteArrayResource(newXml.getBytes()));
} finally {
sc.close();
}
} finally {
try {
inputStream.close();
} catch (IOException e) {
throw new AssertionError();
}
}
}
private String classpathXmlLocation;
public void setClassPathXmlLocation(String classpathXmlLocation) {
this.classpathXmlLocation = classpathXmlLocation;
}
public String getClassPathXmlLocation() {
return this.classpathXmlLocation;
}
#Override
public void postProcessBeanDefinitionRegistry(
BeanDefinitionRegistry registry) throws BeansException {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
readXml(reader);
}
}
XML configuration:
<bean class="CustomXmlImporter">
<property name="classPathXmlLocation" value="a.xml" />
<property name="properties">
<map>
<entry key="key" value="a" />
</map>
</property>
</bean>
<bean class="CustomXmlImporter">
<property name="classPathXmlLocation" value="a.xml" />
<property name="properties">
<map>
<entry key="key" value="b" />
</map>
</property>
</bean>
this code loads the resources from classpath. I would think twice before doing something like that, anyway, you can use this as a starting point.

Resources