Validating zip files in Mendix
Introduction
A while ago I ran into a situation where our users were sometimes uploading zip files that were damaged. The zip file could still be opened with a tool like 7zip, but when an attempt is made to extract the documents from the zip file an error would be raised. In most cases all but one of the files could be extracted pointing to a damaged stream inside the zip. While we could ask our users to validate the zip files before they upload them this is an inconvenience to them so I was asked to look into a way to validate the zip file in Mendix when it is uploaded so we can provide immediate feedback to our users.
Design
I first went to the Mendix Marketplace to see if there was an existing widget or module that already address this specific issue, however the only modules available are for opening or creating zip files. A simple solution would be to extract the zip file and see if it succeeds, however this will result in files being created somewhere that would then have to be deleted which is inefficient, so instead I decided to create a Java action and then implement some logic to validate the file without having to extract it.
Implementation
Mendix Java Action
The first step is to define the Java action in Mendix. I simply created a Java action named ValidateZipFile and set up a single parameter that passes a System.FileDocument as this will allow us to pass any type of document in our project to the action.
For the return type of the action we simply set it to a boolean and give it an appropriate name.
Java Code
The code to validate a zip file is actually fairly simple. While searching I have found some rather complex solutions, however I decided to go for the simplest solution I could create that would still successfully validate the file. Below is the method I created to handle the validation.
private boolean isValid(InputStream fileStream) throws IOException
{
ZipInputStream zis = null;
try
{
zis = new ZipInputStream(fileStream);
ZipEntry ze = zis.getNextEntry();
if(ze == null)
{
return false;
}
while(ze != null)
{
ze.getCrc();
ze.getCompressedSize();
ze.getName();
ze = zis.getNextEntry();
}
return true;
}
catch (ZipException e)
{
return false;
}
catch (IOException e)
{
return false;
}
finally
{
if(zis != null)
{
zis.close();
}
}
}
The method takes an InputStream for our zip file and then uses a ZipInputStream from java.util.zip so we can read the stream as a zip file. We then grab the first ZipEntry from the stream, if we don't get a ZipEntry it means that the stream cannot be read as a zip so the file is probably damaged.
If we do get a ZipEntry we then enter a while loop that will keep running while our ze variable is not null. For each ZipEntry we then get the CRC, the compressed size and the name of the entry. If any of these fail a ZipException will be thrown and we know that our file is not valid. If no error occurs we grab the next ZipEntry from the stream and repeat the process. Once we have gone through all of the entries the getNextEntry method will return null and we will break out of the loop. If we get to this point we know that zip file is valid, so we return true. Finally we will simply close the ZipInputStream.
InputStream iStream = null;
try
{
iStream = Core.getFileDocumentContent(getContext(), __File);
return isValid(iStream);
}
finally
{
if(iStream != null)
{
iStream.close();
}
}
The above code is placed directly in the executeAction method that Mendix generated for our Java Action. The __File variable is simply the IMendixObject that Mendix provides for our parameter that we can use to get the InputStream for our file. We then invoke the isValid method defined above for the stream and returns the result to Mendix.
Conclusion
Validating a zip file without extracting it is pretty straightforward using the code outlined above. I tested the logic using most of the damaged zip files we received and it was able to identify each of them correctly. The Java Action is generic and can easily be included into a reusable module.