How to use FileSystem.getPath() as target for a (File-)Writer? - java

I have an app that outputs some files.
Depending on a configuration I Need to put them either in a plain Folder or in a zip file.
I'm trying to use FileSystem to decoople the Code actually writing from the actual Destination type.
My problem is that for Paths created by Zip-FileSystems the method .tpFile() is not supported. Therefore I cannot create a FileWriter that I can pass to e.g. JaxB.
public class FileSystemWriteTest {
public static void main(String[] args) throws IOException {
FileSystem localFileSystem = FileSystems.getDefault();
File relativeZipPath = Paths.get("target", "testpath").toFile();
relativeZipPath.mkdirs();
URI relativeZipFilePath = Paths.get(relativeZipPath.toString(), "test.zip").toUri();
URI zipUri = URI.create("jar:"
+ relativeZipFilePath);
System.out.println(zipUri);
Map<String, String> env = new HashMap<>();
env.put("create", "true");
try (FileSystem zipFile = FileSystems.newFileSystem(zipUri, env)) {
for (FileSystem fs : Arrays.asList(localFileSystem, zipFile)) {
Path file = fs.getPath("test.txt");
System.out.println(file.toAbsolutePath());
/* line 31 */ try (FileWriter fileWriter = new FileWriter(file.toFile())) {
fileWriter.write("irgend ein Text zum test\nob das so auch geht");
fileWriter.flush();
}
}
}
}
}
throws
jar:file:///D:/data/scm-workspace/anderes/Test/target/testpath/test.zip
D:\data\scm-workspace\anderes\Test\test.txt
/test.txt
Exception in thread "main" java.lang.UnsupportedOperationException
at com.sun.nio.zipfs.ZipPath.toFile(ZipPath.java:597)
at com.oc.test.filesystem.FileSystemWriteTest.main(FileSystemWriteTest.java:31)
What I try to achief is to mashall the JaxB output directly to disk while it is written, not keeping it in Memory until JaxB finished. (My XMLs are rather big so that I may run into an OOME)
My question:
How can I open a suitable Writer or OutputStream fom a FileSystem backed by a ZIP file?
Alternatively:
What other possibility do I have to hide the real write target (folder vs. ZIP file) from JaxB?

Like most other file system operations, opening an OutputStream, Writer, or Channel can be done via the Files utilities class.
See, for example
Files.newOutputStream(Path, OpenOption...)
Files.newBufferedWriter(Path, Charset, OpenOption...) and
Files.newByteChannel(Path, Set<? extends OpenOption>, FileAttribute...)
but note also the higher-level methods like
Files.write(Path, byte[], OpenOption...)
Files.write(Path, Iterable<? extends CharSequence>, Charset, OpenOption...) and
Files.copy(Path, Path, CopyOption...)
which can copy between different filesystems
But note that for streaming directly into a zip file, using ZipOutputStream atop a FileOutputStream may turn out to be more efficient than using the ZipFileSystem.

Related

Get specific file inputstream from TarArchiveInputStream

I have a tar file and which contains many files. I need to get a specific file from tar file and read data from that file.
I am untaring file using the below code and I will read this returned input stream using some other function.
private InputStream unTar(final File inputFile, final File outputDir) throws FileNotFoundException, IOException, ArchiveException {
InputStream versionInputStream = null;
final InputStream is = new FileInputStream(inputFile);
final TarArchiveInputStream debInputStream = (TarArchiveInputStream) new ArchiveStreamFactory().createArchiveInputStream("tar", is);
TarArchiveEntry entry = null;
while ((entry = (TarArchiveEntry)debInputStream.getNextEntry()) != null) {
if (!entry.isDirectory() && entry.getName().equals("version.txt")) {
versionInputStream = new FileInputStream(entry.getFile());
}
}
return versionInputStream;
}
I get null pointer exception when i do versionInputStream = new FileInputStream(entry.getFile());
I know that we can first save this file in directory and then read the file but i dont want to save this file in directory.
Is there some way I can read this file without saving the file to some dir?
There is no file for an entry of an archive you read. TarArchiveEntry's getFile method only returns anything useful when the entry has been created with a File-arg constructor, which only makes sense when creating an archive not reading it.
The stream you are looking for is the TarArchiveInputStream itself after you've positioned it at the entry you want to read, i.e.
if (!entry.isDirectory() && entry.getName().equals("version.txt")) {
versionInputStream = debInputStream;
break;
}
note the break.
The not-yet-released (an no, no release date, yet) Commons Compress 1.21 will contain a new TarFile class that provides random-access to archives read from a seekable source (like a File) and will make your task more convenient.

How do you create non-existing folders/subdirectories when copying a file with Java InputStream?

I have used InputStream to succesfully copy a file from one location to another:
public static void copy(File src, File dest) throws IOException {
InputStream is = null;
OutputStream os = null;
try {
is = new FileInputStream("C:\\test.txt");
os = new FileOutputStream("C:\\javatest\\test.txt");
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = is.read(buf)) > 0) {
os.write(buf, 0, bytesRead);
}
} finally {
is.close();
os.close();
}
}
The problem appears when I add a non-existing folder into the path, for example:
os = new FileOutputStream("C:\\javatest\\javanewfolder\\test.txt");
This returns a NullPointerException error. How can I create all of the missing directories when executing the copy process through Output Stream?
First, if possible I'd recommend you to use the java.nio.file classes (e.g. Path), instead of the File based approach. You will create Path objects by using a file system. You may use the default filesystem, if no flexibility is needed here:
final String folder = ...
final String filename = ...
final FileSystem fs = FileSystems.getDefault();
final Path myFile fs.getPath(folder, filename);
Then your problem is easily solved by a very convenient API:
final Path destinationFolder = dest.getParent();
Files.createDirectories(myPath.getParent());
try (final OutputStream os = Files.newOutputStream(myFile)) {
...
}
The Files.createDirectories() method will not fail if the directory already exists, but it may fail due to other reasons. For example if a file "foo/bar" exists, Files.createDirectories("foo/bar/folder") will most likely not succeed. ;)
Please read the javadoc carefully!
To check, if a path points to an existing directory, just user:
Files.isDirectory(somePath);
If needed, you can convert between File and Path. You will lose file system information, though:
final Path path1 = file1.toPath();
final File file2 = path2.toFile();
You could use Files.createDirectories:
Files.createDirectories(Paths.get("C:\\javatest\\javanewfolder"));
Also, you could use Files.copy to copy file )

Writing a file from byte array into a zip file

I'm trying to write a file name "content" from a byte array into an existing zip file.
I have managed so far to write a text file \ add a specific file into the same zip.
What I'm trying to do, is the same thing, only instead of a file, a byte array that represents a file. I'm writing this program so it will be able to run on a server, so I can't create a physical file somewhere and add it to the zip, it all must happen in the memory.
This is my code so far without the "writing byte array to file" part.
public static void test(File zip, byte[] toAdd) throws IOException {
Map<String, String> env = new HashMap<>();
env.put("create", "true");
Path path = Paths.get(zip.getPath());
URI uri = URI.create("jar:" + path.toUri());
try (FileSystem fs = FileSystems.newFileSystem(uri, env)) {
Path nf = fs.getPath("avlxdoc/content");
try (BufferedWriter writer = Files.newBufferedWriter(nf, StandardOpenOption.CREATE)) {
//write file from byte[] to the folder
}
}
}
(I tried using the BufferedWriter but it didn't seem to work...)
Thanks!
Don't use a BufferedWriter to write binary content! A Writer is made to write text content.
Use that instead:
final Path zip = file.toPath();
final Map<String, ?> env = Collections.emptyMap();
final URI uri = URI.create("jar:" + zip.toUri());
try (
final FileSystem zipfs = FileSystems.newFileSystem(uri, env);
) {
Files.write(zipfs.getPath("into/zip"), buf,
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
}
(note: APPEND is a guess here; it looks from your question that you want to append if the file already exists; by default the contents will be overwritten)
You should use a ZipOutputStream to access the zipped file.
ZipOutputStream lets you add an entry to the archive from whatever you want, specifying the name of the entry and the bytes of the content.
Provided you have a variable named theByteArray here is a snippet to add an entry to an zip file:
ZipOutputStream zos = new ZipOutputStream(/* either the destination file stream or a byte array stream */);
/* optional commands to seek the end of the archive */
zos.putNextEntry(new ZipEntry("filename_into_the_archive"));
zos.write(theByteArray);
zos.closeEntry();
try {
//close and flush the zip
zos.finish();
zos.flush();
zos.close();
}catch(Exception e){
//handle exceptions
}

Properties setting via Servlet

I want to set data from configures.properties via servlet. configures.properties is locating in WEB-INF/classes. This is how I'm getting data:
public static String getDbPassword() {
Properties prop = new Properties();
try {
// load a properties file
InputStream in = Configures.class.getResourceAsStream(INPUT_FILE);
prop.load(in);
// get the property value
return prop.getProperty("dbPassword");
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
}
But how to set? This is how I did:
public static void setDbPassword(String str) {
Properties prop = new Properties();
try {
//load a properties file
InputStream in = Configures.class.getResourceAsStream(INPUT_FILE);
prop.load(in);
prop.setProperty("dbPassword", str);
prop.store(new FileOutputStream(INPUT_FILE), null);
} catch (IOException ex) {
ex.printStackTrace();
}
}
But I'm catching java.io.FileNotFoundException after this. I think it happens after prop.store(new FileOutputStream(INPUT_FILE), null);. How should I modify OutputStream?
UPD:
This is how INPUT_FILE looks:
private static final String INPUT_FILE = "/config.properties";
Your INPUT_FILE is a resource path which getResourceAsStream will resolve relative to the classpath, but you're then trying to pass the same string to the FileOutputStream constructor which will try and treat it as an absolute path relative to the root of the filesystem. These are two different locations.
You could use ServletContext.getRealPath("WEB-INF/classes" + INPUT_FILE) to get the path you need for the FileOutputStream.
But the higher level issue here is that you shouldn't assume that your web application will have write access to its WEB-INF, or even that the directory exists on disk at all (e.g. if the app is running directly from a WAR rather than a directory unpacked on disk). If you want to store configuration data that can change then it should go in a file at a known location outside the web app (the location of this file could be an init parameter) where you know you will have read and write permission. This also stops your changes being overwritten when you deploy a new version of the app.
URL url = Configures.class.getResource(INPUT_FILE);
File file = new File(url.toURI());
OutputStream outputStream = new FileOutputStream(file);
...
prop.store(outputStream, null);
Try a FileWriter instead:
Writer writer = new FileWriter(INPUT_FILE);
...
prop.store(writer, null);
Can you try the following:
While reading the file
URL url = classLoader.getResource(INPUT_FILE);
InputStream in = url.openStream();
While writing :
new FileOutputStream(url.toURI().getPath())
Any files in your webapp should be considered read only. If you want mutable data you should use a database or some other data store.
J2EE advises against manipulating local files as it raises issues of clustering, transactions and security among other things.

Is it possible to create a NEW zip file using the java FileSystem?

I've successfully modified the contents of a (existing) zip file using the FileSystem provided by java 7, but when I tried to create a NEW zip file by this method it fails, with the error message that says: "zip END header not found", it is logical because of the way I'm doing it, first I create the file (Files.createFile) which is a completely empty file, and then I try to access to its file system , and since the file is empty its impossible to find any header inside the zip, my question is is there any way to create a new zip file completely empty using this method?; the hack that I've considered is adding an empty new ZipEntry to a the zip file and then using that new empty file to crate the file system based on it, but i really want to think that the guys of oracle implemented a better (easier) way to do this with nio and the filesystems...
this is my code (the error appears when creating the file system):
if (!zipLocation.toFile().exists()) {
if (creatingFile) {
Files.createFile(zipLocation);
}else {
return false;
}
} else if (zipLocation.toFile().exists() && !replacing) {
return false;
}
final FileSystem fs = FileSystems.newFileSystem(zipLocation, null);
.
.
.
zipLocation is a Path
creatingFile is a boolean
ANSWER:
in my particular case the answer given didn't work appropriately because of the spaces in the path, therefore i have to do it the way i didn't want to:
Files.createFile(zipLocation);
ZipOutputStream out = new ZipOutputStream(
new FileOutputStream(zipLocation.toFile()));
out.putNextEntry(new ZipEntry(""));
out.closeEntry();
out.close();
it does not mean that the given answer is wrong, it just didn't work for my particular case
As described in The Oracle Site:
public static void createZip(Path zipLocation, Path toBeAdded, String internalPath) throws Throwable {
Map<String, String> env = new HashMap<String, String>();
// check if file exists
env.put("create", String.valueOf(Files.notExists(zipLocation)));
// use a Zip filesystem URI
URI fileUri = zipLocation.toUri(); // here
URI zipUri = new URI("jar:" + fileUri.getScheme(), fileUri.getPath(), null);
System.out.println(zipUri);
// URI uri = URI.create("jar:file:"+zipLocation); // here creates the
// zip
// try with resource
try (FileSystem zipfs = FileSystems.newFileSystem(zipUri, env)) {
// Create internal path in the zipfs
Path internalTargetPath = zipfs.getPath(internalPath);
// Create parent directory
Files.createDirectories(internalTargetPath.getParent());
// copy a file into the zip file
Files.copy(toBeAdded, internalTargetPath, StandardCopyOption.REPLACE_EXISTING);
}
}
public static void main(String[] args) throws Throwable {
Path zipLocation = FileSystems.getDefault().getPath("a.zip").toAbsolutePath();
Path toBeAdded = FileSystems.getDefault().getPath("a.txt").toAbsolutePath();
createZip(zipLocation, toBeAdded, "aa/aa.txt");
}

Categories