I recently found a number of developers asking questions about Zip file handling in Apex, which as most find out pretty soon, does not exist. Statement governor concerns aside, nor is there any binary data types to make implementing support for it possible. Most understandably most want to stay on platform and not call out to some external service for this. And rightly so!
Another developer and I recently had the same need before the API provider decided to handle compression over HTTP. However while subsequently doing some work to get the Metadata API working from Apex, which also requires zip support in places. I came across a partial native solution and in addition to that I’ve also had in mind to try out some day the JSZip Javascript library.
And so I decided to write some blog entries covering these two approaches and hopefully help a few people out…
Approach A: Using Static Resources and Metadata API
A static resource is as the name suggests some that changes rarely in your code, typically used for .js, .css and images. Salesforce allows you to upload these type of files individually or within a zip file. Using its development UI and tools.
Allowing you then reference in your Visualforce pages like this
$URLFOR($Resource.myzip, '/folder/file.js').
Further more you can also do this in Apex.
PageReference somefileRef = new PageReference('/resource/myzip/folder/file.js'); Blob contentAsBlob = somefileRef.getContent(); String contentAsText = contextAsBlob.toString();
So in effect Salesforce does have a built in Zip handler, at least for unzipping files anyway. The snag is, uploading a zip file dynamically once your user has provided it to you. If you review the example code that calls the Metadata API from Apex you might have spotted an example of doing just this. To create a Static Resource you can do the following.
MetadataService.MetadataPort service = createService(); MetadataService.StaticResource staticResource = new MetadataService.StaticResource(); staticResource.fullName = 'test'; staticResource.contentType = 'text'; staticResource.cacheControl = 'public'; staticResource.content = EncodingUtil.base64Encode(Blob.valueOf('Static stuff')); MetadataService.AsyncResult[] results = service.create( new List; { staticResource });
They key parts are the assignment of the ‘contentType’ and ‘content’ members. The ‘content’ member is a Base64 encoded string. In the example above its a static peace of text, however this could easily be the zip file the user has just uploaded for you and then Base64 encoded using the Apex EncodingUtil. You also need to set the ‘contentType’ to ‘application/zip’.
This example presents a user with the usual file upload, you will also need to ask or know what the path of the file is you want to extract. Of course if you know this or can infer it e.g. via file extension such as .docx or .xslx, which uses the Office Open XML format, then your good to go.
I’ve shared the code for this here.
Known Issues: If your zip file contains files with names that contain spaces or special characters, I have found issues with this approach. Since this also applies to URLFOR and generating links in VF pages, I plan to raise a support case to see if this is a bug or limitation. Also keep in mind the user will need to have the Author Apex permission enabled on their profile to call the Metadata API.
Summary and Part 2
This blog entry covered only unzip for known file types, but what if you wanted to have the code inspect the zip contents? Part 2 will extend some of the components I’ve been using in the apex-mdapi to use the JSZip library, examples of which are here and here. In extending those components, I’ll be looking at making them use JavaScript Remoting and local HTML5 file handling to unzip the file locally in the page and transmit the files to the server via an Apex Interface the controller implements. Likewise I want to use the same approach to prepare a zipped file by requesting zip content from the server.
Pingback: Handling Office Files and Zip Files in Apex – Part 2 | andrewfawcett
March 18, 2013 at 4:25 pm
Hi Andy – looks like a wondefully useful bit of code – I’ve imported it to test it out but I get “IO Exception: Unauthorized endpoint, please check Setup->Security->Remote site settings. endpoint = https://c.eu2.visual.force.com/services/Soap/m/25.0” I’m running as a system admin with Author Apex.
The error comes here:
Error is in expression ‘{!upload}’ in component in page importstaticresource
If there anything else I need to set up?
Many thanks for your help.
Alex
March 18, 2013 at 6:55 pm
Hi Alex, thanks for the feedback. Even if you are running as Sys Admin with Author Apex, you still need to provide the URL to the Remote Site settings (Setup>Security>Remote Site Settings). Give it the full URL and it will take what it needs. It sounds odd but you do need to authorise your Apex code to call out to a Salesforce provided endpoint. If you do this, it should work just fine. Note that this approach has some limits on the path into the zip file your looking for. Take a look at Part 2 for something more flexible if you think you might hit this.
March 19, 2013 at 10:53 am
Thanks Andy, that’s done the trick, as well as teaching me a bit more about end points!
Running through part 2 now, which does indeed look more flexible – is the size limit set by JSZip or SalesForce?
March 19, 2013 at 6:34 pm
Excellent, your very welcome! Looking at what the JSLib author says about the size of files it can handle. I suspect Salesforce will limit the size before the JSZip library starts to have an impact.
October 8, 2013 at 10:28 am
Hello sir,
I m new in this technology i have download your example and upload excel file but my question is ,
it is possible to convert excel file to csv file after uploaded…?
plz help me …
Thanx,
Parthiv
August 19, 2013 at 10:32 am
Hi am new to salesforce. If i want to import this to my developer org how would i do it. Please suggest.
August 19, 2013 at 11:56 am
– Download the Force.com IDE from Salesforce
– Download the source via this link as a zip and unzip it https://github.com/financialforcedev/apex-zip/archive/master.zip
– Start a Force.com IDE, from the File menu select Import > General > Existing Projects into Workspace
– Point the wizard to the /apex-zip subfolder, it will prompt you for you DE org login details.
– Once it returns to the Force.com IDE workspace, it should start in the background uploading the files (see bottom right corner), you will also see any errors in Eclipse Problems pane.
March 19, 2013 at 7:37 pm
BTW, would love to know more about your use case, if you can share something?
April 7, 2013 at 11:08 am
Hi Alex,
I am getting below issue while upload the file via Unzip via Static Resource Tab:
Issue is:
System.CalloutException: IO Exception: Unauthorized endpoint, please check Setup->Security->Remote site settings. endpoint = https://c.na15.visual.force.com/services/Soap/m/25.0
Error is in expression ‘{!upload}’ in component in page importstaticresource
Class.MetadataService.MetadataPort.listMetadata: line 1498, column 1
Class.ImportStaticResourceController.upload: line 46, column 1
Could you plz help me for the above issue
March 4, 2014 at 12:08 pm
Hi Andy, I have created a zip attachment with some text files in it. And i am trying to access the attachment from the VF page. Can you please suggest as to how should I go about??
March 5, 2014 at 3:31 pm
Part 2 of this series deals with using some VF components I’ve developed to help with unzipping data, have you taken a look at this https://andyinthecloud.com/2012/12/09/handling-office-files-and-zip-files-in-apex-part-2/?
November 7, 2014 at 1:08 pm
Hii Andrew Fawcett , your post is great … But i found that the library you added in the unzip component are not in the jszip …please please send me the libraries if you have . my email id is nitin.om2@gmail.com thanks in advance
November 8, 2014 at 11:21 am
The jszip javascript library is in the static resource, https://github.com/financialforcedev/apex-zip/blob/master/apex-zip/src/staticresources/jszip.resource (which is a zip file, place the .zip extension on the end and unzip it to take a look at the contents if you like).
November 7, 2014 at 1:18 pm
I have followed your post for unzip but i am unable to make a record on choosing a zip file and i also do not find the deflate.js please answer…
November 8, 2014 at 11:23 am
deflate.js is in the static resource https://github.com/financialforcedev/apex-zip/blob/master/apex-zip/src/staticresources/jszip.resource which is referenced from this component https://github.com/financialforcedev/apex-zip/blob/master/apex-zip/src/components/unzip.component
December 2, 2014 at 1:12 pm
Hi Andrew,
I have requirement in which I need to dynamically create a zip file containing multiple csv file and send the zip file as an attachment. Can you let me know in this dimension.I want to perform the process using apex and not using libraries. Is it necessary to create Static resource
December 3, 2014 at 8:32 am
The only way on Force.com i have found, as per Part 2 of this blog, is to use a JavaScript library hosted in a Visualforce page. In my part 2 blog i try to make this as easy as possible by wrapping all this in Visualforce Components.
The Static Resource approach only works for reading (some) zip files, but even then the draw back is the user needs to have admin permissions as that solution uses the Metadata API.
The best all round solution for create and read, if you can tolerate needing to use the browser to do the processing is the one i describe in part 2 of this blog.
December 4, 2014 at 7:03 pm
This code (based on your sample above) works great in developer console
String resourceName = ‘DealcAssetManagementLayout’;
List resourceList= [SELECT Name, Description, NamespacePrefix, SystemModStamp FROM StaticResource WHERE Name = :resourceName];
String namespace = resourceList[0].NamespacePrefix;
resourceName = ‘/resource/’ + resourceList[0].SystemModStamp.getTime() + ‘/’ + (namespace != null && namespace != ” ? namespace + ‘__’ : ”) + resourceName;
System.debug(resourceName);
PageReference somefileRef = new PageReference(resourceName);
Blob contentAsBlob = somefileRef.getContent();
String layoutString = contentAsBlob.toString();
System.debug(layoutString);
But firing it from a trigger gives me a redirect page
01:53:41:000 USER_DEBUG if (this.SfdcApp && this.SfdcApp.projectOneNavigator) { SfdcApp.projectOneNavigator.handleRedirect(‘https://login.salesforce.com/?ec=302&startURL=%2Fvisualforce%2Fsession%3Furl%3Dhttps%253A%252F%252Fc.na10.visual.force.com%252Fresource%252F1417627556000%252F%252Fresource%252F1417627556000%252FDealcAssetManagementLayout%253Finline%253D1’); } else …
Am I doing something wrong?
December 4, 2014 at 11:06 pm
Maybe this is why ==> System.VisualforceException: Getting content from within triggers is currently not supported. So somefileRef.getContent(); will not work.
https://www.salesforce.com/us/developer/docs/apexcode/Content/apex_System_PageReference_getContent.htm
December 4, 2014 at 11:25 pm
LOL….this works. Well, I hope it helps somebody out there.
List resourceList= [SELECT Name, Body, Description, NamespacePrefix, SystemModStamp FROM StaticResource WHERE Name = :resourceName];
Blob contentAsBlob = resourceList[0].Body;
String layoutString = contentAsBlob.toString();
System.Debug(layoutString);
May 8, 2015 at 10:40 pm
Hi Andrew,
Wonderful article!
I am trying to create a app where you select a zip file on visualforce and we save each file from zip as a attachment to a contact. The zip will mostly contain doc, docx and pdf files.
I could not fully follow your component, maybe because I am just starting with salesforce development…
Please answer below questions if you can:
1) For the above requirement, how should I configure FileReader to read the Zipfile? I am currently using readAsArrayBuffer()
2) I am initiating JSZIP as below, is this correct?
var zip = new JSZip(e.target.result, {type:’base64′});
3) While iterating through the zip files, I call a remote action method, and I am confused as to how the file body should be sent…
From visualforce, should I send the body as zipEntry.asBinary() or asText() or something else?
In the remote method, what variable type should I use to accept the file body? I am getting error if I use Blob, and the file gets corrupted if I use String.
Any help with be greatly appreciated 🙂
Thanks,
Arun K
May 9, 2015 at 2:45 pm
Good news, what you want to do is pretty much the same as the demo code linked in this blog! Suggest you deploy to a test de org and experience it m.
September 17, 2018 at 9:35 pm
Hi Andy,
What is the maximum file size for zipping and downloading files onto user’s local computer?
December 15, 2018 at 1:34 pm
Hi! 15MB > https://developer.salesforce.com/docs/atlas.en-us.pages.meta/pages/pages_js_remoting_limits.htm?search_text=limits