fallback for log4j.properties in spring configuration - spring

My config is as follows:
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:META-INF/spring/environments/${env}/log4j.properties</param-value>
</context-param>
What I want is, if
META-INF/spring/environments/${env}/log4j.properties
is not found, I want web.xml to load
META-INF/spring/environments/dev/log4j.properties
Is it possible?

If ${env} cannot be resolved and you get an exception like Could not resolve placeholder 'env' in string value "classpath:META-INF/spring/environments/${env}/log4j.properties" then it's easy by using the following format:
<param-value>classpath:META-INF/spring/environments/${env:dev}/log4j.properties</param-value>
but I don't think this is the actual issue you are facing.
I believe you already have ${env} properly resolved but the actual log4j.properties file is not found on that, otherwise properly resolved (META-INF/spring/environments/prod/log4j.properties for example), location.
I this case I don't think what you want to achieve is available out of the box, I believe you need some custom code. I'm saying this because the Spring code that deals with configuration files not to be found for Log4J configuration is not allowing any "default" fallback option. As you can see in Log4jWebConfigurer source code, if the file is not found an IllegalArgumentException is being ultimately thrown:
catch (FileNotFoundException ex) {
throw new IllegalArgumentException("Invalid 'log4jConfigLocation' parameter: " + ex.getMessage());
}
These being said, this is my try on a possible solution:
define your own Log4jConfigListener
package com.foo.bar;
import java.io.FileNotFoundException;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import org.springframework.util.Log4jConfigurer;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.Log4jConfigListener;
import org.springframework.web.util.Log4jWebConfigurer;
import org.springframework.web.util.ServletContextPropertyUtils;
import org.springframework.web.util.WebUtils;
public class MyLog4jConfigListener extends Log4jConfigListener {
public static final String DEFAULT_CONFIG_LOCATION_PARAM = "defaultLog4jConfigLocation";
#Override
public void contextInitialized(ServletContextEvent event) {
try {
super.contextInitialized(event);
} catch (IllegalArgumentException e) {
// the log4jConfigLocation file hasn't been found, try the default
if (!successfulDefaultLocationLookup(event)) {
// if the default parameter hasn't been set or there are issues, fallback to the initial behavior (throwing an IllegalArgumentException)
throw e;
}
}
}
private boolean successfulDefaultLocationLookup(ServletContextEvent event) {
ServletContext servletContext = event.getServletContext();
String location = servletContext.getInitParameter(DEFAULT_CONFIG_LOCATION_PARAM);
if (location != null) {
try {
// Resolve property placeholders before potentially resolving a real path.
location = ServletContextPropertyUtils.resolvePlaceholders(location, servletContext);
// Leave a URL (e.g. "classpath:" or "file:") as-is.
if (!ResourceUtils.isUrl(location)) {
// Consider a plain file path as relative to the web
// application root directory.
location = WebUtils.getRealPath(servletContext, location);
}
// Write log message to server log.
servletContext.log("Initializing log4j from default location [" + location + "]");
// Check whether refresh interval was specified.
String intervalString = servletContext.getInitParameter(Log4jWebConfigurer.REFRESH_INTERVAL_PARAM);
if (StringUtils.hasText(intervalString)) {
// Initialize with refresh interval, i.e. with log4j's watchdog thread,
// checking the file in the background.
try {
long refreshInterval = Long.parseLong(intervalString);
Log4jConfigurer.initLogging(location, refreshInterval);
}
catch (NumberFormatException ex) {
servletContext.log("Invalid 'log4jRefreshInterval' parameter for default log4j configuration file lookup: " + ex.getMessage());
return false;
}
}
else {
// Initialize without refresh check, i.e. without log4j's watchdog thread.
Log4jConfigurer.initLogging(location);
}
return true;
}
catch (FileNotFoundException ex) {
servletContext.log("Invalid 'log4jConfigLocation' parameter for default log4j configuration file lookup: " + ex.getMessage());
return false;
}
}
return false;
}
}
define the custom listener in web.xml
<listener>
<listener-class>com.foo.bar.MyLog4jConfigListener</listener-class>
</listener>
apart from your normal log4j context-param configuration, define the custom parameter for the default location
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:META-INF/spring/environments/${env}/log4j.properties</param-value>
</context-param>
<context-param>
<param-name>defaultLog4jConfigLocation</param-name>
<param-value>classpath:META-INF/spring/environments/dev/log4j.properties</param-value>
</context-param>

I do not know how to set it up to use a conditional IF statement but I think you could do it with wildcards. The Spring 4 docs explain the how, however due to formatting issues it is easier to read in the Spring 3 docs. But I will sum it up here for you.
You may use both the special classpath*: prefix and/or internal Ant-style regular expressions.
With the Ant-style pattern:
classpath:META-INF/spring/environments/**/log4j.properties
This search would find the first instance of META-INF/spring/environments/ and provide the next directory as a wildcard. This search will only return the first thing found.
With the classpath*: prefix:
classpath*:META-INF/spring/environments/**/log4j.properties
This search would return and merge the results of all the files that match this pattern.

Related

How to configure log4j for an xtext gradle build?

When I start the gradle build in one of my modules, it prints an error-message to std-error:
:m28_presentation_api:generateXtext
Error initializing JvmElement
That's not very helpful and I hope, that I can configure log4j to print more details about the exception.
I think this message is logged by JvmTypesBuilder.initializeSafely()
LOG.error("Error initializing JvmElement", e);
Versions:
I am using xtext 2.13: in the MANIFEST.MF file, I see the
log4j version: 1.2.15
gradle version: 4.6
xtext-gradle-plugin version: 1.0.21
According to the log4j V1 docs, it should be enough when I add a log4j.properties file to the classpath: so I just save this file in src/main/java.
But it seems that this is not used/found - or maybe I did something wrong in the configuration file:
log4j.rootLogger=stderr
log4j.appender.stderr=org.apache.log4j.ConsoleAppender
log4j.appender.stderr.layout=org.apache.log4j.PatternLayout
# Pattern to output the caller's file name and line number.
log4j.appender.stderr.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
When I now start the build, I'd expect a different log-output for the error-message, but it prints the same message as before. So obviously my log-config is not used for some reasons.
What am I missing?
Or can someone maybe point me to an example project?
it looks like LOG.error() does not print a stacktrace by default. maybe you can actively change your code e.g.
class MyDslJvmModelInferrer extends AbstractModelInferrer {
#Inject extension JvmTypesBuilder
private static Logger LOG = Logger.getLogger(MyDslJvmModelInferrer);
def dispatch void infer(Model element, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
acceptor.accept(element.toClass(element.name, [
try {
throw new IllegalArgumentException("mimimi")
} catch (Exception e) {
e.printStackTrace
throw e
}
]))
}
}
This is not really an answer, but a workaround (maybe helpful for others).
I gave up on the log4j configuration after wasting some hours and applied this workaround which took only some minutes and revealed the real problem.
What I've done, is to create my own JvmTypesBuilder, override the initializeSafely method and directly print the stack-trace to stderr:
import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder
import org.eclipse.emf.ecore.EObject
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1
import org.apache.log4j.Logger
class JvmTypesBuilderTm extends JvmTypesBuilder {
private static Logger LOG = Logger.getLogger(JvmTypesBuilder)
// TODO: nasty workaround because I cannot figure out how to configure the logging correctly
override <T extends EObject> initializeSafely(T targetElement, Procedure1<? super T> initializer) {
if(targetElement !== null && initializer !== null) {
try {
initializer.apply(targetElement);
} catch (Exception e) {
LOG.error("Error initializing JvmElement: "+targetElement.toString, e);
e.printStackTrace
}
}
return targetElement;
}
}
Then I just replaced all occurrences of the JvmTypesBuilder in my code with the new one. With the stacktrace, it was easy to find the real issue in my code.

Embedding an H2 Database within the WEB-INF Directory

I have an embedded H2 Database I'd like to put in the WEB-INF directory of a web application.
What is the proper way to refer to this in a JDBC url?
Ideally I'd like a solution that would work both for a WAR, and an expanded WAR (If possible).
Thank-you for your help!
FYI, I've tried the following:
jdbc:h2:/WEB-INF/data/myDB;CIPHER=AES
But this results in:
org.h2.jdbc.JdbcSQLException: A file path that is implicitly relative to the current working directory is not allowed in the database URL "jdbc:h2:/WEB-INF/data/myDB;CIPHER=AES". Use an absolute path, ~/name, ./name, or the baseDir setting instead. [90011-187]
Changing this to:
jdbc:h2:./WEB-INF/data/myDB;CIPHER=AES
Results in the following error, which clearly shows its trying to put my database in Tomcat's bin directory, rather than the true WEB-INF directory where I want it:
org.h2.jdbc.JdbcSQLException: Error while creating file "C:/Program Files/Apache Software Foundation/Tomcat 7.0/bin/WEB-INF" [90062-187]
I managed to make the embedded solution work without AES like this:
try {
Class.forName("org.h2.Driver");
Connection conn = DriverManager.getConnection(
"jdbc:h2:" + getServletContext().getRealPath("/") +
"/WEB-INF/data/myDB", "sa", "");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM INFORMATION_SCHEMA.TABLES");
while (rs.next()) {
}
rs.close();
stmt.close();
conn.close();
} catch(SQLException e) {
} catch(ClassNotFoundException e) {
} finally {
}
This was tested with H2 1.3.176 on Tomcat8. It should work with H2 1.4 and CIPHER=AES provided the embedded database is already inside the war file I guess.
The idea is the following: you need to get the absolute path, and that deployment path may not be the same depending on how you deployed the war file.
So we need to use the servlet context and request the real path. For this we use getServletContext().getRealPath("/") and append /WEB-INF/data/myDB to it as per your needs.
I did not test the CIPHER=AES part as I've never used it.
Update:
Getting a good reference to the servlet context is tricky. One could use a raw request, get the underlying session and then get to the servlet context.
But it would be good to have the embedded H2 database opened as soon as the application is deployed/started in Tomcat, and closed properly as soon as the application is stopped.
In order to perform that, the use of a listener is needed. Here's what I propose as an update to my previous answer. This time the solution is complete with AES CIPHER and it should be easy to plug into your code.
Suggestion: the listener java code can be easily modified to start a H2 tcp server as well, useful to enable the automatic mixed mode (embedded+tcp).
Add 3 lines to the file web.xml:
<listener>
<listener-class>com.mine.MyServletContextListener</listener-class>
</listener>
File MyServletContextListener.java:
package com.mine;
import javax.servlet.*;
import java.sql.*;
public class MyServletContextListener implements ServletContextListener {
Connection conn;
public void contextInitialized(ServletContextEvent sce) {
try {
Class.forName("org.h2.Driver");
conn = DriverManager.getConnection( "jdbc:h2:" + sce.getServletContext().getRealPath("/") + "/WEB-INF/data/myDB;CIPHER=AES", "sa", "aespassword dbpassword");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM INFORMATION_SCHEMA.TABLES");
while (rs.next()) {
}
rs.close();
stmt.close();
} catch(SQLException e) {
} catch(ClassNotFoundException e) {
} finally {
}
}
public void contextDestroyed(ServletContextEvent sce) {
try {
conn.close();
} catch(SQLException e) {
} finally {
}
}
}

Spring Annotated Controllers not working with Tomcated Embedded on Heroku

I have spring annotated controllers that work fine when I am using my WAR, but when I try to run embedded, locally and on Heroku, none of the annotated controllers are working. I have some pages setup using mvc:view-controller and those work, but none of the component-scan controllers work.
package com.myapp.launch;
import java.io.File;
import javax.servlet.ServletException;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
public class Main {
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String webappDirLocation = "src/main/webapp/";
Tomcat tomcat = new Tomcat();
//The port that we should run on can be set into an environment variable
//Look for that variable and default to 8080 if it isn't there.
String webPort = System.getenv("PORT");
if(webPort == null || webPort.isEmpty()) {
webPort = "8080";
}
tomcat.setPort(Integer.valueOf(webPort));
try {
tomcat.addWebapp("/", new File(webappDirLocation).getAbsolutePath());
} catch (ServletException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("configuring app with basedir: " + new File("./" + webappDirLocation).getAbsolutePath());
try {
tomcat.start();
} catch (LifecycleException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
tomcat.getServer().await();
}
}
Here is part of my spring config.
<mvc:view-controller path="/" view-name="home"/>
<mvc:view-controller path="/terms" view-name="terms"/>
<mvc:view-controller path="/privacy" view-name="privacy"/>
<context:component-scan base-package="com.myapp.controllers"/>
I found out that this was due to my controllers being groovy and those controllers were being compiled as part of a make step when I was running tomcat locally, but that same process was not being run when I launched tomcat embedded. After adding an execution goal to my gmaven plugin I was able to get this working without issue.
Since the classes were compiled by gmaven tomcat was able to pick them up.

File inside jar is not visible for spring

All
I created a jar file with the following MANIFEST.MF inside:
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.8.3
Created-By: 1.6.0_25-b06 (Sun Microsystems Inc.)
Main-Class: my.Main
Class-Path: . lib/spring-core-3.2.0.M2.jar lib/spring-beans-3.2.0.M2.jar
In its root there is a file called my.config which is referenced in my spring-context.xml like this:
<bean id="..." class="...">
<property name="resource" value="classpath:my.config" />
</bean>
If I run the jar, everything looks fine escept the loading of that specific file:
Caused by: java.io.FileNotFoundException: class path resource [my.config] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/D:/work/my.jar!/my.config
at org.springframework.util.ResourceUtils.getFile(ResourceUtils.java:205)
at org.springframework.core.io.AbstractFileResolvingResource.getFile(AbstractFileResolvingResource.java:52)
at eu.stepman.server.configuration.BeanConfigurationFactoryBean.getObject(BeanConfigurationFactoryBean.java:32)
at eu.stepman.server.configuration.BeanConfigurationFactoryBean.getObject(BeanConfigurationFactoryBean.java:1)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:142)
... 22 more
classes are loaded the from inside the jar
spring and other dependencies are loaded from separated jars
spring context is loaded (new ClassPathXmlApplicationContext("spring-context/applicationContext.xml"))
my.properties is loaded into PropertyPlaceholderConfigurer ("classpath:my.properties")
if I put my .config file outside the file system, and change the resource url to 'file:', everything seems to be fine...
Any tips?
If your spring-context.xml and my.config files are in different jars then you will need to use classpath*:my.config?
More info here
Also, make sure you are using resource.getInputStream() not resource.getFile() when loading from inside a jar file.
In the spring jar package, I use new ClassPathResource(filename).getFile(), which throws the exception:
cannot be resolved to absolute file path because it does not reside in the file system: jar
But using new ClassPathResource(filename).getInputStream() will solve this problem. The reason is that the configuration file in the jar does not exist in the operating system's file tree,so must use getInputStream().
I know this question has already been answered. However, for those using spring boot, this link helped me - https://smarterco.de/java-load-file-classpath-spring-boot/
However, the resourceLoader.getResource("classpath:file.txt").getFile(); was causing this problem and sbk's comment:
That's it. A java.io.File represents a file on the file system, in a
directory structure. The Jar is a java.io.File. But anything within
that file is beyond the reach of java.io.File. As far as java is
concerned, until it is uncompressed, a class in jar file is no
different than a word in a word document.
helped me understand why to use getInputStream() instead. It works for me now!
Thanks!
The error message is correct (if not very helpful): the file we're trying to load is not a file on the filesystem, but a chunk of bytes in a ZIP inside a ZIP.
Through experimentation (Java 11, Spring Boot 2.3.x), I found this to work without changing any config or even a wildcard:
var resource = ResourceUtils.getURL("classpath:some/resource/in/a/dependency");
new BufferedReader(
new InputStreamReader(resource.openStream())
).lines().forEach(System.out::println);
I had similar problem when using Tomcat6.x and none of the advices I found was helping.
At the end I deleted work folder (of Tomcat) and the problem gone.
I know it is illogical but for documentation purpose...
I was having an issue recursively loading resources in my Spring app, and found that the issue was I should be using resource.getInputStream. Here's an example showing how to recursively read in all files in config/myfiles that are json files.
Example.java
private String myFilesResourceUrl = "config/myfiles/**/";
private String myFilesResourceExtension = "json";
ResourceLoader rl = new ResourceLoader();
// Recursively get resources that match.
// Big note: If you decide to iterate over these,
// use resource.GetResourceAsStream to load the contents
// or use the `readFileResource` of the ResourceLoader class.
Resource[] resources = rl.getResourcesInResourceFolder(myFilesResourceUrl, myFilesResourceExtension);
// Recursively get resource and their contents that match.
// This loads all the files into memory, so maybe use the same approach
// as this method, if need be.
Map<Resource,String> contents = rl.getResourceContentsInResourceFolder(myFilesResourceUrl, myFilesResourceExtension);
ResourceLoader.java
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.StreamUtils;
public class ResourceLoader {
public Resource[] getResourcesInResourceFolder(String folder, String extension) {
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
String resourceUrl = folder + "/*." + extension;
Resource[] resources = resolver.getResources(resourceUrl);
return resources;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String readResource(Resource resource) throws IOException {
try (InputStream stream = resource.getInputStream()) {
return StreamUtils.copyToString(stream, Charset.defaultCharset());
}
}
public Map<Resource, String> getResourceContentsInResourceFolder(
String folder, String extension) {
Resource[] resources = getResourcesInResourceFolder(folder, extension);
HashMap<Resource, String> result = new HashMap<>();
for (var resource : resources) {
try {
String contents = readResource(resource);
result.put(resource, contents);
} catch (IOException e) {
throw new RuntimeException("Could not load resource=" + resource + ", e=" + e);
}
}
return result;
}
}
For kotlin users, I solved it like this:
val url = ResourceUtils.getURL("classpath:$fileName")
val response = url.openStream().bufferedReader().readText()
The answer by #sbk is the way we should do it in spring-boot environment (apart from #Value("${classpath*:})), in my opinion. But in my scenario it was not working if the execute from standalone jar..may be I did something wrong.
But this can be another way of doing this,
InputStream is = this.getClass().getClassLoader().getResourceAsStream(<relative path of the resource from resource directory>);
I was having an issue more complex because I have more than one file with same name, one is in the main Spring Boot jar and others are in jars inside main fat jar.
My solution was getting all the resources with same name and after that get the one I needed filtering by package name.
To get all the files:
ResourceLoader resourceLoader = new FileSystemResourceLoader();
final Enumeration<URL> systemResources = resourceLoader.getClassLoader().getResources(fileNameWithoutExt + FILE_EXT);
In Spring boot 1.5.22.RELEASE Jar packaging this worked for me:
InputStream resource = new ClassPathResource("example.pdf").getInputStream();
"example.pdf" is in src/main/resources.
And then to read it as byte[]
FileCopyUtils.copyToByteArray(resource);
I had the same issue, ended up using the much more convenient Guava Resources:
Resources.getResource("my.file")
While this is a very old thread, but I also faced the same issue while adding FCM in a Spring Boot Application.
In development, the file was getting opened and no errors but when I deployed the application to AWS Elastic beanstalk , the error of FileNotFoundException was getting thrown and FCM was not working.
So here's my solution to get it working on both development env and jar deployment production.
I have a Component class FCMService which has a method as follows:
#PostConstruct
public void initialize() {
log.info("Starting FCM Service");
InputStream inputStream;
try {
ClassPathResource resource = new ClassPathResource("classpath:fcm/my_project_firebase_config.json");
URL url = null;
try {
url = resource.getURL();
} catch (IOException e) {
}
if (url != null) {
inputStream = url.openStream();
} else {
File file = ResourceUtils.getFile("classpath:fcm/my_project_firebase_config.json");
inputStream = new FileInputStream(file);
}
FirebaseOptions options = FirebaseOptions.builder().setCredentials(GoogleCredentials.fromStream(inputStream))
.build();
FirebaseApp.initializeApp(options);
log.info("FCM Service started");
} catch (IOException e) {
log.error("Error starting FCM Service");
e.printStackTrace();
}
}
Hope this helps someone looking for a quick fix with implementing FCM.
Can be handled like:
var serviceAccount = ClassLoader.getSystemResourceAsStream(FB_CONFIG_FILE_NAME);
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.build();
Where FB_CONFIG_FILE_NAME is name of file in your 'resources' folder.

Spring default objects creation?

does Spring read any configuration file from where it comes to know that some default out of the box implementations are to be created (HandlerMapping, ViewResolver etc)?
Yep, DispatcherServlet.properties in the org.springframework.web.servlet pacakge is the what you looking for.
Related snippet from the DispatcherServlet sources:
/**
* Name of the class path resource (relative to the DispatcherServlet class)
* that defines DispatcherServlet's default strategy names.
*/
private static final String DEFAULT_STRATEGIES_PATH ="DispatcherServlet.properties";
....
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
}
}

Resources