Can someone please point out what I'm doing wrong here.
I have a small weather app that generates and sends an HTML email. With my code below, everything works fine when I run it from Eclipse. My email gets generated, it's able to access my image resources and it sends the email with the included attachment.
However, when I build the executable jar by running mvn install and run the jar using java -jar NameOfMyJar.jar I get java.io.FileNotFound Exceptions for my image resource.
I know that I have to be doing something wrong with how I'm accessing my image resources, I just don't understand why it works fine when it's not packaged, but bombs out whenever I package it into a jar.
Any advice is very much appreciated it.
My project layout
How I'm accessing my image resource
//Setup the ATTACHMENTS
MimeBodyPart attachmentsPart = new MimeBodyPart();
try {
attachmentsPart.attachFile("resources/Cloudy_Day.png");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
The StackTrace
Exception in thread "main" java.lang.RuntimeException: javax.mail.MessagingException: IOException while sending message;
nested exception is:
java.io.FileNotFoundException: resources/Cloudy_Day.png (No such file or directory)
at Utilities.SendEmailUsingGmailSMTP.SendTheEmail(SendEmailUsingGmailSMTP.java:139)
at Utilities.SendEmailUsingGmailSMTP.SendWeatherEmail(SendEmailUsingGmailSMTP.java:66)
at Weather.Main.start(Main.java:43)
at Weather.Main.main(Main.java:23)
Caused by: javax.mail.MessagingException: IOException while sending message;
nested exception is:
java.io.FileNotFoundException: resources/Cloudy_Day.png (No such file or directory)
at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:1167)
at javax.mail.Transport.send0(Transport.java:195)
at javax.mail.Transport.send(Transport.java:124)
at Utilities.SendEmailUsingGmailSMTP.SendTheEmail(SendEmailUsingGmailSMTP.java:134)
... 3 more
Caused by: java.io.FileNotFoundException: resources/Cloudy_Day.png (No such file or directory)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:146)
at javax.activation.FileDataSource.getInputStream(FileDataSource.java:97)
at javax.activation.DataHandler.writeTo(DataHandler.java:305)
at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:1485)
at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:865)
at javax.mail.internet.MimeMultipart.writeTo(MimeMultipart.java:462)
at com.sun.mail.handlers.multipart_mixed.writeTo(multipart_mixed.java:103)
at javax.activation.ObjectDataContentHandler.writeTo(DataHandler.java:889)
at javax.activation.DataHandler.writeTo(DataHandler.java:317)
at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:1485)
at javax.mail.internet.MimeMessage.writeTo(MimeMessage.java:1773)
at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:1119)
... 6 more
Others are correct with the use of getResourceAsStream, but the path is a little tricky. You see the little package icon in the resources folder? That signifies that all the files in the resource folder will be put into the root of the classpath. Just like all the packages in src/main/java are put in the root. So you would take out the resources from the path
InputStream is = getClass().getResourceAsStream("/Cloudy_Day.png");
An aside: Maven has a file structure conventions. Class path resources are usually put into src/main/resources. If you create a resources dir in the src/main, Eclipse should automatically pick it up, and create the little package icon for a path src/main/resource that you should see in the project explorer. These files would also go to the root and could be accessed the same way. I would fix the file structure to follow this convention.
Note: A MimeBodyPart, can be Constructed from an InputStream (As suggested by Bill Shannon, this is incorrect). As mentioned in his comment below
"You can also attach the data using"
mbp.setDataHandler(new DataHandler(new ByteArrayDataSource(
this.getClass().getResourceAsStream("/Cloudy_Day.png", "image/png"))));
You can't access resources inside a JAR file as a File, only read them as an InputStream: getResourceAsStream().
As the MimeBodyPart has no attach() method for an InputStream, the easiest way should be to read your resources and write them to temp files, then attach these files.
Try this
new MimeBodyPart().attachFile(new File(this.getClass().getClassLoader().getResource("resources/Cloudy_Day.png").toURI());
I don't know if this will help anyone or not. But, I have a similar case as the OP and I solved the case by finding the file in the classpath using recursive function. The idea is so that when another developer decided to move the resources into another folder/path. It will still be found as long as the name is still the same.
For example, in my work we usually put our resources outside the jar, and then we add said resources path into our classpath, so here the classpath of the resources will be different depending on where it is located.
That's where my code comes to work, no matter where the file is put, as long as it's in the classpath it will be found.
Here is an example of my code in action:
import java.io.File;
public class FindResourcesRecursive {
public File findConfigFile(String paths, String configFilename) {
for (String p : paths.split(File.pathSeparator)) {
File result = findConfigFile(new File(p), configFilename);
if (result != null) {
return result;
}
}
return null;
}
private File findConfigFile(File path, String configFilename) {
if (path.isDirectory()) {
String[] subPaths = path.list();
if (subPaths == null) {
return null;
}
for (String sp : subPaths) {
File subPath = new File(path.getAbsoluteFile() + "/" + sp);
File result = findConfigFile(subPath, configFilename);
if (result != null && result.getName().equalsIgnoreCase(configFilename)) {
return result;
}
}
return null;
} else {
File file = path;
if (file.getName().equalsIgnoreCase(configFilename)) {
return file;
}
return null;
}
}
}
Here I have a test case that is coupled with a file "test.txt" in my test/resources folder. The content of said file is:
A sample file
Now, here is my test case:
import org.junit.Test;
import java.io.*;
import static org.junit.Assert.fail;
public class FindResourcesRecursiveTest {
#Test
public void testFindFile() {
// Here in the test resources I have a file "test.txt"
// Inside it is a string "A sample file"
// My Unit Test will use the class FindResourcesRecursive to find the file and print out the results.
File testFile = new FindResourcesRecursive().findConfigFile(
System.getProperty("java.class.path"),
"test.txt"
);
try (FileInputStream is = new FileInputStream(testFile)) {
int i;
while ((i = is.read()) != -1) {
System.out.print((char) i);
}
System.out.println();
} catch (IOException e) {
fail();
}
}
}
Now, if you run this test, it will print out "A sample file" and the test will be green.
Related
The Description:
I've created a ZIP file in Java 8 and try to copy a directory with all it's subfiles and directories into this zip file.
Path directory = Paths.get("P:\Java\Test\backups\test.zip");
// path to the world;
Path world = Paths.get("P:\Java\Test\world");
[...]
// Create a map which tells the file system to create a new file if it doesn't exist
ImmutableMap immutableMap = ImmutableMap.of("create", String.valueOf(Files.notExists(this.directory)));
// Get a file system provider which is capable of creating a ZIP file
FileSystemProvider zipProvider = FileSystemProvider.installedProviders().stream()
.filter(provider -> provider.getScheme().equals("jar")).findFirst().get();
// Create the file system
try (FileSystem fs = zipProvider.newFileSystem(this.directory, immutableMap)) {
try {
Files.walk(this.world).forEach((Path sourcePath) -> {
try {
CopyOption[] option = new CopyOption[] {
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES
};
Path destination = this.directory.resolve(this.world.relativize(sourcePath));
Files.copy(sourcePath, destination,option);
} catch (IOException e) {
e.printStackTrace();
}
});
} catch (IOException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
The Problem:
Whenever I add the line Files.copy to copy my directory and all sub-directories and sub-files into the zip file I'm getting the following exception: java.nio.file.AccessDeniedException: .\backups\test.zip
Console output:
In the following stacktrace I changed the line numbers of the class calls to the one's of the code snippet I posted above for better readability except for the call to the ThreadBackup.run method. It is basically the method the code is beeing executed in with some other, but unrelated things.
java.nio.file.AccessDeniedException: .\backups\tests.zip
at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:83)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
at sun.nio.fs.WindowsFileCopy.copy(WindowsFileCopy.java:231)
at sun.nio.fs.WindowsFileSystemProvider.copy(WindowsFileSystemProvider.java:278)
at java.nio.file.Files.copy(Files.java:1274)
at serverutilities.backups.ThreadBackups.lambda$createZipFile$1(ThreadBackups.java:24)
at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.Iterator.forEachRemaining(Iterator.java:116)
at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
at serverutilities.backups.ThreadBackups.createZipFile(ThreadBackups.java:18)
at serverutilities.backups.ThreadBackups.run(ThreadBackups.java:56)
at java.lang.Thread.run(Thread.java:748)
java.nio.file.NoSuchFileException: P:\Java\Test\backups\test.zip
at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:90)
at sun.nio.fs.WindowsLinkSupport.getRealPath(WindowsLinkSupport.java:259)
at sun.nio.fs.WindowsPath.toRealPath(WindowsPath.java:836)
at sun.nio.fs.WindowsPath.toRealPath(WindowsPath.java:44)
at com.sun.nio.zipfs.ZipFileSystemProvider.removeFileSystem(ZipFileSystemProvider.java:322)
at com.sun.nio.zipfs.ZipFileSystem.close(ZipFileSystem.java:305)
at serverutilities.backups.ThreadBackups.createZipFile(ThreadBackups.java:32)
at serverutilities.backups.ThreadBackups.run(ThreadBackups.java:56)
at java.lang.Thread.run(Thread.java:748)
I noticed that whenever I call the Files.copy method the ZIP file isn't even created or atleast not saved, thus the NoSuchFileException is thrown after the AccessDeniedException is thrown for every directory and file I try to copy.
I have never used java.nio.file, but once I had to deal with such task and I used java.util.zip, which is quite straightforward to use just for creating a zip file from a directory
Although, if you can't change what you are using for archiving the directory, then this solution won't be much of a help, but sample code with some explanations:
Create new ZIP archive with new ZipOutputStream
Walk through the file tree which is going to be zipped with Files.walk
For each path of the file tree pack the entries. ZipEntry holds the metadata about a single file in the archive
To use it just call method packDir with Paths of src and the destination.zip
private static void packDir(Path src, Path dest) throws IOException {
try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(dest));
ZipOutputStream zo = new ZipOutputStream(out);
Stream<Path> dirStream = Files.walk(src)) {
dirStream.filter(p -> !p.equals(src)).forEach(path -> {
try {
packEntry(src, zo, path);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
private static void packEntry(Path src, ZipOutputStream zo, Path path) throws IOException {
String name = src.relativize(path).toString().replace('\\', '/');
boolean isDir = Files.isDirectory(path);
if (isDir) {
name += "/";
}
ZipEntry e = new ZipEntry(name);
zo.putNextEntry(e);
if (!isDir) {
Files.copy(path, zo);
}
zo.closeEntry();
}
You are trying to use a regular file as a directory.
In this line
try (FileSystem fs = zipProvider.newFileSystem(this.directory, immutableMap)) {
you are opening or creating a zip file system at this.directory, which must be a valid path within the default file system. After succeeding, this.directory definitely is a regular file (in the zip file format), still within the default file system.
This line
Path destination = this.directory.resolve(this.world.relativize(sourcePath));
is treating this regular file like a directory.
You want to copy into the zip file system, hence you must use a path from the zip file system, not the path to the zip file within the default file system.
You may get the root of the zip filesystem, e.g.
Path zipRoot = fs.getPath("/");
and use this as target. As far as I know, you can’t use the Path retrieved from one file system as argument to a method of the Path of another file system, so you would have to resolve the target path like
Path destination = zipRoot;
for(Path p: this.world.relativize(sourcePath))
destination = destination.resolve(p.toString());
But perhaps there is a simpler method.
Another issue is the use of Files.copy for directories. When the directory already exists (and the root directory always exists), it will fail, unless you specify REPLACE_EXISTING, but this will fail as soon as the target directory is not empty. This simplest solution is to keep existing directories as-is, so the code would look like
try(FileSystem fs = zipProvider.newFileSystem(this.directory, immutableMap)) {
Path zipRoot = fs.getPath("/");
CopyOption[] option = {
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES
};
Files.walk(this.world).forEach(sourcePath -> {
try {
Path destination = zipRoot;
for(Path p: this.world.relativize(sourcePath))
destination = destination.resolve(p.toString());
if(!Files.isDirectory(destination) || !Files.isDirectory(sourcePath))
Files.copy(sourcePath, destination, option);
} catch(IOException e) {
throw new UncheckedIOException(e);
}
});
} catch(IOException|UncheckedIOException e) {
e.printStackTrace(); // TODO replace with actual exception handling
}
This will skip path entries, if the target directory exists and the source also is a directory, as situations where the source is not a directory but the target is an existing directory should be reported via exception.
If you want to enforce the policy of replacing existing files and directories, you would have to implement a tree deletion for the case that there is an existing non-empty directory, but still, you have to skip the root directory, which can’t be deleted.
Some time ago I released some utility classes for adding and extracting files to/from JAR/ ZIP files using the NIO.2 File API.
Here's a snippet from the tutorial:
public void addResource(Path zipPath, Path targetDirPath, Path srcPath, String targetInZipPathString) throws IOException {
Path targetZipPath = copyZipFile(zipPath, targetDirPath);
try (FileSystem jarFS = JarFiles.newJarFileSystem(targetZipPath.toUri())) {
Path targetInZipPath = jarFS.getPath(targetInZipPathString);
// Adds the src directory name to the zip. You can omit this if you just want to copy the contents.
Path finalTargetInZipPath = PathUtils.resolve(targetInZipPath, srcPath.getFileName());
Files.createDirectories(finalTargetInZipPath);
CopyFileVisitor.copy(srcPath, finalTargetInZipPath);
}
}
The CopyFileVisitor uses PathUtils to resolve Paths across FileSystems.
There is a tutorial.
The library is Open Source and available from Maven Central:
<dependency>
<groupId>org.softsmithy.lib</groupId>
<artifactId>softsmithy-lib-core</artifactId>
<version>0.9</version>
</dependency>
When I run this as a jar, this .properties file is created on the desktop. For the sake of keeping things clean, how can I set the path to save this file somewhere else, like a folder? Or even the jar itself but I was having trouble getting that to work. I plan on giving this to someone and wouldn't want their desktop cluttered in .properties files..
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties;
public class DataFile {
public static void main(String[] args) {
Properties prop = new Properties();
OutputStream output = null;
try {
output = new FileOutputStream("config.properties");
prop.setProperty("prop1", "000");
prop.setProperty("prop2", "000");
prop.setProperty("prop3", "000");
prop.store(output, null);
} catch (IOException io) {
io.printStackTrace();
} finally {
if (output != null) {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Since you are using the file name without a path, the file you creates ends in the CWD. That is the Current working directory the process inherits from the OS.
That is is you execute your jar file from the Desktop directory any files that use relative paths will end in the Desktop or any of it sub directories.
To control the absolute location of the file you must use absolute paths.
An absolute path always starts with a slash '/'.
Absolute path:
/etc/config.properties
Relative path:
sub_dir/config.properties
The simplest way is to hard code some path into the file path string.
output = new FileOutputStream("/etc/config.properties");
You can of course setup the path in a property which you can pass using the command line instead of hard coding it. The you concat the path name and the file name together.
String path = "/etc";
String full_path = "/etc" + "/" + "config.properties";
output = new FileOutputStream(full_path);
Please note that windows paths in java use a forward slash instead of back slash.
Check this for more details
file path Windows format to java format
I am trying to add a txt file into a folder which is inside a zip file.
First, I was extracting all the contents of zip file then adding the txt file and then zipping back.
Then I read about the nio method which I can modify the zip without extracting it. Using this method I can add the txt file to the main folder of zip but I can't go deeper.
testing.zip file has res folder in it.
Here is my code:
Path txtFilePath = Paths.get("\\test\\prefs.txt");
Path zipFilePath = Paths.get("\\test\\testing.zip");
FileSystem fs;
try {
fs = FileSystems.newFileSystem(zipFilePath, null);
Path fileInsideZipPath = fs.getPath("res/prefs.txt"); //when I remover "res/" code works.
Files.copy(txtFilePath, fileInsideZipPath);
fs.close();
} catch (IOException e) {
e.printStackTrace();
}
I get the following exception:
java.nio.file.NoSuchFileException: res/
(edit to give the actual answer)
Do:
fs.getPath("res").resolve("prefs.txt")
instead of:
fs.getPath("res/prefs.txt")
The .resolve() method will do the correct thing with regards to file separators etc.
The fs.getPath("res/prefs.txt") should certainly work and you don't need to split it to fs.getPath("res").resolve("prefs.txt") as the approved answer says.
The exception java.nio.file.NoSuchFileException: res/ is slightly confusing because it mentions file but in fact directory is missing.
I had a similar problem and all I had to do was:
if (fileInsideZipPath.getParent() != null)
Files.createDirectories(fileInsideZipPath.getParent());
See full example:
#Test
public void testAddFileToArchive() throws Exception {
Path fileToAdd1 = rootTestFolder.resolve("notes1.txt");
addFileToArchive(archiveFile, "notes1.txt", fileToAdd1);
Path fileToAdd2 = rootTestFolder.resolve("notes2.txt");
addFileToArchive(archiveFile, "foo/bar/notes2.txt", fileToAdd2);
. . .
}
public void addFileToArchive(Path archiveFile, String pathInArchive, Path srcFile) throws Exception {
FileSystem fs = FileSystems.newFileSystem(archiveFile, null);
Path fileInsideZipPath = fs.getPath(pathInArchive);
if (fileInsideZipPath.getParent() != null) Files.createDirectories(fileInsideZipPath.getParent());
Files.copy(srcFile, fileInsideZipPath, StandardCopyOption.REPLACE_EXISTING);
fs.close();
}
If I remove Files.createDirectories() bit, and ensure clear start with clear test directory, I get:
java.nio.file.NoSuchFileException: foo/bar/
at com.sun.nio.zipfs.ZipFileSystem.checkParents(ZipFileSystem.java:863)
at com.sun.nio.zipfs.ZipFileSystem.newOutputStream(ZipFileSystem.java:528)
at com.sun.nio.zipfs.ZipPath.newOutputStream(ZipPath.java:792)
at com.sun.nio.zipfs.ZipFileSystemProvider.newOutputStream(ZipFileSystemProvider.java:285)
at java.nio.file.Files.newOutputStream(Files.java:216)
at java.nio.file.Files.copy(Files.java:3016)
at java.nio.file.CopyMoveHelper.copyToForeignTarget(CopyMoveHelper.java:126)
at java.nio.file.Files.copy(Files.java:1277)
at my.home.test.zipfs.TestBasicOperations.addFileToArchive(TestBasicOperations.java:111)
at my.home.test.zipfs.TestBasicOperations.testAddFileToArchive(TestBasicOperations.java:51)
I have the following function inside a Stateless EJB running in Glassfish. All it does is write some data to a file. The first part of the function just creates the path to where the file needs to go. The second part actually writes the file.
private boolean createFile(String companyName, String fileName, byte[] data)
{
logger.log(Level.FINEST, "Creating file: {0} for company {1}", new Object[]{fileName, companyName});
File companyFileDir = new File(LOCAL_FILE_DIR, companyName);
if(companyFileDir.exists() == false)
{
boolean createFileDir = companyFileDir.mkdirs();
if(createFileDir == false)
{
logger.log(Level.WARNING, "Could not create directory to place file in");
return false;
}
}
File newFile = new File(companyFileDir, fileName);
try
{
FileOutputStream fileWriter = new FileOutputStream(newFile);
fileWriter.write(data);
}
catch(IOException e)
{
logger.log(Level.SEVERE,"Could not write file to disk",e);
return false;
}
logger.log(Level.FINEST,"File successfully written to file");
return true;
}
The output I get after this code executes is:
WARNING: Could not create directory to place file in
So obviously Glassfish cant create this directory. I am am assuming this has something to do with permissions. Can anyone give me a direction to go as to what might be wrong here?
I am running this on Glassfish 3.12 on Ubuntu 12
different things:
1) Compare spec: (21.1.2 Programming Restrictions)
An enterprise bean must not use the java.io package to attempt to access files and directories in the file system.
I'm sure GF isn't enforcing this, but you should be aware of that.
2) The code itself is fine. Try chmod +777 on the LOCAL_FILE_DIR to get an idea if it has to do with rights in general ...
Hope that helps ...
I have a resources folder/package in the root of my project, I "don't" want to load a certain File. If I wanted to load a certain File, I would use class.getResourceAsStream and I would be fine!! What I actually want to do is to load a "Folder" within the resources folder, loop on the Files inside that Folder and get a Stream to each file and read in the content... Assume that the File names are not determined before runtime... What should I do? Is there a way to get a list of the files inside a Folder in your jar File?
Notice that the Jar file with the resources is the same jar file from which the code is being run...
Finally, I found the solution:
final String path = "sample/folder";
final File jarFile = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
if(jarFile.isFile()) { // Run with JAR file
final JarFile jar = new JarFile(jarFile);
final Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
while(entries.hasMoreElements()) {
final String name = entries.nextElement().getName();
if (name.startsWith(path + "/")) { //filter according to the path
System.out.println(name);
}
}
jar.close();
} else { // Run with IDE
final URL url = Launcher.class.getResource("/" + path);
if (url != null) {
try {
final File apps = new File(url.toURI());
for (File app : apps.listFiles()) {
System.out.println(app);
}
} catch (URISyntaxException ex) {
// never happens
}
}
}
The second block just work when you run the application on IDE (not with jar file), You can remove it if you don't like that.
Try the following.
Make the resource path "<PathRelativeToThisClassFile>/<ResourceDirectory>" E.g. if your class path is com.abc.package.MyClass and your resoure files are within src/com/abc/package/resources/:
URL url = MyClass.class.getResource("resources/");
if (url == null) {
// error - missing folder
} else {
File dir = new File(url.toURI());
for (File nextFile : dir.listFiles()) {
// Do something with nextFile
}
}
You can also use
URL url = MyClass.class.getResource("/com/abc/package/resources/");
The following code returns the wanted "folder" as Path regardless of if it is inside a jar or not.
private Path getFolderPath() throws URISyntaxException, IOException {
URI uri = getClass().getClassLoader().getResource("folder").toURI();
if ("jar".equals(uri.getScheme())) {
FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap(), null);
return fileSystem.getPath("path/to/folder/inside/jar");
} else {
return Paths.get(uri);
}
}
Requires java 7+.
I know this is many years ago . But just for other people come across this topic.
What you could do is to use getResourceAsStream() method with the directory path, and the input Stream will have all the files name from that dir. After that you can concat the dir path with each file name and call getResourceAsStream for each file in a loop.
I had the same problem at hands while i was attempting to load some hadoop configurations from resources packed in the jar... on both the IDE and on jar (release version).
I found java.nio.file.DirectoryStream to work the best to iterate over directory contents over both local filesystem and jar.
String fooFolder = "/foo/folder";
....
ClassLoader classLoader = foofClass.class.getClassLoader();
try {
uri = classLoader.getResource(fooFolder).toURI();
} catch (URISyntaxException e) {
throw new FooException(e.getMessage());
} catch (NullPointerException e){
throw new FooException(e.getMessage());
}
if(uri == null){
throw new FooException("something is wrong directory or files missing");
}
/** i want to know if i am inside the jar or working on the IDE*/
if(uri.getScheme().contains("jar")){
/** jar case */
try{
URL jar = FooClass.class.getProtectionDomain().getCodeSource().getLocation();
//jar.toString() begins with file:
//i want to trim it out...
Path jarFile = Paths.get(jar.toString().substring("file:".length()));
FileSystem fs = FileSystems.newFileSystem(jarFile, null);
DirectoryStream<Path> directoryStream = Files.newDirectoryStream(fs.getPath(fooFolder));
for(Path p: directoryStream){
InputStream is = FooClass.class.getResourceAsStream(p.toString()) ;
performFooOverInputStream(is);
/** your logic here **/
}
}catch(IOException e) {
throw new FooException(e.getMessage());
}
}
else{
/** IDE case */
Path path = Paths.get(uri);
try {
DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path);
for(Path p : directoryStream){
InputStream is = new FileInputStream(p.toFile());
performFooOverInputStream(is);
}
} catch (IOException _e) {
throw new FooException(_e.getMessage());
}
}
Another solution, you can do it using ResourceLoader like this:
import org.springframework.core.io.Resource;
import org.apache.commons.io.FileUtils;
#Autowire
private ResourceLoader resourceLoader;
...
Resource resource = resourceLoader.getResource("classpath:/path/to/you/dir");
File file = resource.getFile();
Iterator<File> fi = FileUtils.iterateFiles(file, null, true);
while(fi.hasNext()) {
load(fi.next())
}
If you are using Spring you can use org.springframework.core.io.support.PathMatchingResourcePatternResolver and deal with Resource objects rather than files. This works when running inside and outside of a Jar file.
PathMatchingResourcePatternResolver r = new PathMatchingResourcePatternResolver();
Resource[] resources = r.getResources("/myfolder/*");
Then you can access the data using getInputStream and the filename from getFilename.
Note that it will still fail if you try to use the getFile while running from a Jar.
As the other answers point out, once the resources are inside a jar file, things get really ugly. In our case, this solution:
https://stackoverflow.com/a/13227570/516188
works very well in the tests (since when the tests are run the code is not packed in a jar file), but doesn't work when the app actually runs normally. So what I've done is... I hardcode the list of the files in the app, but I have a test which reads the actual list from disk (can do it since that works in tests) and fails if the actual list doesn't match with the list the app returns.
That way I have simple code in my app (no tricks), and I'm sure I didn't forget to add a new entry in the list thanks to the test.
Below code gets .yaml files from a custom resource directory.
ClassLoader classLoader = this.getClass().getClassLoader();
URI uri = classLoader.getResource(directoryPath).toURI();
if("jar".equalsIgnoreCase(uri.getScheme())){
Pattern pattern = Pattern.compile("^.+" +"/classes/" + directoryPath + "/.+.yaml$");
log.debug("pattern {} ", pattern.pattern());
ApplicationHome home = new ApplicationHome(SomeApplication.class);
JarFile file = new JarFile(home.getSource());
Enumeration<JarEntry> jarEntries = file.entries() ;
while(jarEntries.hasMoreElements()){
JarEntry entry = jarEntries.nextElement();
Matcher matcher = pattern.matcher(entry.getName());
if(matcher.find()){
InputStream in =
file.getInputStream(entry);
//work on the stream
}
}
}else{
//When Spring boot application executed through Non-Jar strategy like through IDE or as a War.
String path = uri.getPath();
File[] files = new File(path).listFiles();
for(File file: files){
if(file != null){
try {
InputStream is = new FileInputStream(file);
//work on stream
} catch (Exception e) {
log.error("Exception while parsing file yaml file {} : {} " , file.getAbsolutePath(), e.getMessage());
}
}else{
log.warn("File Object is null while parsing yaml file");
}
}
}
Took me 2-3 days to get this working, in order to have the same url that work for both Jar or in local, the url (or path) needs to be a relative path from the repository root.
..meaning, the location of your file or folder from your src folder.
could be "/main/resources/your-folder/" or "/client/notes/somefile.md"
Whatever it is, in order for your JAR file to find it, the url must be a relative path from the repository root.
it must be "src/main/resources/your-folder/" or "src/client/notes/somefile.md"
Now you get the drill, and luckily for Intellij Idea users, you can get the correct path with a right-click on the folder or file -> copy Path/Reference.. -> Path From Repository Root (this is it)
Last, paste it and do your thing.
Simple ... use OSGi. In OSGi you can iterate over your Bundle's entries with findEntries and findPaths.
Inside my jar file I had a folder called Upload, this folder had three other text files inside it and I needed to have an exactly the same folder and files outside of the jar file, I used the code below:
URL inputUrl = getClass().getResource("/upload/blabla1.txt");
File dest1 = new File("upload/blabla1.txt");
FileUtils.copyURLToFile(inputUrl, dest1);
URL inputUrl2 = getClass().getResource("/upload/blabla2.txt");
File dest2 = new File("upload/blabla2.txt");
FileUtils.copyURLToFile(inputUrl2, dest2);
URL inputUrl3 = getClass().getResource("/upload/blabla3.txt");
File dest3 = new File("upload/Bblabla3.txt");
FileUtils.copyURLToFile(inputUrl3, dest3);