Externalized message.properties file is not picking up in Tomcat - spring

According to my requirement, I need to externalize the message.properties file (Keep outside from war file) and in same time it should be automatically re loadable on update.
So I achieved both by following code and its working fine with Jetty Server. But in when I use Tomcat Server that externalized property file is not picking up by the system, instead its uses only the file inside the war.
public final class Messages
{
public static final String BUNDLE_NAME = "com.sample.project.core.ui.resources.messages";
// private static ResourceBundle resourceBundle = ResourceBundle.getBundle(BUNDLE_NAME);
private static ResourceBundle resourceBundle;
private static final Logger LOGGER = Logger.getLogger(Messages.class);
private static ReloadableResourceBundleMessageSource messageSource;
static
{
try
{
FileInputStream fis =
new FileInputStream(System.getProperty("resources.messages.file.path"));
resourceBundle = new PropertyResourceBundle(fis);
}
catch (FileNotFoundException e)
{
LOGGER.error("messages.properties file not found: " + e);
resourceBundle = ResourceBundle.getBundle(BUNDLE_NAME);
}
catch (Exception e)
{
LOGGER.error("messages.properties file reading failed: " + e);
resourceBundle = ResourceBundle.getBundle(BUNDLE_NAME);
}
}
private Messages()
{
}
/**
* <p>
* setter methos to ReloadableResourceBundleMessageSource object.
* </p>
*
*
* #param inMessageSource
* set reloadable resources bundle
**/
public static void setMessageSource(final ReloadableResourceBundleMessageSource inMessageSource)
{
Messages.messageSource = inMessageSource;
Messages.messageSource.setBasename(System.getProperty("resources.messages.file.path"));
}
/**
* <p>
* Resolve a message by a key and argument replacements.
* </p>
*
* #see MessageFormat#format(String, Object...)
* #param key
* the message to look up
* #param arguments
* optional message arguments
* #return the resolved message
**/
public static String getMessage(final String key, final Object... arguments)
{
try
{
if (messageSource != null)
{
return messageSource.getMessage(key, arguments, Locale.getDefault());
}
else
{
if (arguments != null)
return MessageFormat.format(resourceBundle.getString(key), arguments);
return resourceBundle.getString(key);
}
}
catch (NoSuchMessageException e)
{
LOGGER.error("Message key not found: " + key);
return '!' + key + '!';
}
catch (MissingResourceException e)
{
LOGGER.error("Message key not found: " + key);
return '!' + key + '!';
}
}
}
(Here file path I'm passing as a VM argument using "resources.messages.file.path" key)
First I thought it was a problem with accessing the file system and tried many ways. Then I heard about catalina.policy file and I added some lines like this..
grant codeBase "file:${catalina.base}/webapps/sample.war/-" {
permission java.security.AllPermission;
permission java.io.FilePermission "file:${catalina.base}${file.separator}webapps${file.separator}messages.properties", "read, write";
permission java.util.PropertyPermission "resources.messages.file.path", "read";
}
But non of them gave me luck. I'm desperate. Any idea what this issue is? Please help me. Thank you in advance. (Tested on Tomcat6)

Finally I found where I screwed up.
This setter method of messageSource should not be static and I removed static access of messageSource
messageSource = inMessageSource;
messageSource.setBasename(System.getProperty("resources.messages.file.path"));
Now code is working fine. And no need of that permission entry in catalina.policy file. Thanks to everyone who helped me.

Related

#Value can not generate value properly, getting null

When I run the code the external configuration in the application.properties file does not get populated into the variable within the DataBucketUtil. I'm sure I'm doing something stupid,but I can not find out wheres the problem.
public class DataBucketUtil {
private static final Logger logger = LoggerFactory.getLogger(DataBucketUtil.class);
#Value("${gcp.config.file}")
private String gcpConfigFile;
#Value("${gcp.project.id}")
private String gcpProjectId;
#Value("${gcp.bucket.id}")
private String gcpBucketId;
#Value("${gcp.directory.name}")
private String gcpDirectoryName;
/**
* Upload file to GCS
*
* #param multipartFile-
* #param fileName-
* #param contentType-
* #return -
*/
public FileDto uploadFile(MultipartFile multipartFile, String fileName, String contentType) {
try {
logger.debug("Start file uploading process on GCS");
byte[] fileData = FileUtils.readFileToByteArray(convertFile(multipartFile));
InputStream inputStream = new ClassPathResource(gcpConfigFile).getInputStream();
StorageOptions options = StorageOptions.newBuilder().setProjectId(gcpProjectId)
.setCredentials(GoogleCredentials.fromStream(inputStream)).build();
Storage storage = options.getService();
Bucket bucket = storage.get(gcpBucketId, Storage.BucketGetOption.fields());
RandomString id = new RandomString(6, ThreadLocalRandom.current());
Blob blob = bucket.create(gcpDirectoryName + "/"
+ fileName + "-" + id.nextString() + checkFileExtension(fileName),
fileData, contentType);
if (blob != null) {
logger.debug("File successfully uploaded to GCS");
return new FileDto(blob.getName(), blob.getMediaLink());
}
} catch (IOException e) {
logger.error("An error occurred while uploading data. Exception: ", e);
throw new RuntimeException("An error occurred while uploading data to GCS");
}
throw new RuntimeException("An error occurred while uploading data to GCS");
}
My application properties is given below:
gcp.config.file=gcp-config/gcs-prod-ho-finance.json
gcp.project.id=brac-main gcp.bucket.id=prod-ho-finance
gcp.dir.name=gs://prod-ho-finance
It is not entirely clear from your code snippet but my guess would be that your DataBucketUtil is not instantiated as a Bean and therefore the #Value annotated fields are not populated. See here for more details about the #Value annotation.
You could transform your class to a service or component with the #Component or #Service annotation and then autowire it to where you need it. See here for more information about beans.
Please add Annotations. I hope it work.
#EnableConfigurationProperties
#Component
public class DataBucketUtil {
private static final Logger logger = LoggerFactory.getLogger(DataBucketUtil.class);
#Value("${gcp.config.file}")
private String gcpConfigFile;
#Value("${gcp.project.id}")
private String gcpProjectId;
#Value("${gcp.bucket.id}")
private String gcpBucketId;
#Value("${gcp.directory.name}")
private String gcpDirectoryName;
/** ............ **/
}

Spring Integration MimeMessage gmail Folder is not Open exception

Whenever I try to read content of a MimeMessage I get the "java.lang.IllegalStateException: Folder is not Open" exception.
I have a very simple service handling the received message:
#Service
public class ReceiveMailService {
private final Logger log = LoggerFactory.getLogger(ReceiveMailService.class);
public void handleReceivedMail(MimeMessage receivedMessage) {
try {
log.debug("{}", receivedMessage.getContent());
MimeMessageParser mimeMessageParser = new MimeMessageParser(receivedMessage).parse(); // it breaks here
doMyStuff(mimeMessageParser);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
Here's my configuration class:
#Configuration
#EnableIntegration
public class MailReceiverConfiguration {
private static final Logger log = LoggerFactory.getLogger(MailReceiverConfiguration.class);
#Value("${spring.mail.pop3.host}")
private String host;
#Value("${spring.mail.pop3.port}")
private Integer port;
#Value("${spring.mail.username}")
private String username;
#Value("${spring.mail.password}")
private String password;
private final ReceiveMailService receiveMailService;
public MailReceiverConfiguration(ReceiveMailService receiveMailService) {
this.receiveMailService = receiveMailService;
}
#Bean
public IntegrationFlow mailListener() {
return IntegrationFlows
.from(Mail
.pop3InboundAdapter(host, port, username, password)
.javaMailProperties(p -> {
p.put("mail.debug", "false");
p.put("mail.pop3.socketFactory.fallback", "false");
p.put("mail.pop3.port", port);
p.put("mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
p.put("mail.pop3.socketFactory.port", port);
})
.maxFetchSize(10)
.shouldDeleteMessages(false),
e -> e.poller(Pollers.fixedRate(5000).maxMessagesPerPoll(10))
)
.handle(message -> receiveMailService.handleReceivedMail((MimeMessage) message.getPayload()))
.get();
}
}
I have ran out of ideas why this wouldn't work.
See this option for use-cases like yours:
/**
* When configured to {#code false}, the folder is not closed automatically after a fetch.
* It is the target application's responsibility to close it using the
* {#link org.springframework.integration.IntegrationMessageHeaderAccessor#CLOSEABLE_RESOURCE} header
* from the message produced by this channel adapter.
* #param autoCloseFolder set to {#code false} to keep folder opened.
* #return the spec.
* #since 5.2
* #see AbstractMailReceiver#setAutoCloseFolder(boolean)
*/
public S autoCloseFolder(boolean autoCloseFolder) {
The docs is here: https://docs.spring.io/spring-integration/docs/current/reference/html/mail.html#mail-inbound
Starting with version 5.2, the autoCloseFolder option is provided on the mail receiver. Setting it to false doesn’t close the folder automatically after a fetch, but instead an IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE header (see MessageHeaderAccessor API for more information) is populated into every message to producer from the channel adapter.

Thymeleaf - check if a template exists

I am searching a way to check if a template exists before returning it in a view with Thymeleaf and Spring.
In my controller i was trying to do something like this:
String finalTemplate = template.getPrefix() + "/" + templateName;
try {
return finalTemplate;
} catch(TemplateInputException e) {
logger.debug("Sono qua");
return templateName;
}
but the exception is not catch...
The way you are handling exception here will not work for template exceptions.
Check below threads regarding this in issues section github.
https://github.com/thymeleaf/thymeleaf-spring/issues/94
https://github.com/thymeleaf/thymeleaf-spring/issues/81
StringTemplateResource from the comment above seems to return true in every case for thymeleaf 3.0. Here is a method to do this for classpath included templates:
public static String baseDir = "templates";
/**
* Check if a template exists in subfolder templates.
*
* #param templateName relative name of template below the basedir.
* #return true if exists, otherwise false
*/
public static boolean templateExists(final String templateName)
{
final ClassLoaderTemplateResource iTemplateResource =
new ClassLoaderTemplateResource(baseDir + "/" + templateName, "UTF8");
return iTemplateResource.exists();
}
Testcase:
#Test
public void testNonExisting()
{
assertFalse(templateExists("foo"));
}
#Test
public void testExisting()
{
assertTrue(templateExists("foo.txt"));
}
Assuming a classpath resource templates/foo.txt exists.

Spring Boot: Override convention used to find application.properties config files

I was looking at the spring-boot documentation located here
Specifically the section regarding the order in which the properties are considered:
More specifically:
Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants)
Let me first mention that I am not having any issues loading profile specific configurations using this approach(provided that the files are located in classpath:/ or classpath:/config.
However, what I am hoping to do is implement a convention like the following:
classpath:/default/application.properties
classpath:/{profile}/application.properties
Furthermore I'd like to achieve this configuration without making use of the spring.config.location property. I'm pretty new to Spring Boot so I'm looking for some hints as how to how I would implement this convention. Based on my research It seems that this might be achievable by adding a custom ConfigFileApplicationListener. Please let me know if that is a sensible starting point or any other ideas that might be better.
Update:
It seems that if I could programmatically build out the spring.config.location list of properties I could pass in locations such as classpath:/default, classpath:{profile}. based on the spring.profiles.active environment variable. The following ConfigFileApplicationListener seems like its the one I want to call:
public void setSearchLocations(String locations)
However, I'm not sure where in the lifecycle I would make such a call.
So here is what I managed to come up with, not sure if I'll even go with this solution but I figured I'll offer it up in case there is any helpful feedback.
So I resorted to trying to set the call the setSearchLocations(String locations) method on the ConfigFileApplicationListener after it has been added to the SpringApplication but before its triggered. I did this by adding a new listener that also implements Ordered and made sure it ran before ConfigFileApplicationListener. This seems to do what I want but I'm still thinking there is a more elegant approach. I especially dont like having to iterate over the Listeners.
public class LocationsSettingConfigFileApplicationListener implements
ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
/**
* this should run before ConfigFileApplicationListener so it can set its
* state accordingly
*/
#Override
public int getOrder() {
return ConfigFileApplicationListener.DEFAULT_ORDER - 1;
}
#Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
SpringApplication app = event.getSpringApplication();
ConfigurableEnvironment env = event.getEnvironment();
for (ApplicationListener<?> listener : app.getListeners()) {
if (listener instanceof ConfigFileApplicationListener) {
ConfigFileApplicationListener cfal = (ConfigFileApplicationListener) listener;
//getSearchLocations omitted
cfal.setSearchLocations(getSearchLocations(env));
}
}
}
A solution that doesn't require writing a new class:
public static void main(String[] args) {
SpringApplication app = new SpringApplication();
app.getListeners().stream()
.filter(listener -> listener instanceof ConfigFileApplicationListener)
.forEach(configListener -> {
((ConfigFileApplicationListener) configListener).setSearchLocations(mySearchLocations);
((ConfigFileApplicationListener) configListener).setSearchNames(mySearchNames);
});
app.setSources(singleton(MyClassName.class));
app.run(args);
}
We did something similar with an EnvironmentPostProcessor to achieve the following naming convention:
System properties
environment variables
"random" (not used, but we kept that default PropertySource)
file:${foo.home}/foo-<profile>.properties
classpath*:<appName-profile>.properties
classpath*:application-profile.properties
classpath*:<appName>.properties
classpath*:application.properties
classpath*:meta.properties
Some applications do not have their own <appName>; those that do call setApplicationName in the main class's static initializer to use those two additional files.
The hacky part here is that we do not exclude the default ConfigFileApplicationListener, but undo it by removing PropertySource ConfigFileApplicationListener.APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME.
File FooPropertiesEnvPostProcessor.java
package example.foo.utils.spring;
import static org.springframework.core.env.AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME;
import java.io.IOException;
import java.util.List;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.env.PropertySourcesLoader;
import org.springframework.boot.logging.LoggingApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.io.support.SpringFactoriesLoader;
/**
* Configures environment properties according to the FOO conventions.
*/
public class FooPropertiesEnvPostProcessor implements EnvironmentPostProcessor, Ordered {
/**
* Order before LoggingApplicationListener and before
* AutowiredAnnotationBeanPostProcessor. The position relative to
* ConfigFileApplicationListener (which we want to override) should not
* matter: If it runs before this, we remove its PropertySource; otherwise,
* its PropertySource remains but should do no harm as it is added at the
* end.
*/
public static final int ORDER
= Math.min(LoggingApplicationListener.DEFAULT_ORDER, new AutowiredAnnotationBeanPostProcessor().getOrder()) - 1;
static {
System.setProperty(AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME,
System.getProperty(AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, "production"));
}
public FooPropertiesEnvPostProcessor() {
}
/**
* Property key used as the application (sub-project) specific part in
* properties file names.
* <p>
* <strong>Note:</strong> Direct access to this property key is meant for
* tests which set the property in an annotation (e.g.
* {#link IntegrationTest}). However, SpringBootApplications which need to
* set this system property before Spring initialization should call
* {#link #setApplicationName(String) setApplicationName} instead.
* </p>
*/
public static final String APP_KEY = "foo.config.name";
/**
* Sets the application name used to find property files (using
* {#link FooPropertiesEnvPostProcessor}).
*
* #param appName
* the application name
*/
public static void setApplicationName(String appName) {
System.setProperty(APP_KEY, appName);
}
/**
* Replacement for logging, which is not yet initialized during
* postProcessEnvironment.
*/
static void log(String format, Object... args) {
System.out.println(String.format(format, args));
}
static void debug(PropertyResolver env, String format, Object... args) {
String level = env.getProperty("logging.level." + FooPropertiesEnvPostProcessor.class.getName());
if ("trace".equalsIgnoreCase(level) || "debug".equalsIgnoreCase(level)) {
log(format, args);
}
}
static void trace(PropertyResolver env, String format, Object... args) {
String level = env.getProperty("logging.level." + FooPropertiesEnvPostProcessor.class.getName());
if ("trace".equalsIgnoreCase(level)) {
log(format, args);
}
}
#Override
public int getOrder() {
return ORDER;
}
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addProperties(environment.getPropertySources(), application.getResourceLoader(), environment);
}
public static void addProperties(MutablePropertySources propSources, ResourceLoader resLoader, ConfigurableEnvironment propRes) {
trace(propRes, "FooPropertiesEnvPostProcessor.addProperties(..)");
List<PropertySourceLoader> psls = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
PropertySourcesLoader.class.getClassLoader());
// ResourcePatternUtils does not accept null yet
// (https://jira.spring.io/browse/SPR-14500)
ResourcePatternResolver rpr = resLoader != null ? ResourcePatternUtils.getResourcePatternResolver(resLoader)
: new PathMatchingResourcePatternResolver();
final String suffix = ".properties"; // SonarQube made me declare this
String[] profiles = propRes.getActiveProfiles();
if (profiles.length == 0) {
profiles = new String[] { System.getProperty(DEFAULT_PROFILES_PROPERTY_NAME) };
}
// ConfigFileApplicationListener adds PropertySource "applicationConfigurationProperties" consisting of
// - "applicationConfig: [classpath:/${spring.config.name}-<profile>.properties]"
// - "applicationConfig: [classpath:/${spring.config.name}.properties]"
// Since we want the profile to have higher priority than the app name, we cannot just set
// "spring.config.name" to the app name, use ConfigFileApplicationListener, and add
// "application-<profile>.properties" and "application.properties".
// Instead, remove ConfigFileApplicationListener:
PropertySource<?> removedPropSource = propSources.remove(ConfigFileApplicationListener.APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME);
trace(propRes, "removed %s from %s", removedPropSource, propSources);
// add meta.properties at last position, then others before the previously added. => resulting order:
// - { systemProperties
// - systemEnvironment
// - random } - already added automatically elsewhere
// - file:${foo.home}/foo-<profile>.properties
// - classpath:<appName>-<profile>.properties
// - classpath:application-<profile>.properties
// - classpath:<appName>.properties
// - classpath:application.properties
// - classpath:meta.properties
// By adding ${foo.home}/... (chronlogically) last, the property can be set in the previously added resources.
boolean defaultAppName = "application".equals(propRes.resolveRequiredPlaceholders("${" + APP_KEY + ":application}"));
String psn = null;
psn = addProperties(propSources, propRes, rpr, psls, true, psn, propRes.resolveRequiredPlaceholders("classpath*:meta" + suffix));
psn = addProperties(propSources, propRes, rpr, psls, true, psn, propRes.resolveRequiredPlaceholders("classpath*:application" + suffix));
if (!defaultAppName) {
psn = addProperties(propSources, propRes, rpr, psls, false,
psn, propRes.resolveRequiredPlaceholders("classpath*:${" + APP_KEY + ":application}" + suffix));
}
for (String profile : profiles) {
psn = addProperties(propSources, propRes, rpr, psls, false, psn,
propRes.resolveRequiredPlaceholders("classpath*:application-" + profile + suffix));
}
if (!defaultAppName) {
for (String profile : profiles) {
psn = addProperties(propSources, propRes, rpr, psls, false,
psn, propRes.resolveRequiredPlaceholders("classpath*:${" + APP_KEY + ":application}-" + profile + suffix));
}
}
for (String profile : profiles) {
psn = addProperties(propSources, propRes, rpr, psls, false,
psn, propRes.resolveRequiredPlaceholders("file:${foo.home:.}/foo-" + profile + suffix));
}
Stream<PropertySource<?>> propSourcesStream = StreamSupport.stream(Spliterators.spliteratorUnknownSize(propSources.iterator(), 0), false);
debug(propRes, "Property sources: %s%n", propSourcesStream.map(PropertySource::getName).collect(Collectors.joining(", ")));
}
/**
* Adds a resource given by location string to the given PropertySources, if
* it exists.
*
* #param propSources
* the property sources to modify
* #param successorName
* the name of the (already added) successor resource, i.e. the
* resource before which the new one should be added; if null,
* add as last resource
* #param location
* the location of the resource to add
* #return the name of the newly added resource, or {#code successorName} if
* not added
*/
private static String addProperties(MutablePropertySources propSources, PropertyResolver propRes, ResourcePatternResolver resLoader,
List<PropertySourceLoader> propLoaders, boolean required, String successorName, String location) {
Resource[] resources;
try {
resources = resLoader.getResources(location);
} catch (IOException e) {
throw new IllegalStateException("failed to load property source " + location + ": " + e, e);
}
if (resources.length == 0) {
debug(propRes, "%s property resource not found: %s", required ? "required" : "optional", location);
if (required) {
throw new IllegalStateException("required property source " + location + " not found");
} else {
return successorName;
}
}
String newSuccessorName = successorName;
for (Resource resource : resources) {
boolean exists = resource.exists();
debug(propRes, "%s property resource %sfound: %s%s", required ? "required" : "optional", exists ? "" : "not ", location,
uriDescription(resource, propRes));
if (!required && !exists) {
continue;
}
boolean loaded = false;
for (PropertySourceLoader propLoader : propLoaders) {
if (canLoadFileExtension(propLoader, resource)) {
newSuccessorName = addResource(propSources, propRes, resource, propLoader, newSuccessorName);
loaded = true;
break;
}
}
if (!loaded && required) {
throw new IllegalStateException("No PropertySourceLoader found to load " + resource);
}
}
return newSuccessorName;
}
private static String addResource(MutablePropertySources propSources, PropertyResolver propRes, Resource resource,
PropertySourceLoader propLoader, String successorName) {
try {
PropertySource<?> propSource = propLoader.load(resource.getDescription(), resource, null);
if (propSource == null) {
// e.g. a properties file with everything commented;
// org.springframework.boot.env.PropertiesPropertySourceLoader
// converts empty to null
return successorName;
}
if (successorName == null) {
propSources.addLast(propSource);
} else if (successorName.equals(propSource.getName())) {
// happens if APP_KEY is not set, so that
// "${APP_KEY:application}" == "application"
trace(propRes, "skipping duplicate resource %s", successorName);
} else {
propSources.addBefore(successorName, propSource);
}
return propSource.getName();
} catch (IOException e) {
throw new IllegalStateException("Unable to load configuration file " + resource + ": " + e, e);
}
}
/**
* Stolen from {#link PropertySourcesLoader}
*/
private static boolean canLoadFileExtension(PropertySourceLoader loader, Resource resource) {
String filename = resource.getFilename().toLowerCase();
for (String extension : loader.getFileExtensions()) {
if (filename.endsWith("." + extension.toLowerCase())) {
return true;
}
}
return false;
}
private static String uriDescription(Resource resource, PropertyResolver propRes) {
try {
return resource.exists() ? (" in " + resource.getURI()) : "";
} catch (IOException e) {
trace(propRes, "getURI: %s", e);
return "";
}
}
}
File META-INF/spring.factories
org.springframework.boot.env.EnvironmentPostProcessor = example.foo.utils.spring.FooPropertiesEnvPostProcessor
To get the same properties in tests, they have #ContextConfiguration(..., initializers = TestAppContextInitializer.class).
TestAppContextInitializer implements ApplicationContextInitializer<GenericApplicationContext> and calls
FooPropertiesEnvPostProcessor.addProperties in its initialize method.
Unfortunately, the EnvironmentPostProcessor seems to be missing Spring Shell by default, too. In our case (since only a tiny
part of the application uses Spring Shell), it was sufficient to restrict the <context:component-scan base-package=.../>
scope in META-INF/spring/spring-shell-plugin.xml to contain only stuff which does not need any properties set by the EnvironmentPostProcessor.
# override from outer config , eg. java -jar --spring.profiles.active=your config
spring.profiles.active=dev
spring.config.location=classpath:/${spring.profiles.active}/application.properties

log4j2 configuration from file

I am working on an existing system that uses log4j, I want to update to log4j2.
There is a custom spring bean that loads the configuration from a file. I need to keep this approach. I cannot use the "log4j.configurationFile" system property.
We have a properties file where the path to the current log4j.xml is specified ( NFS share )
The spring bean has this code ...
public class Log4jConfigurationBean implements ResourceLoaderAware,
InitializingBean {
private ResourceLoader resourceLoader;
private boolean enabled;
private String location;
/**
* Default, no argument constructor.
*/
public Log4jConfigurationBean() {
enabled = true;
}
/**
* Sets whether or not this bean should load an external configuration
* defined by {#link #setLocation(Resource)}. If <code>false</code>, this
* bean does nothing.
*
* <p>
* Default value is <code>true</code>.
* </p>
*
* #param enabled
* <code>false</code> causes this bean to do nothing
*/
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
/**
* Sets the location of the external log4j configuration (xml or properties)
* to be loaded.
*
* #param location
* the location of the external configuration to be loaded.
* #throws IllegalStateException
* if there is a problem resolving the location resource
* #throws NullPointerException
* if <code>resource</code> is <code>null</code>
*/
public void setLocation(final String location) {
this.location = StringUtils.trimToNull(location);
}
#Override
public void setResourceLoader(final ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* #throws IllegalStateException
* if enabled and no location has be set, or if the external
* configuration is neither xml or properties.
*/
#Override
public void afterPropertiesSet() throws Exception {
URL configURL = null;
if (null != location) {
try {
final Resource resource = resourceLoader.getResource(location);
if (null != resource) {
configURL = resource.getURL();
}
} catch (IOException e) {
throw new IllegalArgumentException(
"Could not resolve configuration location due to error: ",
e);
}
}
if (enabled && null == configURL) {
throw new IllegalStateException(
"Log4j configuration enabled, but configuration location is not set.");
}
if (enabled) {
if (configURL.getFile().toLowerCase().endsWith(".xml")) {
DOMConfigurator.configure(configURL);
} else if (configURL.getFile().toLowerCase()
.endsWith(".properties")) {
PropertyConfigurator.configure(configURL);
} else {
throw new IllegalStateException(
"Configuration must be properties or xml: "
+ configURL.getFile());
}
}
}
}
In log4j2 there is no PropertyConfigurator.
How can I load the log4j2.xml file the same way.
The file path to the log4j2.xml file is specified in a spring property file.
The goal is to have the war files contain a log4j2.xml file in the classpath. This will be used when developing on your local box.
When the web apps are deployed to a qa environment, there is a property file containing the following key/value pair...
# Should an external file be used for log4j configuration
log4j.enabled=true
log4j.location=file:/paht to log4j2.xml
A spring bean is using these values to decide if an external log4j2.xml file should be used instead of the one on the classpath.
I tried with a spring bean like this... the code is executed, but it still uses the configuration file on the classpath.
public class Log4j2ConfigurationBean implements ResourceLoaderAware, InitializingBean {
private static final Logger log = LoggerFactory.getLogger(Log4j2ConfigurationBean.class);
private ResourceLoader resourceLoader;
private boolean enabled;
private String location;
/**
* Default, no argument constructor.
*/
public Log4j2ConfigurationBean() {
enabled = true;
}
/**
* Sets whether or not this bean should load an external configuration defined by {#link #setLocation(Resource)}. If <code>false</code>, this bean does nothing.
*
* <p>
* Default value is <code>true</code>.
* </p>
*
* #param enabled
* <code>false</code> causes this bean to do nothing
*/
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
/**
* Sets the location of the external log4j configuration (xml or properties) to be loaded.
*
* #param location
* the location of the external configuration to be loaded.
* #throws IllegalStateException
* if there is a problem resolving the location resource
* #throws NullPointerException
* if <code>resource</code> is <code>null</code>
*/
public void setLocation(final String location) {
this.location = StringUtils.trimToNull(location);
}
#Override
public void setResourceLoader(final ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* #throws IllegalStateException
* if enabled and no location has be set, or if the external configuration is neither xml or properties.
*/
#Override
public void afterPropertiesSet() throws Exception {
URL configURL = null;
if (enabled) {
if (StringUtils.isBlank(location)) {
throw new IllegalStateException("Log4j2 configuration enabled, but configuration location is not set.");
}
try {
System.out.println(this.getClass().getName() + " : Loading log4j2 configuration with " + location);
final Resource resource = resourceLoader.getResource(location);
if (null != resource) {
configURL = resource.getURL();
}
} catch (IOException e) {
throw new IllegalArgumentException("Could not resolve configuration location due to error: ", e);
}
if (configURL.getFile().toLowerCase().endsWith(".xml")) {
try {
System.setProperty("Log4jContextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
System.setProperty("AsyncLogger.RingBufferSize", "8192");
ConfigurationFactory configurationFactory = XmlConfigurationFactory.getInstance();
ConfigurationSource configurationSource = new ConfigurationSource(configURL.openStream(), configURL);
Configuration configuration = configurationFactory.getConfiguration(configurationSource);
configuration.start();
log.info("Log4j2 configured with {}", location);
log.info("System property Log4jContextSelector set to {}", System.getProperty("Log4jContextSelector"));
log.info("System property AsyncLogger.RingBufferSize set to {}", System.getProperty("AsyncLogger.RingBufferSize"));
} catch (Exception e) {
System.out.println(this.getClass().getName() + " : Could not initialize log4j2 with resource " + location);
System.out.println(e.getStackTrace());
}
} else {
throw new IllegalStateException("Configuration must be xml: " + configURL.getFile());
}
} else {
System.out.println(this.getClass().getName() + " : External log4j2 configuration not configured.");
}
}
}
Thanks.
Check out the How do I configure log4j2 in code without a configuration file? section here - http://logging.apache.org/log4j/2.x/faq.html

Resources