Java NIO ZipFileSystem: "zip END header not found" while creating file system - java

I'm asking this here because googling this error only gives me hits on writing a zip file, while I'm only trying to read it.
I have a unit test where I'm trying to test the following production code:
Map<String, String> zipProps = new HashMap<>();
URI zipUri = URI.create("jar:file:" + itemZipPath.toString());
try (FileSystem zipfiles = FileSystems.newFileSystem(zipUri, zipProps)) {
// do stuff...
} catch (IOException e) {
// log an error
}
However this fails on the line containing the try:
java.util.zip.ZipError: zip END header not found
at com.sun.nio.zipfs.ZipFileSystem.zerror(ZipFileSystem.java:1605)
at com.sun.nio.zipfs.ZipFileSystem.findEND(ZipFileSystem.java:1021)
at com.sun.nio.zipfs.ZipFileSystem.initCEN(ZipFileSystem.java:1030)
at com.sun.nio.zipfs.ZipFileSystem.<init>(ZipFileSystem.java:130)
at com.sun.nio.zipfs.ZipFileSystemProvider.newFileSystem(ZipFileSystemProvider.java:117)
at java.nio.file.FileSystems.newFileSystem(FileSystems.java:326)
at java.nio.file.FileSystems.newFileSystem(FileSystems.java:276)
at com.company.PageCommandHandler$ProvisioningSteps.getItemModel(PageCommandHandler.java:105)
I've tried creating the zipfile using both OSX's zip utility and using jar cvf but both fail (the output of file <filename> differs slightly however).
All the information about this error I can find relates to creating a zipfile using Java NIO, but as you can see I'm only doing a read (verifying the presence of a certain file inside the ZIP for now).
Any thoughts on what is going wrong here?

I've met exactly the same error. Java 8. The reason was, by mistake I created an empty file, not only object:
File zipFile = new File(path);
zipFile.createNewFile();
An then passed this path to
URI uri = URI.create("jar:file:" + zipFile.getAbsolutePath());
To fix it, I did not create file itself, only created a File object:
File zipFile = new File(path);
URI uri = URI.create("jar:file:" + zipFile.getAbsolutePath());
To make it more reliable I would offer to delete file first if it exists:
File zipFile = new File(path);
//Caused by: java.util.zip.ZipError: zip END header not found
if (zipFile.exists()){
try {
Files.delete(Paths.get(zipFile.getAbsolutePath()));
} catch (IOException e) {
throw new IllegalStateException(
"Could not delete file.", e);
}
}
...
URI uri = URI.create("jar:file:" + zipFile.getAbsolutePath());
Probably in some case the solution with deletion of the file is not acceptable. For instance, if you add to the same zip file several entries. But in my use case it is OK.

I tried using the normal ZipInputStream and related classes, but kept having issues, so the problem did not seem related to NIO.
A colleague of mine found this question on SO: Extracting zipped file from ResourceStream throws error "Invalid stored block lengths"
So I tried adding this snippet to my pom.xml as well:
<properties>
<project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
</properties>
After this, all problems disappeared and all my tests turned green. I did not revert back to NIO as I was happy enough getting to a working solution, but I'm pretty sure this would solve the problem on NIO as well.
Posted here in hopes that it helps somebody having the same issue some day.

Sometimes it's Gradle issue
Go to YOUR_PROJECT_DIRECTORY\android\gradle\wrapper
Open the file
gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
Last line update with Gradle
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
Then it will work
Other options you can check now
\android\build.gradle
dependencies {
classpath 'com.android.tools.build:gradle:3.5.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.5'
}
to
dependencies {
classpath 'com.android.tools.build:gradle:4.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.5'
}

Related

java.nio.file.NoSuchFileException When File.transferTo() is called

I've recently inherited a Java API and am having trouble with file uploads. Unfortunately, Java isn't a language I have much experience in so I'm a bit stumped by this.
The MultiPartFile is being received ok, and I can find the file in the temp directory, but when I try to use File.transferTo() to create the final file I just get the below error;
java.nio.file.NoSuchFileException: C:\Users\myUser\AppData\Local\Temp\undertow3706399294849267898upload -> S:\Dev\PolicyData\Temp.xlsx
As I mentioned the temp undertow file exists, and the directory on the S drive also exist, (but there's no Temp.xlsx as my understanding is this should be created by transferTo()). Any solutions I've found to this problem so far are resolved using absolute file paths.
This is a simplified version of the code but the error remains the same.
SpringBoot framework is "1.5.3.RELEASE", running Java 1.8.0_131
ResponseEntity handleFileUpload(#RequestPart(name = "file") MultipartFile file, #PathVariable Long stageFileTypeId) {
if (!file.isEmpty()) {
try {
String filePath = "S:\\Dev\\PolicyData\\Temp.xlsx";
log.info("Upload Path = {}", filePath);
File dest = new File(filePath);
file.transferTo(dest);
return ResponseUtil.wrapOrNotFound(Optional.ofNullable(filePath));
}
catch (Exception ex) {
log.error("An error has occurred uploading the file", ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
else {
log.error("An error has occurred, no file was received");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
If you need any more information please let me know.
Thanks,
Neil
The API for MultipartFile is a bit tricky. The transferTo(File) method javadoc states that (bold are mine):
This may either move the file in the filesystem, copy the file in the
filesystem, or save memory-held contents to the destination file. If
the destination file already exists, it will be deleted first.
If the target file has been moved in the filesystem, this operation
cannot be invoked again afterwards. Therefore, call this method just
once in order to work with any storage mechanism.
It seems that the Undertow implementantion already called it to move the in-memory uploaded file to "C:\Users\myUser\AppData\Loca\Temp\undertow3706399294849267898upload" so another transferTo is failing.
I came across the same problem using javax.servlet.http.Part in a Wildfly containter with Undertow.
If you are using Spring framework >= 5.1, you could try the Multipart.transferTo(Path) method, using dest.toPath()
Or you can copy from the inputStream, with something like this:
try (InputStream is = multipartFile.getInputStream()) {
Files.copy(is, dest.toPath());
}

Apache CLI, Executable jar, classLoader().getResource()

My goal is to use Apache CLI with an executable jar file to read in a text file, perform string manipulations, and then write to a CSV. You would execute the tool in the terminal like this:
$ java -jar my-tool-with-dependencies.jar -i input.txt -o output.csv
I've written tests for this functionality and those tests are passing. The test input text file is located in src/test/resources/. The following test is passing:
#Test
public void testWordockerWriteCsvFileContents() {
// Make sure the csv file contains contents
Wordocker w = new Wordocker();
String intext = "/textformat/example_format.txt";
String outcsv = "/tmp/foo.csv";
w.writeCsvFile(intext, outcsv);
try {
Reader in = new FileReader(outcsv);
Iterable<CSVRecord> records = CSVFormat.DEFAULT.parse(in);
for (CSVRecord record : records) {
assertTrue(record.toString().length() > 0);
}
} catch(FileNotFoundException e){
assertTrue(false);
} catch(IOException e) {
assertTrue(false);
}
File file = new File(outcsv);
if (file.exists()) {
file.delete();
}
}
We I compile my jar files with dependencies using mvn clean compile assembly:single then I raise the following FileNotFoundException:
// Get file from resources folder
URL resourceURL = ParseDoc.class.getClassLoader().getResource(fileName);
if (resourceURL == null) {
throw new FileNotFoundException(fileName + " not found");
}
file = new File(resourceURL.getFile());
This leads me to believe that there is an issue with where ParseDoc.class.getClassLoader().getResource(fileName); is looking for the file. I'm aware of related questions which have been asked. Related questions are the following:
Strange behavior of Class.getResource() and ClassLoader.getResource() in executable jar
What is the difference between Class.getResource() and ClassLoader.getResource()?
MyClass.class.getClassLoader().getResource(“”).getPath() throws NullPointerException
this.getClass().getClassLoader().getResource(“…”) and NullPointerException
getResourceAsStream returns null
None of these questions appear to ask about how to use an executable jar with Apache CLI. I think the basic issue is that the filepath given by my command line argument cannot be found by URL resourceURL = ParseDoc.class.getClassLoader().getResource(fileName);.
Please let me know what you think. Thank you for your time.
I'm posting this as answer as well after discussion via comments:
Classloader.getResource() is only fetching files that are packaged as part of the Jar-file or located in the classpath-folders.
For reading a normal file you would use something like your first example, i.e. FileReader or FileInputStream or simply pass a java.io.File depending on what the library that you are trying to use supports.

In Java, why does class.getResource("/path/to/res"); not work in the runnable jar when copying files to the system?

I have been working on a project that requires the user to "install" the program upon running it the first time. This installation needs to copy all the resources from my "res" folder to a dedicated directory on the user's hard drive. I have the following chunk of code that was working perfectly fine, but when I export the runnable jar from eclipse, I received a stack trace which indicated that the InputStream was null. The install loop passes the path of each file in the array list to the export function, which is where the issue is (with the InputStream). The paths are being passed correctly in both Eclipse and the runnable jar, so I doubt that is the issue. I have done my research and found other questions like this, but none of the suggested fixes (using a classloader, etc) have worked. I don't understand why the method I have now works in Eclipse but not in the jar?
(There also exists an ArrayList of File called installFiles)
private static String installFilesLocationOnDisk=System.getProperty("user.home")+"/Documents/[project name]/Resources/";
public static boolean tryInstall(){
for(File file:installFiles){
//for each file, make the required directories for its extraction location
new File(file.getParent()).mkdirs();
try {
//export the file from the jar to the system
exportResource("/"+file.getPath().substring(installFilesLocationOnDisk.length()));
} catch (Exception e) {
return false;
}
}
return true;
}
private static void exportResource(String resourceName) throws Exception {
InputStream resourcesInputStream = null;
OutputStream resourcesOutputStream = null;
//the output location for exported files
String outputLocation = new File(installFilesLocationOnDisk).getPath().replace('\\', '/');
try {
//This is where the issue arises when the jar is exported and ran.
resourcesInputStream = InstallFiles.class.getResourceAsStream(resourceName);
if(resourcesInputStream == null){
throw new Exception("Cannot get resource \"" + resourceName + "\" from Jar file.");
}
//Write the data from jar's resource to system file
int readBytes;
byte[] buffer = new byte[4096];
resourcesOutputStream = new FileOutputStream(outputLocation + resourceName);
while ((readBytes = resourcesInputStream.read(buffer)) > 0) {
resourcesOutputStream.write(buffer, 0, readBytes);
}
} catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
} finally {
//Close streams
resourcesInputStream.close();
resourcesOutputStream.close();
}
}
Stack Trace:
java.lang.Exception: Cannot get resource "/textures\gameIcon.png" from Jar file.
All help is appreciated! Thanks
Stack Trace:
java.lang.Exception: Cannot get resource "/textures\gameIcon.png" from Jar file.
The name if the resource is wrong. As the Javadoc of ClassLoader.getResource(String) describes (and Class.getResourceAsStream(String) refers to ClassLoader for details):
The name of a resource is a /-separated path name that identifies
the resource.
No matter whether you get your resources from the File system or from a Jar File, you should always use / as the separator.
Using \ may sometimes work, and sometimes not: there's no guarantee. But it's always an error.
In your case, the solution is a change in the way that you invoke exportResource:
String path = file.getPath().substring(installFilesLocationOnDisk.length());
exportResource("/" + path.replace(File.pathSeparatorChar, '/'));
Rename your JAR file to ZIP, uncompress it and check where did resources go.
There is a possibility you're using Maven with Eclipse, and this means exporting Runnable JAR using Eclipse's functionality won't place resources in JAR properly (they'll end up under folder resources inside the JAR if you're using default Maven folder names conventions).
If that is the case, you should use Maven's Assembly Plugin (or a Shade plugin for "uber-JAR") to create your runnable JAR.
Even if you're not using Maven, you should check if the resources are placed correctly in the resulting JAR.
P.S. Also don't do this:
.getPath().replace('\\', '/');
And never rely on particular separator character - use java.io.File.separator to determine system's file separator character.

Java 7 fails to create a file on Win7 with a 230-character path

I had some new code using the commons-io FileUtils.openOutputStream(File) method, for a file that doesn't exist at the point of the call. This was failing with a "FileNotFoundException". I first thought this was a bug in commons-io, but then I realized that it's just calling "new FileOutputStream(file, append)", which is also supposed to create the file if it doesn't exist.
I then added code right before my call to FileUtils.openOutputStream(File) like the following:
if (!file.exists()) {
logger.info("Parent file exists: " + file.getParentFile().exists());
try {
file.createNewFile();
}
catch (Exception ex) {
logger.error("Creating file failed", ex);
}
}
This prints "true" for the parent file, and then "java.io.IOException: The system cannot find the path specified". I googled for this situation, and some people were hitting this if they went past the supposed 260 character limit for a file path on Windows. I thought that might be relevant, but my file path is only 230 characters long.
I also tried an experiment of trying to "touch" the same file path in my Cygwin bash shell, and it had no trouble doing that.
Update:
So I took the partial advice of trying to use Paths & Files to do this instead of just "File". My incoming parameter is a "File", so I can't do anything about that. I added the following code:
try {
Path path = Paths.get(file.getAbsolutePath()).toAbsolutePath();
if (!Files.exists(path.getParent())) {
Files.createDirectories(path);
}
file = Files.createFile(path).toFile();
}
catch (Exception ex) {
logger.error("Failed to create file");
}
What's curious is that this doesn't give me a better error message. In fact, it doesn't give me any error message, because it doesn't fail. It appears that NIO is taking a very different path to creating the file than the regular File object.
Update:
What is now working fine is the following:
file = Paths.get(file.getAbsolutePath()).toAbsolutePath().toFile();
try {
Path path = file.toPath();
if (!Files.exists(path.getParent())) {
Files.createDirectories(path);
}
if (!file.exists()) {
file = Files.createFile(path).toFile();
}
}
catch (Exception ex) {
logger.error("Failed to create file");
}
What's curious is that I should be able to remove that first line, which is essentially converting a relative path to an absolute path. My test run creates 50 or so files in the process. I tried commenting out that line and then clearing out my output tree and running the test. It got the following exception attempting to create the first file:
java.nio.file.AccessDeniedException: build\gen1\org\opendaylight\yang\gen\v1\urn\opendaylight\params\xml\ns\yang\pcep\types\rev131005\vs\tlv\vs\tlv\VendorPayload.java
at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:83)
What the heck?
Also note that I never did remove the older code that uses "File.createNewFile()", I just put the "Files" code before that, and the old code checks for "!file.exists()", so theoretically the old code would only execute if the new code somehow didn't create the file. On this first file, since the NIO creation failed, the file still didn't exist, and it went through the old creation code, which SUCCEEDED.
And even stranger, I let the test case run to the next file, and that failed in the NEW code with:
java.nio.file.FileAlreadyExistsException: build\gen1\org\opendaylight\yang\gen\v1\urn\opendaylight\params\xml\ns\yang\pcep\types\rev131005\vs\tlv\VsTlv.java
Note that the only way that block could have gotten that exception is if it executed the "Files.createFile(path).toFile()" line, and the only way it could have gotten to that line is if "!file.exists()" was TRUE, which means that the file did not exist. my brain is starting to melt. Also note that while I'm sitting at this breakpoint, I examined the file system, and that file does not exist.
This is 2015 and you say that you use Java 7.
Don't use File. Use this instead:
final Path path = Paths.get("....").toAbsolutePath();
// use Files.exists(path.getParent()) to check for the existence;
// if it doesn't exist use Files.createDirectories() on it
Files.createFile(thePath);
If the operation fails, you will at least get a meaningful exception telling you why it fails.
This is 2015. Drop. File. Now.

Copy file exception with TrueZIP

Here is my code to copy a war file to another using TrueZIP.
TFile srcFile = new TFile(sourceFilePath);
TFile destFile = new TFile(destFilePath);
if (!destFile.getParentFile().exists()) {
destFile.getParentFile().mkdirs();
}
try {
srcFile.cp_rp(destFile);
TVFS.umount();
} catch (IOException e) {
e.printStackTrace();
}
For example, source file location:
I:\Code-Base\test.7.2.0\projects\test\main\branches\test.7.2.0_for_test\ui\portlets\dist\portlets.war\WEB-INF\server-config.wsdd
and destination location:
D:\deploy\work\237798_237980\web\deploy\prtlets.war\WEB-INF\server-config.wsdd
I've already checked that both paths exists, but I get an InputException error::
de.schlichtherle.truezip.io.InputException: de.schlichtherle.truezip.fs.FsReadOnlyArchiveFileSystemException: This is a read-only archive file system!
at de.schlichtherle.truezip.socket.IOSocket.copy(IOSocket.java:102)
at de.schlichtherle.truezip.file.TBIO.cp0(TBIO.java:221)
at de.schlichtherle.truezip.file.TBIO.cp_r0(TBIO.java:179)
at de.schlichtherle.truezip.file.TBIO.cp_r(TBIO.java:138)
at de.schlichtherle.truezip.file.TFile.cp_rp(TFile.java:3210)
at com.accela.work.WorkThread.run(WorkThread.java:110)
at com.accela.work.Worker.getUpgradePackageByVersion(Worker.java:162)
at com.accela.work.Main.generateUpgradePackage(Main.java:114)
at com.accela.work.Main.getUpgradePackageByVersion(Main.java:107)
at com.accela.work.Main.main(Main.java:75)
Caused by: de.schlichtherle.truezip.fs.FsReadOnlyArchiveFileSystemException: This is a read-only archive file system!
at de.schlichtherle.truezip.fs.FsReadOnlyArchiveFileSystem.mknod(FsReadOnlyArchiveFileSystem.java:54)
at de.schlichtherle.truezip.fs.FsBasicArchiveController$1Output.mknod(FsBasicArchiveController.java:273)
at de.schlichtherle.truezip.fs.FsBasicArchiveController$1Output.getLocalTarget(FsBasicArchiveController.java:220)
at de.schlichtherle.truezip.fs.FsBasicArchiveController$1Output.getLocalTarget(FsBasicArchiveController.java:217)
at de.schlichtherle.truezip.fs.FsContextController$Output.getLocalTarget(FsContextController.java:296)
at de.schlichtherle.truezip.fs.FsContextController$Output.getLocalTarget(FsContextController.java:280)
at de.schlichtherle.truezip.socket.DelegatingOutputSocket.getLocalTarget(DelegatingOutputSocket.java:47)
at de.schlichtherle.truezip.socket.DelegatingOutputSocket.getLocalTarget(DelegatingOutputSocket.java:21)
at de.schlichtherle.truezip.socket.DelegatingOutputSocket.getLocalTarget(DelegatingOutputSocket.java:47)
at de.schlichtherle.truezip.socket.DelegatingOutputSocket.getLocalTarget(DelegatingOutputSocket.java:21)
at de.schlichtherle.truezip.fs.FsSyncController$Output.getLocalTarget(FsSyncController.java:421)
at de.schlichtherle.truezip.fs.FsSyncController$Output.getLocalTarget(FsSyncController.java:408)
at de.schlichtherle.truezip.fs.FsLockController$Output$1GetLocalTarget.call(FsLockController.java:498)
at de.schlichtherle.truezip.fs.FsLockController$Output$1GetLocalTarget.call(FsLockController.java:495)
at de.schlichtherle.truezip.fs.FsLockController.locked(FsLockController.java:316)
at de.schlichtherle.truezip.fs.FsLockController.writeLocked(FsLockController.java:268)
at de.schlichtherle.truezip.fs.FsLockController$Output.getLocalTarget(FsLockController.java:501)
at de.schlichtherle.truezip.fs.FsLockController$Output.getLocalTarget(FsLockController.java:484)
at de.schlichtherle.truezip.socket.DelegatingOutputSocket.getLocalTarget(DelegatingOutputSocket.java:47)
at de.schlichtherle.truezip.socket.DelegatingOutputSocket.getLocalTarget(DelegatingOutputSocket.java:21)
at de.schlichtherle.truezip.fs.FsFalsePositiveArchiveController$1Output$GetLocalTarget.call(FsFalsePositiveArchiveController.java:374)
at de.schlichtherle.truezip.fs.FsFalsePositiveArchiveController$1Output$GetLocalTarget.call(FsFalsePositiveArchiveController.java:367)
at de.schlichtherle.truezip.fs.FsFalsePositiveArchiveController$TryChild.call(FsFalsePositiveArchiveController.java:507)
at de.schlichtherle.truezip.fs.FsFalsePositiveArchiveController.call(FsFalsePositiveArchiveController.java:104)
at de.schlichtherle.truezip.fs.FsFalsePositiveArchiveController$1Output.getLocalTarget(FsFalsePositiveArchiveController.java:364)
at de.schlichtherle.truezip.fs.FsFalsePositiveArchiveController$1Output.getLocalTarget(FsFalsePositiveArchiveController.java:348)
at de.schlichtherle.truezip.socket.InputSocket.getPeerTarget(InputSocket.java:50)
at de.schlichtherle.truezip.fs.FsBasicArchiveController$1Input.getDelegate(FsBasicArchiveController.java:199)
at de.schlichtherle.truezip.socket.DelegatingInputSocket.getBoundSocket(DelegatingInputSocket.java:43)
at de.schlichtherle.truezip.socket.DelegatingInputSocket.newInputStream(DelegatingInputSocket.java:63)
at de.schlichtherle.truezip.fs.FsContextController$Input.newInputStream(FsContextController.java:273)
at de.schlichtherle.truezip.fs.FsResourceController$Input.newInputStream(FsResourceController.java:242)
at de.schlichtherle.truezip.socket.DelegatingInputSocket.newInputStream(DelegatingInputSocket.java:63)
at de.schlichtherle.truezip.fs.FsSyncController$Input.newInputStream(FsSyncController.java:378)
at de.schlichtherle.truezip.fs.FsLockController$Input$1NewInputStream.call(FsLockController.java:455)
at de.schlichtherle.truezip.fs.FsLockController$Input$1NewInputStream.call(FsLockController.java:452)
at de.schlichtherle.truezip.fs.FsLockController.locked(FsLockController.java:328)
at de.schlichtherle.truezip.fs.FsLockController.writeLocked(FsLockController.java:268)
at de.schlichtherle.truezip.fs.FsLockController$Input.newInputStream(FsLockController.java:459)
at de.schlichtherle.truezip.fs.FsFinalizeController$Input.newInputStream(FsFinalizeController.java:177)
at de.schlichtherle.truezip.fs.FsFalsePositiveArchiveController$1Input$NewInputStream.call(FsFalsePositiveArchiveController.java:333)
at de.schlichtherle.truezip.fs.FsFalsePositiveArchiveController$1Input$NewInputStream.call(FsFalsePositiveArchiveController.java:326)
at de.schlichtherle.truezip.fs.FsFalsePositiveArchiveController$TryChild.call(FsFalsePositiveArchiveController.java:507)
at de.schlichtherle.truezip.fs.FsFalsePositiveArchiveController.call(FsFalsePositiveArchiveController.java:104)
at de.schlichtherle.truezip.fs.FsFalsePositiveArchiveController$1Input.newInputStream(FsFalsePositiveArchiveController.java:323)
at de.schlichtherle.truezip.socket.IOSocket.copy(IOSocket.java:100)
... 9 more
TrueZIP does a simple test to check if the archive file is writable. If this test fails, the archive file system is set read-only as indicated by the exception.
In most cases, this is simply an issue with the access permissions. But Windows is particularly bitchy. For example, if there is another tool concurrently accessing the archive file (many Explorer plug-ins do this) then the file is effectively read-only, too.
So please stay away from the archive file (and best, its directory) while the operation is running.
You cannot do a replace on a read-only file, because you would have to delete it, i.e. write to it.
Make sure your destFile is writeable.

Categories