I've seen a few forum posts that show how to zip files using both Exec and the XML Publisher PSXP_RPTDEFNMANAGER:Utility app package. Those are great options, but might not fit every scenario. Since the Java API includes support for zip files, let's investigate how we can use it to create or extract zip files.
Java allows developers to create zip files by writing data to a ZipOutputStream. We've used OutputStreams a few times on this blog to write data to files. A ZipOutputStream is just a wrapper around an OutputStream that writes contents in the zip file format. Here is an example of reading a text file and writing it out to a ZipOutputStream
REM ** The file I want to compress;
Local string &fileNameToZip = "c:\temp\blah.txt";
REM ** The internal zip file's structure -- internal location of blah.txt;
Local string &zipInternalPath = "my/internal/zip/folder/structure";
Local JavaObject &zip = CreateJavaObject("java.util.zip.ZipOutputStream", CreateJavaObject("java.io.FileOutputStream", "c:\temp\compressed.zip", True));
Local JavaObject &file = CreateJavaObject("java.io.File", &fileNameToZip);
REM ** We will read &fileNameToZip into a buffer and write it out to &zip;
Local JavaObject &buf = CreateJavaArray("byte[]", 1024);
Local number &byteCount;
Local JavaObject &in = CreateJavaObject("java.io.FileInputStream", &fileNameToZip);
Local JavaObject &zipEntry = CreateJavaObject("java.util.zip.ZipEntry", &zipInternalPath | "/" | &file.getName());
REM ** Make sure zip entry retains original modified date;
&zipEntry.setTime(&file.lastModified());
&zip.putNextEntry(&zipEntry);
&byteCount = &in.read(&buf);
While &byteCount > 0
&zip.write(&buf, 0, &byteCount);
&byteCount = &in.read(&buf);
End-While;
&in.close();
&zip.flush();
&zip.close();To add multiple files to a single zip file, we can convert the above code into a function (preferably a FUNCLIB function) and then call it multiple times, once for each file:
Function AddFileToZip(&zipInternalPath, &fileNameToZip, &zip)
Local JavaObject &file = CreateJavaObject("java.io.File", &fileNameToZip);
REM ** We will read &fileNameToZip into a buffer and write it out to &zip;
Local JavaObject &buf = CreateJavaArray("byte[]", 1024);
Local number &byteCount;
Local JavaObject &in = CreateJavaObject("java.io.FileInputStream", &fileNameToZip);
Local JavaObject &zipEntry = CreateJavaObject("java.util.zip.ZipEntry", &zipInternalPath | "/" | &file.getName());
REM ** Make sure zip entry retains original modified date;
&zipEntry.setTime(&file.lastModified());
&zip.putNextEntry(&zipEntry);
&byteCount = &in.read(&buf);
While &byteCount > 0
&zip.write(&buf, 0, &byteCount);
&byteCount = &in.read(&buf);
End-While;
&in.close();
End-Function;
Local JavaObject &zip = CreateJavaObject("java.util.zip.ZipOutputStream", CreateJavaObject("java.io.FileOutputStream", "c:\temp\compressed.zip", True));
AddFileToZip("folder1", "c:\temp\file1.txt", &zip);
AddFileToZip("folder1", "c:\temp\file2.txt", &zip);
AddFileToZip("folder2", "c:\temp\file1.txt", &zip);
AddFileToZip("folder2", "c:\temp\file2.txt", &zip);
&zip.flush();
&zip.close();The contents to zip doesn't have to come from a static file in your file system. It could come from the database or... well, anywhere. Here is an example of zipping static text. In this example I intentionally left the internal zip file path (folder) blank to show how to create a zip file with no structure.
Local JavaObject &textToCompress = CreateJavaObject("java.lang.String", "This is some text to compress... probably a bloated XML document or something ;)");
Local string &zipInternalFileName = "contents.txt";
Local JavaObject &zip = CreateJavaObject("java.util.zip.ZipOutputStream", CreateJavaObject("java.io.FileOutputStream", "c:\temp\compressed.zip", True));
Local JavaObject &zipEntry = CreateJavaObject("java.util.zip.ZipEntry", &zipInternalFileName);
Local JavaObject &buf = &textToCompress.getBytes();
Local number &byteCount = &buf.length;
&zip.putNextEntry(&zipEntry);
&zip.write(&buf, 0, &byteCount);
&zip.flush();
&zip.close();And, finally, unzipping files. The following example prints the text inside each file from a zip file named "compressed.zip" that contains four fictitious text files named file1.txt, file2.txt, file3.txt, and file4.txt.
Local JavaObject &zipFileInputStream = CreateJavaObject("java.io.FileInputStream", "c:\temp\compressed.zip");
Local JavaObject &zipInputStream = CreateJavaObject("java.util.zip.ZipInputStream", &zipFileInputStream);
Local JavaObject &zipEntry = &zipInputStream.getNextEntry();
Local JavaObject &buf = CreateJavaArray("byte[]", 1024);
Local number &byteCount;
While &zipEntry <> Null
If (&zipEntry.isDirectory()) Then
REM ** do nothing;
Else
Local JavaObject &out = CreateJavaObject("java.io.ByteArrayOutputStream");
&byteCount = &zipInputStream.read(&buf);
While &byteCount > 0
&out.write(&buf, 0, &byteCount);
&byteCount = &zipInputStream.read(&buf);
End-While;
&zipInputStream.closeEntry();
MessageBox(0, "", 0, 0, &out.toString());
/*Else
&log.writeline("&zipEntry is a directory named " | &zipEntry.getName);*/
End-If;
&zipEntry = &zipInputStream.getNextEntry();
End-While;
&zipInputStream.close();
&zipFileInputStream.close();What about unzipping binary files into the file system? I'll let you write that one.
Password protected zip files? Java doesn't make this easy. There are a few Java libraries, but as Chris Rigsby points out here, using non-standard Java classes (including your own) can be hazardous. At this time, it seems the best way to password protect a zip file is to use Exec to call a command line zip program. On Linux with the zip utility, use the -P parameter to encrypt with a password.




10 comments:
Is there a significant benefit to doing it this way instead of calling the system unzip?
@Brett, good question. For some, there may be no difference.
The main benefit is that it doesn't require any system utilities. For those that run on Linux, finding a compression utility or writing a shell script is trivial. For windows, however, there isn't a lot in the GPL/GNU space (except UnxUtils, of course). The example shown here uses the Java API which is guaranteed to be in your PeopleSoft system.
The second benefit is shown in the third code listing. It shows how to stream information directly into a zip file without having to write to a file first. When it comes to distributed processing, servers, load balancing, etc, it is difficult to make assumptions for the file system. Now, what I didn't show was how to stream a zip out to the response object without ever writing to disk. That sounds like a good post for tonight :)
Hi Jim
Few months back we had a similar requirement for one of our client and we end up developing two different scripts for unix and windows .
Maintenance / development of batch script was just a nightmare..
This looks like more cleaner solution.
@Neeraj, Thank you for the feedback. What you mention is a common scenario and why I went the Java/PeopleCode route instead of a batch file.
If you have to password protect the zip files, then the solution is not as clean, but for standard compression, this is a good solution.
Notice that I didn't have to use any reflection with the Java! Just nice, clean method calls.
Hi, your post inspired me to provide online some code I wrote a while back. - More specifically the zip password issue.
I have a free library that provides zipping with or without passwords as well as checking if office documents are encrypted.
The zip password is 'standard' encryption - so compatible with all zipping programs!
The library is Java and there is a wrapper so that it works in PeopleSoft with relative ease.
Example: &zipUtil.CreateZipFolderEncrypted(&FolderPathToZip, &ZipFileToSaveAs, &Password);
http://users.adam.com.au/kane81/PeopleSoft/Utilities/
@Kane81, thanks for sharing!
Hi Jim,
A very useful tutorial.
Thanks for this post. I have expanded this tutorial to create TAR Files in PeopleCode, using JTAR library.
@Raajesh, thanks for sharing!
Hello jim,
I am using the code to zip multiple pdf's into a zip file. But when I unzip the files, the pdf's are under sub-directories.
e.g if a pdf file is under
\\1.1.1\folder1\folder2\OutPut1.pdf
\\1.1.1\folder1\folder2\OutOut2.pdf
then the resultant zip file has the following structure
1.1.1\folder1\folder2\OutPut1.pdf
1.1.1\folder1\folder2\OutPut2.pdf
Is there a way to not include the sub-directories but just the pdf files inside the zip files?
@Narender, yes, just set &zipInternalPath to "".
Post a Comment