Question

How to create ZIP archive and attach to case?

Hello!

We have a need to create a ZIP file with files from the case and then attach this file to this case.

We do not use external storage to store files and save files in PRPC using standard methods.

Can anyone suggest how to create an archive in PRPC and add it to сase?

We use PRPC 7.1.9

Thank you in advance!

**Moderation Team has archived post**

This post has been archived for educational purposes. Contents and links will no longer be updated. If you have the same/similar question, please write a new post.

Correct Answer
April 26, 2016 - 11:55am

John Pritchard-williams, Hello!

I, in turn, tried to make his own version

And that's what came out of it:

Test Attachment in case:

test.jpg

Clipboard info about one row:

Clipboard.jpg

I get Property value Category, DateTime, InsHandle, Name from Link-Attachment class and AttachName and AttachStream from Data-WorkAttach-File class

Code in activity:

java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
org.apache.commons.compress.archivers.ArchiveOutputStream logical_zip = null;
try{
logical_zip = new org.apache.commons.compress.archivers.ArchiveStreamFactory().createArchiveOutputStream("zip", baos);
} catch(Exception e) {
throw new PRRuntimeException("Cannot initialize archive");
}


ClipboardProperty cpResults = tools.findPage("pyWorkPage").getProperty("SendFiles");
int i = 1;
for (Iterator instPgList = cpResults.iterator(); instPgList.hasNext();) {


ClipboardProperty objOneRow = (ClipboardProperty) instPgList.next();
ClipboardPage objOneRowPage = objOneRow.getPageValue();
oLog.infoForced("FileName Page: "+ objOneRowPage );


String strOutputPRFileName = objOneRowPage.getProperty("AttachStream").getStringValue();
byte [] data = Base64Util.decodeToByteArray(strOutputPRFileName);
String strOutputFileName = objOneRowPage.getProperty("AttachName").getStringValue();


oLog.infoForced("File Stream: "+ strOutputPRFileName);
  PRFile prDestinationFile = new PRFile(strOutputPRFileName);


PRInputStream prOut = null;

try {
        java.util.zip.ZipEntry entry = new java.util.zip.ZipEntry(strOutputFileName);
  logical_zip.putArchiveEntry(new  org.apache.commons.compress.archivers.zip.ZipArchiveEntry(strOutputFileName));
  logical_zip.write(data);
        logical_zip.closeArchiveEntry();
      } catch(Exception e) {
        throw new PRRuntimeException("Cannot create archive");
    }
  i++;
}
//Send file to client
String s = tools.getLocalizedTextForString("pyCaption","AllDocuments");
HashStringMap aMap = new HashStringMap();
aMap.put("ContentDisposition", "attachment; filename="+s);
aMap.put("ContentType", "application/zip");
try{
  logical_zip.finish();
  baos.close();
  tools.sendFile(baos.toByteArray(),s+".zip",false,null,true);
} catch(Exception e) {
  throw new PRRuntimeException("Error while sending:"+e.getMessage());
}


This is one step in activity.

During the execution of the case, I choose which files you want to send to the archive, they are stored in the Property SendFiles. Consisting of AttaсhStream and AttaсhName values.

In the implementation fulfills tools.SendFile and I can save the file without any errors when opening. I attached a test ZIP to this post.

My question remains, how can I attach a logical archive to case?

Comments

Keep up to date on this post and subscribe to comments

Pega
April 22, 2016 - 11:46am

Hi Thommees,

I can't answer this all in one go: but I will start off the post with what I have so far: and I'll continue to post updates to this as I go along.

I'm using PRPC72 here - but this should pretty much apply to any 7.x version .....(and probably lower).

Cheers

John

PART ONE : How do you Create a ZIP file in Java ?

Since PRPC is built on Java; I find it sometimes helpful to build a simple standalone Java Program to give me something to aim for - its useful also to see what imports are needed an the like.

Here's a basic working example of creating a ZIP file with some dummy content (they are real files; but they all just contain 'hello world'):

// See: http://stackoverflow.com/questions/1091788/how-to-create-a-zip-file-in-java
package com.pega.gcs.zipit;

import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipIt {
    String zipOutputName="test.zip";
    String[] filesInZip={ "one.txt", "b/two.txt", "a/b/three.txt" };
   
    public ZipIt() {
       
    }
   
    public void WriteZip() {
        try {
            String DummyContent="Hello World.";
            byte[] data=DummyContent.getBytes("UTF8");
            FileOutputStream fout=new FileOutputStream(this.zipOutputName);
            ZipOutputStream zout = new ZipOutputStream(fout);
            for (String zipEntryName : this.filesInZip ) {
                ZipEntry ze=new ZipEntry( zipEntryName );
                zout.putNextEntry(ze);
                zout.write(data, 0, data.length);
                zout.closeEntry();
            }
            zout.close();
        }
        catch(Exception e) { throw new RuntimeException(e); }
    }
   
    public static void main(String[] args) {
        ZipIt zipit=new ZipIt();
        zipit.WriteZip();
       
    }  
}

I'll attach this for reference here; along with a Maven 'POM' file (although everything is using Java Libraries, so it's not strictly necessary here).

I'll also attach the simple 'test.zip' it creates to show it works.

PART TWO: How does PRPC store Work Item Attachments ?

I have just created a simple Test Application in PRPC - I logged into the 'User Portal' and created a Work Item : it was assigned the Work Item of 'T-4' and attached three files, a PNG, a PDF and a TEXT File - like this:

00Attachments00.png

So where are these stored ? I checked the PRPC Help, this page is useful for our purposes:

http://host:port/prhelp/procomhelpmain.htm#concepts/casemanagement/con_conceptsattachments.htm

Extract:

Classes for attachments

The system stores attachments in instances of the following concrete classes, subclasses of the Data-Work Attach- class:

    Data-WorkAttach-File — Holds files of any type and format, including PDF files generated by an application.

    Data-WorkAttach-Note — Contains text that is pasted into, or typed directly into, a work item.

    Data-WorkAttach-URL — Records an Internet Uniform Resource Locator (URL) or Uniform Resource Identifier (URI).

    Data-WorkAttach-ScreenShot — Holds screen shot attachments that usually record facts about the work item that were obtained from another system.

    Data-WorkAttach-ScanDocument — Contains a TIFF image file created by a scanner.

    Data-WorkAttach-ECM — File attachments saved in an external enterprise content management (ECM) system, accessed through a Connect CMIS rule.

By default, attachments are stored as rows of the pc_data_workattach table. Attachments are not stored in the same database table as the cases.

So: in fact there are different types of attachments ; so for a first draft of this - I'll stick to the simple 'Data-WorkAttach-File' types for now.

In my Designer Studio - I added the class 'Data-WorkAttach-File' to my 'pinned-classes' (we're going to look here frequently probably...); and double-clcking on this pinned-class - this brings up the instances; we can see the 3 attachments that are associated with the Work Item 'T-4': (I restricted my search to include my Work Class only, which is called 'Zip-Zipit-Work').

00Attachments01.png

Its also useful to check the definition of the Class itself - to see what the 'keys' are :

00Attachments02.png

So the keys are 'pxRefObjectKey' and 'pxAttachKey'; lets now look at an example instance and see what those keys are in that case; on the previous 'instances' screen - I just clicked on an instance

Tip #1: Since we are dealing with large amounts of data here (attachments) - I picked the 'hello.txt' file, because I know that is small, rather than PDF for instance!

Tip #2: Make sure your Pop-Up Blocker isn't stopping you opening up a new window !

Here's a screenshot showing the Instance Data of the 'hello.txt' attachment to Work Item 'T-4':

00Attachments04.png

So here's a snippet of that XML above:

<pagedata>
    <pxInsName>ZIP-ZIPIT-WORK T-4!20160422T144234.228 GMT</pxInsName>
    <pxAttachKey>20160422T144234.228 GMT</pxAttachKey>
    <pzDocumentKey>DATA-WORKATTACH-FILE ZIP-ZIPIT-WORK T-4!20160422T144234.228 GMT</pzDocumentKey>
    <pyRuleHarness>RuleFormLegacy</pyRuleHarness>
    <pxLimitedAccess>Dev</pxLimitedAccess>
    <pyNote>hello</pyNote>
    <pxRefObjectClass>Zip-ZipIt-Work-Ticket</pxRefObjectClass>
    <pyRuleFormStatusTime>20160422T145323.444 GMT</pyRuleFormStatusTime>
    <pxAttachName>hello.txt</pxAttachName>
    <pxRefObjectKey>ZIP-ZIPIT-WORK T-4</pxRefObjectKey>
    <pyRuleFormType>Form</pyRuleFormType>
    <pxCreateDateTime>20160422T144234.226 GMT</pxCreateDateTime>
    <pyShowJavaWindowName>GeneratedJava20160422T145323.449 GMT</pyShowJavaWindowName>
    <pxCreateOpName>Administrator</pxCreateOpName>
    <pxSaveDateTime>20160422T144234.229 GMT</pxSaveDateTime>
    <pxApplication>ZipIt</pxApplication>
    <pxTabLabel>ZIP-ZIPIT-WORK T-4!20160422T144234.228 GMT</pxTabLabel>
    <pxCommitDateTime>20160422T144234.000 GMT</pxCommitDateTime>
    <pyRuleFormStatus>Good</pyRuleFormStatus>
    <pxAttachedByWorkgroup>default@zip</pxAttachedByWorkgroup>
    <pxAttachedByOrganization>zip</pxAttachedByOrganization>
    <pxAttachedByOrgUnit>Unit</pxAttachedByOrgUnit>
    <pzInsKey>DATA-WORKATTACH-FILE ZIP-ZIPIT-WORK T-4!20160422T144234.228 GMT</pzInsKey>
    <!-- [...] -->
    <pxAttachedBy>Admin@zip</pxAttachedBy>
    <pyCategory>File</pyCategory>
    <pzSkipCreateCMISDocument>true</pzSkipCreateCMISDocument>
    <pxAttachedByOrgDivision>Div</pxAttachedByOrgDivision>
    <pyAttachStream>SGVsbG8gV29ybGQgIQ==</pyAttachStream>
</pagedata>

So we can see that the Work Item reference is held as :

<pxRefObjectKey>ZIP-ZIPIT-WORK T-4</pxRefObjectKey>

And the rest of the KEY appears to be based on a DateTime value:

<pxAttachKey>20160422T144234.228 GMT</pxAttachKey>

And the content appears to be held as (base64 encoded) this:

<pyAttachStream>SGVsbG8gV29ybGQgIQ==</pyAttachStream>

PART THREE: How do we access the Attachment Data programmatically ?

Lets try an 'OBJ-BROWSE' in PRPC to fetch the attachment data from our single test Work Item ('T-4').

This Activity does this (I have rather optmisitically named this Activity as 'ZipUpAttachments' - but it doesn't do anything of the sort yet ;-) )

01ObjBrowse01.png

I have set MaxRecords to 10 ; just as a 'safety-valve'  -since we don't want to 'running away' at this point - as Attachment Data may be very large of course.

And at this point: we are NOT fetching the content - just the name of the attachment to confirm we are getting back what we want.

01ObjBrowse02.png

Running this:

01ObjBrowse03.png

01ObjBrowse04.png

Results in this page of 'pxResults' being built for us :it contains the Three Attachments we seek.... (hurrah!)

01ObjBrowse05.png

And here's the XML snippet of this pxResults Page: (which is actually embedded in another page, which contains other meta-data etc) :

<pxResults REPEATINGTYPE="PageList">
    <rowdata REPEATINGINDEX="1">
        <pxAttachName>face.png</pxAttachName>
        <pxRefObjectKey>ZIP-ZIPIT-WORK T-4</pxRefObjectKey>
        <pxObjClass>Data-WorkAttach-File</pxObjClass>
    </rowdata>
    <rowdata REPEATINGINDEX="2">
        <pxAttachName>pega-72-platform-upgrade-guide.pdf</pxAttachName>
        <pxRefObjectKey>ZIP-ZIPIT-WORK T-4</pxRefObjectKey>
        <pxObjClass>Data-WorkAttach-File</pxObjClass>
    </rowdata>
    <rowdata REPEATINGINDEX="3">
        <pxAttachName>hello.txt</pxAttachName>
        <pxRefObjectKey>ZIP-ZIPIT-WORK T-4</pxRefObjectKey>
        <pxObjClass>Data-WorkAttach-File</pxObjClass>
    </rowdata>
</pxResults>


So far , so good - we can identify the attachments for a particular Work Item.

This is as far as I have got at time point; but I would suggest the next things to work out would be:

1. How to get at the Content ? We'll need to convert B64 encoded content to an array of Bytes...

2. How to Generate a ZIP file and write each file of the raw Bytes into a Zip Entry: where are we going to store this ZIP file ? In-memory ? A temporary file ? Are we going to create a Folder Structure or just a 'flat' structure....

3. Then we need to try the last bit of your question: how to re-attach the resultant ZIP.

I'll be back......

April 25, 2016 - 3:27am
Response to JOHNPW_GCS

John Pritchard-williams, thank you.

We will try to do, as you have written.

If you have additions, you will be glad to read it.

Pega
April 26, 2016 - 10:54am
Response to ThommeeS

I have *almost* got this working: I'm not that proud of the results for two reasons:

1. The result is fairly inelegant currently:

- There is lots of passing stuff back and forth through the Parameter Page and Local Vairables: and since I'm using 'Pass current parameter page' throughout - the Parameter Tab for the Top-Level Activity is 'bloated'.

- The Activities (except the top-level one) are all built at "baseclass" (actually, this might be the best place for them: but my original intention was to build it within my Work Pool)

2. The resultant ZIP reports as being corrupt ! (even though I can open it, and extract out the contents without an error....)

Here's what I did anyway: I have attached a RAP file - that contains *just* the Activities used.

You will need to create a Ruleset 'zipit:01-01-01' and a class structure of 'Zip-ZipIt-Work' for these to 'fit'.

Here's the Top-Level Activity - it's an extension of the one previously: does the same 'OBJ-BROWSE' as before: but now loops through the resultant 'FileAttachments.pxResults' page:

one.png

Here's the Parameter Page Definition - this basically covers all the parameters needs by all the Activities in the Call Stack: but the only real one that is needed to be in 'scope' for a caller's point of view is 'WorkItemKey'.

two.png

The way this works is to call four other Activities:

1. CreateZipOutputStream

Which creates a ZipOutpuStream object from a newly created ByteOutputStream object , and passed these object references back to the Caller via the Parameter Page:

Here's the Java Step code (which is collapsed in the screenshot to save space):

java.io.ByteArrayOutputStream bos=new java.io.ByteArrayOutputStream();
java.util.zip.ZipOutputStream zout=new java.util.zip.ZipOutputStream(bos);
oZipOutputStream = zout;
oByteArrayOutputStream = bos;

three.png

four.png

Then for each Attachment Entry , we call 'AddZipEntry' : which looks like this:

five.png

six.png

Here's the Java Step Code- note the use of the 'Base64Util.decodeToByteArray' to decode the Attachment Contents.

if (sZipEntryContents==null) { throw new PRRuntimeException("sZipEntryContents was null"); }
if (oZipOutputStream==null) { throw new PRRuntimeException("oZipOutputStream was null"); }
if (sZipEntryName==null) { throw new PRRuntimeException("sZipEntryName were null"); }

try {
  byte[] data = Base64Util.decodeToByteArray(sZipEntryContents);
  java.util.zip.ZipOutputStream zout=(java.util.zip.ZipOutputStream)oZipOutputStream;
  java.util.zip.ZipEntry ze=new java.util.zip.ZipEntry( sZipEntryName );
  zout.putNextEntry(ze);
  zout.write(data, 0, data.length);
  zout.flush();
  zout.closeEntry();
  oZipOutputStream=zout;

}
  catch(java.io.UnsupportedEncodingException use) { throw new PRRuntimeException(use); }
catch(java.io.IOException ioe) { throw new PRRuntimeException(ioe); }

And once we have finished the loop, we have to close the Streams - and get hold of the resultant Bytes of the ZIP file:

seven.png

eight.png

Here's the Java Code (collapsed in the screenshot):

java.util.zip.ZipOutputStream zout=(java.util.zip.ZipOutputStream)oZipOutputStream;
java.io.ByteArrayOutputStream bos=(java.io.ByteArrayOutputStream)oByteArrayOutputStream;

oZipBytes=(byte[])bos.toByteArray();

try {
zout.flush();
bos.flush();
bos.close();
zout.close();
}
catch(java.io.IOException ioe) { throw new PRRuntimeException(ioe); }

And as a last step; we stream the ZIP file back to the Browser: this is basically a modified version of the 'CODE-PEGA-PDF.VIEW' activity:

nine.png

ten.png

So now running it with a Work Item Identifier (I have subsequently uploaded another Attachment to the Work Item - a WORD file):

Which downloads the file to my browser:

run2.png

But when I open it , Windows doesn't like it:

run3.png

However: I *am* able to unzip it (with errors):

run4.png

But the unzipped directory looks to be ok:

run5.png

And I am able to open up each file and see that they are ok......

I'm not sure what is going on here......

Pega
April 26, 2016 - 11:17am
Response to JOHNPW_GCS

So the corrupt ZIP File does NOT seem to be due to the contents of each individual file in the ZIP: I altered 'AddZipEntry' like this:

  //byte[] data = Base64Util.decodeToByteArray(sZipEntryContents);
  
  String DummyContent="Hello World.";
  byte[] data=DummyContent.getBytes("UTF8");

So that all the attachments (despite having the incorrect file extensions of course) contained the simple 'Hello World' text - but this still results in the 'unexpected end of data' error.

So the issue is then most likely down to the way I'm adding the Zip Entries and/or the way I'm streaming back the resultant ZIP back. (Or something in between...)

April 26, 2016 - 11:50am
Response to JOHNPW_GCS

John Pritchard-williams, Hello!

I, in turn, tried to make his own version

And that's what came out of it:

Test Attachment in case:

test.jpg

Clipboard info about one row:

Clipboard.jpg

I get Property value Category, DateTime, InsHandle, Name from Link-Attachment class and AttachName and AttachStream from Data-WorkAttach-File class

Code in activity:

java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
org.apache.commons.compress.archivers.ArchiveOutputStream logical_zip = null;
try{
logical_zip = new org.apache.commons.compress.archivers.ArchiveStreamFactory().createArchiveOutputStream("zip", baos);
} catch(Exception e) {
throw new PRRuntimeException("Cannot initialize archive");
}


ClipboardProperty cpResults = tools.findPage("pyWorkPage").getProperty("SendFiles");
int i = 1;
for (Iterator instPgList = cpResults.iterator(); instPgList.hasNext();) {


ClipboardProperty objOneRow = (ClipboardProperty) instPgList.next();
ClipboardPage objOneRowPage = objOneRow.getPageValue();
oLog.infoForced("FileName Page: "+ objOneRowPage );


String strOutputPRFileName = objOneRowPage.getProperty("AttachStream").getStringValue();
byte [] data = Base64Util.decodeToByteArray(strOutputPRFileName);
String strOutputFileName = objOneRowPage.getProperty("AttachName").getStringValue();


oLog.infoForced("File Stream: "+ strOutputPRFileName);
  PRFile prDestinationFile = new PRFile(strOutputPRFileName);


PRInputStream prOut = null;

try {
        java.util.zip.ZipEntry entry = new java.util.zip.ZipEntry(strOutputFileName);
  logical_zip.putArchiveEntry(new  org.apache.commons.compress.archivers.zip.ZipArchiveEntry(strOutputFileName));
  logical_zip.write(data);
        logical_zip.closeArchiveEntry();
      } catch(Exception e) {
        throw new PRRuntimeException("Cannot create archive");
    }
  i++;
}
//Send file to client
String s = tools.getLocalizedTextForString("pyCaption","AllDocuments");
HashStringMap aMap = new HashStringMap();
aMap.put("ContentDisposition", "attachment; filename="+s);
aMap.put("ContentType", "application/zip");
try{
  logical_zip.finish();
  baos.close();
  tools.sendFile(baos.toByteArray(),s+".zip",false,null,true);
} catch(Exception e) {
  throw new PRRuntimeException("Error while sending:"+e.getMessage());
}


This is one step in activity.

During the execution of the case, I choose which files you want to send to the archive, they are stored in the Property SendFiles. Consisting of AttaсhStream and AttaсhName values.

In the implementation fulfills tools.SendFile and I can save the file without any errors when opening. I attached a test ZIP to this post.

My question remains, how can I attach a logical archive to case?

Pega
April 26, 2016 - 11:53am
Response to ThommeeS

Nice !

I'm afraid I don't know what a "Logical Archive" is though....

Sorry : I now understand what you mean - you have this in your code:

[...]
logical_zip.putArchiveEntry(new  org.apache.commons.compress.archivers.zip.ZipArchiveEntry(strOutputFileName));
logical_zip.write(data);
logical_zip.closeArchiveEntry();
// etc
[...]


So your question is (I think): how to get this logical_zip attached to a Work Item....

There is an OOTB Activity for PDFs called 'CODE-PEGA-PDF.AttachToWork': which might give you a starting point:

link.png

This appears to mainly rely on the OOTB 'Method' Link-Objects....

I haven't had chance to try this yet though....

April 27, 2016 - 8:22am
Response to JOHNPW_GCS

John Pritchard-williams, Thanks for the help!

Comment row in code:

//tools.sendFile(baos.toByteArray(),s+".zip",false,null,true);

I added a few lines at the end of the code and announced local variables strFileData string type:

try
{
  strFileData = Base64Util.encodeToString(baos.toByteArray());
}
catch (Exception e)
{
  throw new PRRuntimeException("Error encode to string");
}


And call custom activity on second step

1 step: Page-New method - Create pyNewAttachment page (Data-WorkAttach-File) with pyDefault DataTransform

2 step: Property-Set-Special method - for property: .pxRefObjectKey, .pyNote, .pxAttachKey, .pxAttachedBy, .pxAttachName, .pyAttachStream

3 step: Obj-Save method: pyNewFileAttachment page

4 step: Link-Objects method: LinkToPage - pyNewFileAttachment, LinkClass - Link-Attachment, LinkMemo - pyNewFileAttachment.pyNote and LinkPageProperty .pyCategory - param.AttachmentCategory.

5-6 step: Commit and Page-Remove temp page

And Zip is attached to case.

And new task, how we can password protect this zip?

Pega
April 27, 2016 - 9:23am
Response to ThommeeS

Great - thanks for posting your findings here  - this will benefit other people in the community as well !

One thing: be careful about using an explicit 'COMMIT' : if you plan on using this in a PRPC Flow - you should let PRPC take care of the Transaction Boundaries and let it issue any COMMITs on your behalf.

You can generate errors (on-screen and in-log) if the system issues an automatic COMMIT on some data which you have explicitly issued a manual COMMIT previously.

For testing in the Designer Studio, you will have to issue the COMMIT manually as you have done of course. (So: maybe create a WHEN condition to detect when you are running in the Designer Studio; or create a 'wrapper' version for testing which performs the COMMIT).

I quickly checked the Javadocs for the Apache Compress Library you are using; it doesn't seem to support this. (Of course: to password-protect a ZIP; it really means "Encrypt the ZIP" - so this would introduce a layer of extra complexity to the API I guess).

There are other ZIP libraries available - I haven't tried them : but this one might do you ? : https://code.google.com/archive/p/winzipaes/ (Which I *think* also requires Bouncy Castle to perform the encryption).

Do you need a 'real' Password-Protected ZIP (that is: do you need end-users to use WINZIP ,7ZIP etc to unzip the file - and provide the password)  - or just an encrypted file which happens to be a ZIP ? (So the users would have to download, use a decryption tool to reveal a ZIP?)

Thanks again,

John

January 30, 2018 - 12:24am
Response to ThommeeS

The steps explained above works perfectly in my scenario too, but the only issue I had was the sendFile did not send the zipfile for browser downloads.

There was no exceptions in logs or tracer, everything looked like it worked perfectly. But the zipfile was not created.

I had to call this activity using Open URL in Window action. This allows the download during run time in the case.

September 7, 2018 - 1:40am

I am getting compilation error how to resolve this for the above code