Sunday 25 February 2024

Zip Handling in Apex, Spring '24 Developer Preview

Image generated by DALL-E 3, based on a prompt by Bob Buzzard

Introduction

The Spring '24 release of Salesforce had some great new features and improvements, but for me some of the Beta/Developer Preview features were more interesting - for example the scratch org snapshots beta that I wrote about a few weeks ago.

I've been developing on the Salesforce platform for close to 16 years now, and I've lost count of the number of different approaches that I've taken to handle zip files. Sometimes on the server, sometimes on the client in JavaScript, but always using custom solutions that didn't perform fantastically well. The new functionality that is native to Apex won't solve this problem - governor limits still apply - that fact that it isn't implemented in Apex itself should mean that a little more processing can be wrung out of the transaction.

Trying it Out

This is a departure from previous developer preview functionality that I've tried in the past, as it's available in scratch orgs only. I'm onside with this approach as it feels like it will allow the Apex development team to be more agile and give us access to changes earlier. Anything that does go awry will disappear in a maximum of 30 days, so there won't be loads of developer editions left behind needing to be patched up. I'll take a bit of additional effort tracking down issues if it means I get my hands on things earlier.

You need to enable support for zip file processing via the features property in your scratch org definition, specifying ZipSupportInApex :

  "features": ["EnableSetPasswordInApi", "ZipSupportInApex"],

Then create a new scratch org and you are off to the races.

What Worked

Creating a zip file and then extracting it works great - I did this through some execute anonymous Apex and was pleasantly surprised there were no further hoops to jump through. As this is in developer preview I didn't expend a huge amount of effort, just adding a single entry created from a String via a Blob:

public void SimpleZip()
{
    Compression.ZipWriter writer=new Compression.ZipWriter();
    writer.addEntry('zs.csv', Blob.valueOf('Name, Title\nKeir Bowden, CEO'));
    Blob zipFile=writer.getArchive();

    ContentVersion contentVer = new ContentVersion(
        VersionData =zipFile,
        Title = 'zipsample.zip',
        Description = 'Zip Sample',
        PathOnClient = '/zipsample.zip'
    );

    insert contentVer;
}

Once I'd done this, I could see the zip and download it to extract files:

And to extract the file from the zip and debug the contents in Apex :

public void SimpleExtract()
{
    ContentVersion contentVer=[select Id, VersionData, Title, Description, PathOnClient
                                from ContentVersion
                                where Title='zipsample.zip'];

    Blob zipBody = contentVer.VersionData;
    Compression.ZipReader reader = new Compression.ZipReader(zipBody);
    Compression.ZipEntry csvEntry = reader.getEntry('zs.csv');
    Blob csv = reader.extract(csvEntry);

    System.debug('CSV body = ' + csv.toString());
}

Gave me the contents of the file that I'd created from my String :


What Didn't Work

The demo that I was actually trying to put together was the ability to choose a zip file from the content library and inspect the contents via the UI. Selecting the file was all well and good, but once I tried to extract the details of the files and wrap them in an inner class, I was out of luck.

In case it was something about my processing of the entries, I tried just debugging them :
@AuraEnabled(cacheable=true)
public static List<Entry> GetZipEntries(Id contentVersionId)
{
    List<Entry> entries=new List<Entry>();
    try 
    {
        ContentVersion contentVer=[select Id, VersionData, Title, Description, PathOnClient
                                    from ContentVersion
                                    where Id=:contentVersionId];

        Blob zipBody = contentVer.VersionData;
        Compression.ZipReader reader = new Compression.ZipReader(zipBody);
        for (Compression.ZipEntry entry : reader.getEntries())
        {
            System.debug('Entry = ' + entry.getName());
        }
    }
    catch (Exception e)
    {
        System.debug(e);
    }

    return entries;
}
but this failed in the same, odd way. No exceptions, no errors, just an exit of the server side call when I invoked ZipReader.getZipEntries().



In case it was something Lightning Components related I dusted off my Visualforce skills and tried a custom controller :
public List<Entry> getEntries()
{
    if (null==this.entries)
    {
        entries=new List<Entry>();
        
        String cvId=ApexPages.currentPage().getParameters().get('cvid');
        System.debug('Id = ' + cvId);
        ContentVersion contentVer=[select Id, VersionData, Title, Description, PathOnClient
                                    from ContentVersion
                                    where Id=:cvId];

        Blob zipBody = contentVer.VersionData;
        Compression.ZipReader reader = new Compression.ZipReader(zipBody);
        for (Compression.ZipEntry zipEntry : reader.getEntries()) 
        {
            System.debug('Processing entry');
            Entry entry=new Entry();
            entry.name=zipEntry.getName();
            entry.method=zipEntry.getMethod().name();
            entry.lastModifiedTime=zipEntry.getLastModifiedTime();
            entries.add(entry);
        }
    }

    return entries;
}
This was slightly more successful, in that it very occasionally showed the list of entries after a hard reload of the page. Most of the time though, it behaved the same and just gave up when I executed ZipReader.getEntries().



Just in case it was something about my zip file, which was an old bootstrap library I had lying around, I copied the same code into a new class and invoked that using execute anonymous.

That worked fine:



So it does appear there is an issue accessing the contents of a zip file in a transaction that originated from a custom UI, or something along those lines anyway. I couldn't find anything in the docs to indicate I shouldn't be trying this, but it is in developer preview so I'd expect a few glitches. As a wise man once said, "I'll take a bit of additional effort tracking down issues if it means I get my hands on things earlier.". 

More Information

No comments:

Post a Comment