What's the best way to load resources (Sounds, images, xml data), that also works from inside a distributed jar file?
I need to load some sounds, images, and xml data, for use in my program. Using
AudioInputStream ais = AudioSystem.getAudioInputStream(new File("~/src/com/example/package/name/assets/TestSound.wav"));
does not work in the jar, for obvious reasons, including the fact that src will not be in the jar.
Edit:
(A working) MWE: http://pastebin.com/CNq6zgPw
The ClassLoader class has two relevant methods:
getResource(path) delivers a URL for any resource on the classpath,
getResourceAsStream(path) delivers an input stream for the resource.
You can use these methods with overloads of the AudioSystem.getAudioInputStream(...) method to get an audio stream that reads a resource in your JAR file.
Note that if you call these methods on a ClassLoader object, that paths will be resolved in the namespaces of the JAR files on the classpath ... not the filesystem namespace of your development platform.
You can load any resources using this code from the jar or outside of the jar:
InputStream is = this.getClass().getClassLoader().getResourceAsStream("~/src/package/name/assets/TestSound.wav");
This snippet of code worked for me:
Clip clip = null;
ClassLoader cl = this.getClass().getClassLoader();
AudioInputStream ais;
URL url = cl.getResource("com/example/project/assets/TestSound.wav");
System.out.println(url);
try {
ais = AudioSystem.getAudioInputStream(url);
clip = AudioSystem.getClip();
clip.open(ais);
}
catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
The important thing is not adding the /src/ folder to the class path.
The critical change is changing cl.getResource("/com/example/project/assets/TestSound.wav") to cl.getResource("com/example/project/assets/TestSound.wav");
because /com/... indicates that the path is absolute, whereas com/... indicates that the path is relative.
For example,
System.out.println(new File("/Test.File").getAbsolutePath());
System.out.println(new File("Test.File").getAbsolutePath());
return
/Test.File
/Users/alphadelta/Documents/Workspace/TestProject/Test.File
respectively.
The first File created is created using "/Test.File", which is absolute. The second is created using "Test.File", which is relative to the project root in eclipse.
Related
In my application I load resources in this manner:
WinProcessor.class.getResource("repository").toString();
and this gives me:
file:/root/app/repository (and I replace "file:" with empty string)
This works fine when I run my application from the IDE, but when I run the jar of my application:
java -jar app.jar
The path becomes:
jar:/root/app.jar!/repository
is there any way to solve this problem?
I'll use the "repository" dir name in order to create this:
ConfigurationContext ctx = (ConfigurationContext) ConfigurationContextFactory.createConfigurationContextFromFileSystem(repositoryString, null);
In the same manner, I'll get one file name (instead of a dir) and I'll use it this way:
System.setProperty("javax.net.ssl.trustStore", fileNameString)
It sounds like you're then trying to load the resource using a FileInputStream or something like that. Don't do that: instead of calling getResource, call getResourceAsStream and read the data from that.
(You could load the resources from the URL instead, but calling getResourceAsStream is a bit more convenient.)
EDIT: Having seen your updated answer, it seems other bits of code rely on the data being in a physical single file in the file system. The answer is therefore not to bundle it in a jar file in the first place. You could check whether it's in a separate file, and if not extract it to a temporary file, but that's pretty hacky IMO.
When running code using java -jar app.jar, java uses ONLY the class path defined in the manifest of the JAR file (i.e. Class-Path attribute). If the class is in app.jar, or the class is in the class path set in the Class-Path attribute of the JAR's manifest, you can load that class using the following code snippet, where the className is the fully-qualified class name.
final String classAsPath = className.replace('.', '/') + ".class";
final InputStream input = ClassLoader.getSystemResourceAsStream( path/to/class );
Now if the class is not part of the JAR, and it isn't in the manifest's Class-Path, then the class loader won't find it. Instead, you can use the URLClassLoader, with some care to deal with differences between windows and Unix/Linux/MacOSX.
// the class to load
final String classAsPath = className.replace('.', '/') + ".class";
// the URL to the `app.jar` file (Windows and Unix/Linux/MacOSX below)
final URL url = new URL( "file", null, "///C:/Users/diffusive/app.jar" );
//final URL url = new URL( "file", null, "/Users/diffusive/app.jar" );
// create the class loader with the JAR file
final URLClassLoader urlClassLoader = new URLClassLoader( new URL[] { url } );
// grab the resource, through, this time from the `URLClassLoader` object
// rather than from the `ClassLoader` class
final InputStream input = urlClassLoader.getResourceAsStream( classAsPath );
In both examples you'll need to deal with the exceptions, and the fact that the input stream is null if the resource can't be found. Also, if you need to get the InputStream into a byte[], you can use Apache's commons IOUtils.toByteArray(...). And, if you then want a Class, you can use the class loader's defineClass(...) method, which accepts the byte[].
You can find this code in a ClassLoaderUtils class in the Diffusive source code, which you can find on SourceForge at github.com/robphilipp/diffusive
And a method to create URL for Windows and Unix/Linux/MacOSX from relative and absolute paths in RestfulDiffuserManagerResource.createJarClassPath(...)
Construct a URL, you can then load a resource (even in a jar file) using the openStream method.
I create a dynamic web app project using JSP/Servlet with eclipse. And I want to create a copy of "db.xls" file in the same place.
I try to create a copy of the "db.xls", the copy will named out.xls but it won't. These files should be located inside the same folder "files". My code compile, db.xls is correctly read, but file out.xls is not created.
What's wrong with my method ? Please help !
public void readExcel()
{
try{
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL url1 = classLoader.getResource("");
// read db.xls
wbook = Workbook.getWorkbook(new File(url1.getPath()+"/db.xls"));
// create a copy of db.xls nammed out.xls
wwbCopy = Workbook.createWorkbook(new File(url1.getPath()+"/out.xls"), wbook);
shSheet = wwbCopy.getSheet(0);
}
catch(Exception e)
{
e.printStackTrace();
}
}
I move the file "db.xls" inside WEB-INF and use getServletContext().getRealPath("/WEB-INF") but the output file "out.xls" still not created.
public void readExcel()
{
try{
// ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// URL url1 = classLoader.getResource("");
String tomcatRoot = getServletContext().getRealPath("/WEB-INF");
wbook = Workbook.getWorkbook(new File(tomcatRoot+"/db.xls"));
wwbCopy = Workbook.createWorkbook(new File(tomcatRoot+"/out.xls"), wbook);
shSheet = wwbCopy.getSheet(0);
}
catch(Exception e)
{
e.printStackTrace();
}
}
System.out your files and you'll see what's wrong
System.out.println(new File(tomcatRoot+"/db.xls").getAbsolutePath());
System.out.println(new File(tomcatRoot+"/out.xls").getAbsolutePath());
You expect the file to be you project directory but it isnt read/writen from/to that location because you have set up the files forlder as source folder in eclipse, so it is part our yours assempbly and lands in the classpath where you can read from a resource, i.e. using classloader and getResource / getResourceAsStream but you cannot and should not write to it, for several resons, most obvious is that your web app might not be unpacked from a war files.
In fact, you dont know where you are reading/writing your files to/from.
You might package your file with the war file and read from it, this is correct. But for writing the best is to have an explicite location on the filesystem where you can write your output files. check this answer for how you could go abut it using context init parameter
check the WEB-INF/classes folder, it might be in there
I think your missing write and close statements.
Try:
wwbCopy.write();
wwbCopy.close();
In order to read files within a web application, the files need to be stored somewhere under the WEB-INF folder, otherwise they won't be deployed as part of the application.
Once you've moved the folders into there you can use the following method within a servlet:
String tomcatRoot = getServletContext().getRealPath("/");
This will give you the root of the web application. Then you must build the path (including the WEB-INF folder) from there:
String sourceFile = tomcatRoot + "/WEB-INF/folder/source.file"
String targetFile = tomcatRoot + "/WEB-INF/folder/target.file"
EDIT: I originally stated that getRealPath() would give you the WEB-INF location. It doesn't, it gives the parent folder.
Hi i am using maven web project and want to write something to file abc.properties. This file in placed in standard /src/main/resource folder. My code is:
FileWriter file = new FileWriter("./src/main/resources/abc.properties");
try {
file.write("hi i am good");
} catch (IOException e) {
e.printStackTrace();
} finally {
file.flush();
file.close();
}
But it does not work as path is not correct. I tried many other examples but was unable to give path of this file.
Can you kindly help me in setting path of file which is placed in resources folder.
Thanks
I think you're confusing buildtime and runtime. During buildtime you have your src/main/java, src/main/resources and src/main/webapp, but during runtime these are all bundled in a war-file. This means there's no such thing as src/main/resources anymore.
The easiest way is to write to a [tempFile][1] and write to that file. The best way is to configure your outputFile, for instance in the wqeb.xml.
[1]: http://docs.oracle.com/javase/6/docs/api/java/io/File.html#createTempFile(java.lang.String, java.lang.String)
If your file is dropped under src/main/resources, it will end up under your-webapp/WEB-INF/classes directory if you project is package as a Web application i.e. with maven-war-plugin.
At runtime, if you want to files that are located under the latter directory, which are considered as web application resources, thus are already present in the application classpath, you can use the getResourceAsStream() method either on the ServletContext or using the current class ClassLoader:
From current thread context:
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream is = classLoader.getResourceAsStream("abc.properties");
FileWriter file = new FileWriter(new File(new FileInputStream(is)));
// some funny stuff goes here
If you have access to the Servlet context:
ServletContext context = getServletContext();
InputStream is = context.getResourceAsStream("/abc.properties");
FileWriter file = new FileWriter(new File(new FileInputStream(is)));
// some funny stuff goes here
Notice the leading slash in the latter example.
This should be simple but it has cost me hours. Everything I find on this site indicates I am doing it right but the file still cannot be found.
Inside a jar file I have two files 'CDAkeystore.jks' and 'CDAtruststore.jks' at top level.
Yet when I call
securityProps.setProperty("javax.net.ssl.keyStore","CDAkeystore.jks");
I get a system cannot find the file requested error.
The class file calling this method is inside the same jar in the usual package arrangement.
The jar file is as follows:
com ..... (a lot more class files)
org ..... (lots of class files)
META-INF
CDAtruststore.jks
CDAkeystore.jks
How can this be SOOO difficult?!!
---------- Added INfo ------n
Since the object using the path is open source I found the routine they are using to load the file. It is:
InputStream keystoreInputStream = preBufferInputStream(new FileInputStream(keyStoreName));
which according to the documentation of FileInputStream(String name) is
Creates a FileInputStream by opening a connection to an actual file, the file named by the path name 'name' in the file system. So how should this path be expressed?
Use YourClass.class.getResourceAsStream() or this.getClass().getResourceAsStream(). You can also use class loader if you are in multiple class loaders environment.
The answer is, in short, that you can't. At least in this situation. I am stuck with passing a path to a file to a library implementation that I have no control over. So the library method accesses the file on the assumption that the file exists in unzipped form in the OS's file system. It is getting the path from a Property.setProperty(stringKey, stringPath)
So the only solution I found was an ugly hack. I need to take the resource in my jar and copy it to a file on the system. Then I would pass the path to that file in the above setProperty() method. The ugly hack is implemented as follows (if anyone else can come up with a nicer solution I would be happy). It does solve the problem. The library routine is able to find my newly created file.
/* This evil reads a file as a resource inside a jar and dumps the file where ever
* the loader of this jar/application defines as the current directory. This pain is done
* so the SecurityDomian class can load the file; it cannot access the file from
* the jar. This means the 'name' passed in contains the file name inside the jar
* prefixed with "/" so it is not read with an assumed package extension.
*/
private boolean createFileFromResource(String name)
{
// Dont bother if the file already exists
if(new File(name.replace("/", "")).exists())
{
return true;
}
byte[] keystoreFile = new byte[2048];
ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream(2048);
// Get the resource
InputStream inputStream = this.getClass().getResourceAsStream(name);
try
{
int bytesRead = 0;
while(true)
{
// Read the resource into the buffer keystoreFile in 2048 byte chunks
bytesRead = inputStream.read(keystoreFile);
if(bytesRead < 0)
{
break;
}
// Copy and append the chunks to the ByteArrayOutputStream (this class
// does the auto-extending of the output array as more chunks are
// added so you don't have to do it.
byteArrayOut.write(keystoreFile, 0, bytesRead);
}
inputStream.close();
// Now create a file at the root of where ever the loader happens to think
// the root is. So remove the "/" in front of the file name
FileOutputStream outputStream = new FileOutputStream(name.replace("/", ""));
// Write out the file. Note you will be left with garbage at that location.
byteArrayOut.writeTo(outputStream);
outputStream.flush();
outputStream.close();
}
catch (IOException e)
{
e.printStackTrace();
return false;
}
return true;
}
I'm using Spring's Resource abstraction to work with resources (files) in the filesystem. One of the resources is a file inside a JAR file. According to the following code, it appears the reference is valid
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
// The path to the resource from the root of the JAR file
Resource fileInJar = resourcePatternResolver.getResources("/META-INF/foo/file.txt");
templateResource.exists(); // returns true
templateResource.isReadable(); // returns true
At this point, all is well, but then when I try to convert the Resource to a File
templateResource.getFile();
I get the exception
java.io.FileNotFoundException: class path resource [META-INF/foo/file.txt] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/D:/m2repo/uic-3.2.6-0.jar!/META-INF/foo/file.txt
at org.springframework.util.ResourceUtils.getFile(ResourceUtils.java:198)
at org.springframework.core.io.ClassPathResource.getFile(ClassPathResource.java:174)
What is the correct way to get a File reference to a Resource that exists inside a JAR file?
What is the correct way to get a File
reference to a Resource that exists
inside a JAR file?
The correct way is not doing that at all because it's impossible. A File represents an actual file on a file system, which a JAR entry is not, unless you have a special file system for that.
If you just need the data, use getInputStream(). If you have to satisfy an API that demands a File object, then I'm afraid the only thing you can do is to create a temp file and copy the data from the input stream to it.
If you want to read it, just call resource.getInputStream()
The exception message is pretty clear - the file does not reside on the file-system, so you can't have a File instance. Besides - what will do do with that File, apart from reading its content?
A quick look at the link you provided for Resource documentation, says the following:
Throws: IOException if the resource cannot be resolved as absolute file path,
i.e. if the resource is not available in a file system
Maybe the text file is inside a jar? In that case you will have to use getInputStream() to read its contents.
Just adding an example to the answers here. If you need a File (and not just the contents of it) from within your JAR, you need to create a temporary file from the resource first. (The below is written in Groovy):
InputStream inputStream = resourceLoader.getResource('/META-INF/foo/file.txt').inputStream
File tempFile = new File('file.txt')
OutputStream outputStream = new FileOutputStream(tempFile)
try {
IOUtils.copy(inputStream, outputStream)
} catch (IOException e) {
// Handle exception
} finally {
outputStream.close()
}