Using ResourceUtils.getFile to read a file from classpath in Heroku environment - java

I'm running a Spring Boot application in Heroku, using Maven to manage the build lifecycle.
During the initialisation phase of my application, I want to read a file packaged into my JAR file.
To manage to get the content of the file I'm using the Spring utility class ResourceUtils, and I'm expressing the path of the file using the special prefix classpath:.
The code I'm using looks like this:
String pathToMyFile = "classpath:com/myapp/myFile.test"
List<String> fileLines = Files.readLines(ResourceUtils.getFile(pathToMyFile), IOConstants.DEFAULT_CHARSET_TYPE);
This code works as expected when I execute the application in my local machine.
But when I push my application to Heroku I'm getting the following error:
Caused by: java.io.FileNotFoundException: class path resource [com/myapp/myFile.test]
cannot be resolved to absolute file path because it does not reside in the
file system: jar:file:/app/target/myapp.jar!/com/myapp/myFile.test
I've run a heroku run bash and I've checked that the file is just where it should be (inside the jar).
Moreover, according to the error trace, Spring locates the file, because it transform the path from classpath:com/myapp/myFile.test to jar:file:/app/target/myapp.jar!/com/myapp/myFile.test

I suspect that when you are running locally, it is picking up the file on the classpath from an exploded JAR file (i.e. as a regular file on the filesystem).
On Heroku, it is in the JAR file, which means it is not a regular file, and must be read as an input stream, which might look like this:
ClassLoader cl = this.getClass().getClassLoader();
InputStream inputStream = cl.getResourceAsStream(pathToMyFile);
Then you might use a BufferedReader to read the lines. But maybe ResourceUtils has a better method.
You can probably reproduce the problem locally by running the same command that's in your Profile.

Related

java.io.FileNotFoundException when using tomcat

I have an application running fine on localhost but I am having issues when It is deployed on tomcat
The code I am using to read the file is :
File jasperFile = new File(getClass().getClassLoader().getResource("reports/Header.jasper").getFile());
I get this error in catalina :
net.sf.jasperreports.engine.JRException: java.io.FileNotFoundException: file:/usr/local/apache-tomcat9/webapps/com.peek.facture.server/WEB-INF/lib/facture.server-1.0.0-SNAPSHOT.jar!/reports/Header.jasper
What triggers me is the "!" at the end of the jar name, where does it come from?
Also I have tried to download the jar, extract it, and my Header.jasper is correctly in the resources/reports/ folder
When you run on your local a stand-alone physical file Header.jasper exists (you can physically see it when you browse the reports directory).
However when you deploy to a tomcat server, that stand-alone physical file no longer exists. Instead, if you set-up your build correctly, when you open up your jar (facture.server-1.0.0-SNAPSHOT.jar), there should be a directory called reports in it with the file Header.jasper within that directory.
So when your try get a resource via getClass().getClassLoader().getResource(...).getFile() you are actually trying to access a stand-alone physical file. Instead you need to get the resource as an InputStream and then work with if from there...
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("reports/Header.jasper");
When working with resources, it's always better to rather access them this way. Especially if you are planning to deploy anywhere with a single artifact, because your resources should be packaged in with your artifact.

Can not load properties file on classpath; [java, jboss, maven, spring]

I have a properties file called app.properties that is correctly put in src/main/resources/app.properties.
In the WAR file it is correctly located in \WEB-INF\classes.
In Standalone mode in my local environment (windows) and standalone mode on a linux test server, the WAR file starts up correctly reading my properties file.
In Jboss Domain mode on another linux server, using the exact same WAR file, I get error file not found app.properties. It is indeed there.
Other than domain mode being the difference between the two servers, the first test server jboss is installed under root and running as root. The other server is running as a user that has read and execute access.
I've thoroughly debugged the code with print statements and im 99% sure it is not a code issue, any ideas what in jboss domain mode may be causing the problem of not being able to read the properties file on the classpath?
Thanks in advance.
Relevant parts of code
MutablePropertySources sources = new MutablePropertySources();
sources.addLast(getEncryptablePropertiesSource(new ClassPathResource("app.properties")));
partial method
private EncryptablePropertiesPropertySource getEncryptablePropertiesSource(Resource propsResource) throws IOException{
//don't use file system resource because property files may be in a jar
System.out.println(">>>> in getEncryptablePropertiesSource filename is :");
System.out.println(propsResource.getFilename());
System.out.print(">>>> URL is: ");
System.out.println(propsResource.getURL());
The last System out print statement throws the error in the 2nd test environment, does not cause problems in any other environment.
If your ClassPathResource is the class from Spring:
public class ClassPathResource extends AbstractFileResolvingResource
Resource implementation for class path resources. Uses either a given
ClassLoader or a given Class for loading resources.
Supports resolution as java.io.File if the class path resource resides
in the file system, but not for resources in a JAR. Always supports
resolution as URL.
Therefore I don't think you can use it in your case.
Have you tried using one of the following methods?
ClassLoader.getResource(String name)
ClassLoader.getResourceAsStream(String name)

Getting reources path creates weird path (in jar works)

I've got some weird problem. I get access to my resources files like this:
File xmlFile = new File(getClass().getResource(xmlPath).getPath());
Where xmlPath is "/META-INF/file.xml".
When I run from Eclipse, everything works fine. Unfortunately, when I pack everything to jnlp file, upload with my web app on tomcat (from where I download all jar's by jnlp) it stops work.
When I run my jnlp, it downloads all jar's like it should and fails to start. Throwing this exception:
java.io.FileNotFoundException: C:\Users\A050868\Desktop\http:\address:port\webapp\downloads\lib\package.jar!\META-INF\componentContext.xml (The filename, directory name, or volume label syntax is incorrect)
How can I get access to my file, which is in resources/META-INF folder, in cached locale jar copy? For now it seems, like Java try get access to jar on server side - no this local, downloaded by jnlp.
Any ideas?
All the files are packed together in your jnlp file. They don't to exist as individual files on filesystem when you port your package.
That said, they are available on the classpath. You can access the content of your package using the appropriate classloader.
getClass().getClassLoader().getResourceAsStream(...)
This may help you

finding static file in jar

I am trying to use a jar file which itself is a web application in another web project. In my jar which i have created using eclipse's export to jar functionality, I have stored a csv file in a folder. To use relative paths in the code in the jar I access it using
MyClass.class.getResource(ApplicationConstants.ALIASESFILE).getPath();
and this works fine when I deploy (glassfish) and use the project as a separate application. But when I am using the same from within another project, it gives a path as shown below
D:\javaProjects\AutomodeGS_Prachi\lib\internal\RESTWSGS.jar!\aliases\aliases.csv
I am getting a file notfound exception.What could be wrong?
The getResource() method is returning a "jar:" URL. The path component of that URL is not a normal filesystem pathname, and can't be opened directly using Java's file classes.
The simple way to do this is to use Class.getResourceAsStream(...) to open the stream. If you need an "identifier" for the JAR entry, use Class.getResource(...), but then open the stream using URL.openStream().
This works fine from glassfish may be because glassfish has exploded jar on file system so that your csv file is acutually a file to the file system,
if you try to read it from another project it fails because the jar containing your file is in classpath that is fine, but the csv file is under jar file and it is no longer a File
You can read it as Stream
InputStream is = MyClass.class.getResourceAsStream(ApplicationConstants.ALIASESFILE);

Error with the .class location when building a WAR for a Play! framework application

I'm working on a 1.2 Play! framework application, and I have a problem when deploying it as a WAR on a Tomcat 6.
One page of my application displays a list of information.
These information are retrieved from a .yml file.
So I have a controller that generate a Iterable<Object> from this .yml file, like that:
public static void myFunction() {
Constructor constructor = new Constructor(MyClass.class); // org.yaml.snakeyaml.constructor.Constructor
constructor.addTypeDescription(new TypeDescription(MyClass.class));
Yaml yaml = new Yaml(constructor);
Iterable<Object> listOfInfo = yaml.loadAll(Application.class.getResourceAsStream("/my-file.yml"));
render("Application/my-page.html", listOfInfo);
}
The important point is that MyClass is located in the app/my/company/my-app/ package (my.company.my-app.MyClass).
When I run my application using play run, there is no problem.
Now, I build the WAR package (using play war -o some/dir --zip), and I install this generated WAR on a Tomcat (6.0).
Once the server is started, and try to access the corresponding page, I get the following error:
#683lh76fn
Internal Server Error (500)
Template execution error (In /app/views/Application/my-page.html around line 9)
Execution error occured in template /app/views/Application/my-page.html. Exception raised was ConstructorException : null; Can't construct a java object f
or tag:yaml.org,2002:my.company.my-app.MyClass; exception=Class not found: my.company.my-app.MyClass.
play.exceptions.TemplateExecutionException: null; Can't construct a java object for tag:yaml.org,2002:my.company.my-app.MyClass; exception
=Class not found: my.company.my-app.MyClass
at play.templates.BaseTemplate.throwException(BaseTemplate.java:84)
at play.templates.GroovyTemplate.internalRender(GroovyTemplate.java:252)
at play.templates.Template.render(Template.java:26)
at play.templates.GroovyTemplate.render(GroovyTemplate.java:184)
at play.mvc.results.RenderTemplate.<init>(RenderTemplate.java:24)
at play.mvc.Controller.renderTemplate(Controller.java:659)
at play.mvc.Controller.renderTemplate(Controller.java:639)
at play.mvc.Controller.render(Controller.java:694)
at controllers.Application.myFunction(Application.java:311)
If I have a look in the exploded war, I see that my.company.my-app.MyClass is located in the directory WEB-INF/application/precompiled/java/my/company/my-app/ directory.
If I move this directory into WEB-INF/classes, then I don't get this error anymore.
Why does this error occurs? What are my options to make it work (without modifying manually the WAR)?
By the default, Tomcat only looks at files in /WEB-INF/classes and /WEB-INF/lib (for each war file) as they are included in the classpath. There are a number of things you could do..
Put your classes in the Tomcat lib folder and they'll be handled by the common loader rather than the webapp loader. (not recommended as they will be available to other apps)
You can enable the shared loader in conf/catalina.properties and use whatever directory you want.
add the path to the CLASSPATH variable in the '/bin/setclasspath.sh' script (or setclasspath.bat for
Windows).
Personally i would fix/modify how i build the war file and make sure that the classes are copied to WEB-INF/classes instead of WEB-INF/application folder.
I finally found the answer of this problem!
The Yaml parser (snakeyaml) creates its own ClassLoader in order to parse a .yml file. The structure of the Play! framework uses its own ClassLoader, and the compiled classes are located in the WEB-INF/application/precompiled/java directory, which is not the WAR standard.
Due to that, Yaml was not able to retrieve the classes, in particular MyClass.class.
The solution to solve this issue (except by modifying the WAR using Ant for example), is to give the Play! ClassLoader to the Yaml parser. Instead of writing that:
Constructor constructor = new Constructor(MyClass.class);
constructor.addTypeDescription(new TypeDescription(MyClass.class));
I write:
CustomClassLoaderConstructor constructor = new CustomClassLoaderConstructor(MyClass.class, MyController.class.getClassLoader());
constructor.addTypeDescription(new TypeDescription(MyClass.class));
using this, I can use the WAR created by Play! directly without getting the Yaml error anymore!

Categories