In a typical content management or process management system, documents are routed from one user to the next with no guarantee that the contents of the document are secure. After it has been downloaded or checked out, a document might be used maliciously. So let us say we encrypt the document as it comes from the content repository, for example FileNet Content Engine (CE). The document cannot be openly viewed, even if it is tranferred maliciously. It will be viewable only by users who can decrypt it with the help of a policy server.
A typical use case for a digital rights management (DRM) scenerio is shown in Figure 1:
Figure 1. Typical digital rights management scenario
The scenario includes the following steps:
- First you (the user) request to check in a protected document(Base64 encoding and decoding is used in this example).
- The document, which needs to be sent to a repository (in this case FileNet Content Engine), must be decoded to enable text search.
- When you attempt to check-out or download the file, the system encrypts the document, then sends it to you.
- You then contact the policy server provided by a DRM solution provider with the checked-out, encrypted file. After you authenticate at the policy server, the file is decrypted and you can view or modify it as desired.
You will need to customize FileNet P8 in order to accomplish steps 2 and 3.
The solution uses FileNet Web Application Toolkit (WAT), an extensible framework for building web applications that was used to create the FileNet P8 Workplace.
To implement this solution, you should have some knowledge of the different components of the IBM FileNet P8 platform,including Application Engine (AE), Content Engine (CE) and Process Engine (PE). The code accompanying this article has been tested on FileNet P8 4.5.1 and IBM WebSphere Application Server - ND, 6.1.0.25. In addition, you should have familiarity with Eclipse and Java.
How do you achieve the customizations shown in Figure 1? This will depend on which method was used to get the document into the FileNet CE repository:
- Using FileNet P8 Workplace (Application Engine)
- Using FileNet Enterprise Manager (FEM)
- Using a custom application
In the next part of the article, you'll see how to customize the FileNet P8 Workplace so that you can do the following operations:
- Decrypt an encrypted file while adding through FileNet P8 Workplace (WAT).
- Encrypt a file while viewing or checking out the file using FileNet P8 Workplace (WAT).
In the real world there are many third-party vendors who offer DRM solutions. Most of them use an architecture similar to the one you saw in Figure 1.
For the sake of simplicity, we used Base64 in our example to encode and decode a document. In this article, we are considering simple Text files (*.txt). In an actual content management environment, there are be many ways in which document protection can be achieved, for all kinds of documents.
We are using Apache Base64 conversion library commons-codec-1.4.jar (org.apache.commons.codec.binary.Base64)
Document check-in in FileNet P8 Workplace happens through WcmUploadInputStream servlet. This is part of the p8ToolKit.jar,
found at the Websphere Application Server machine where the workplace is deployed, in the following path:
<WAS node Cell path>\ Workplace.ear\app_engine.war\WEB-INF\lib\
You will find the WcmUploadInputStream.java file at this path:
<WAS node Cell path>\Workplace.ear\app_engine.war\AESource
It is part of the com.filenet.wcm.toolkit.server.util package.
WcmUploadInputStream class has a static method getInstance which is overloaded twice:
- The first overload takes HttpServletRequest and the form name as second and third parameters respectively.
- The second overload, shown below, takes the WcmMimeFormParser and HttpServletRequest as second and third parameters respectively.
WcmDataStore is the common parameter for both of the overloaded functions. This parameter holds the user session-specific data store.
As the code examples below illustrate the following steps:
- A static function DecodeFile is added to WcmUploadInputStream.java. This function takes a reference to file object and the filepath where the inputstream has been cached.
- The DecodeFile is called in getInstance.
Listing 1. Document check-in
public static WcmUploadInputStream getInstance(WcmDataStore ds,
WcmMimeFormParser mfp,HttpServletRequest req)throws IOException,Exception
{
WcmUploadInputStream uis = null;
if ( !mfp.isEOD() )
{
String fname = mfp.getSectionParameter(WcmMimeFormParser.INPUT_FILENAME);
if ( fname != null )
{
File theFile = null;
try
{
theFile = UploadDownloadFileUtil.initUploadCacheFile(req.getLocale(),
req.getSession().getServletContext(),null,null);
}
catch (WcmException e)
{
//System.out.println("Hello #2_3");
throw new IOException(wsCacheFileError.toString(req.getLocale()));
}
System.out.println("File name is : " + fname);
System.out.println("File name is : " + theFile.getPath());
FileOutputStream fos = new FileOutputStream(theFile);
mfp.writeSectionContent(fos);
fos.close();
// Start of decoding
File theFile1 = DecodeFile (theFile,fname,req.getLocale());
//End of decoding
uis = new WcmUploadInputStream(
new FileInputStream(theFile1),
theFile1,
mfp.getSectionContentLength(),
mfp.getSectionParameter(WcmMimeFormParser.MIME_TYPE),
//fname,
theFile1.getPath(),
theFile1.getPath(),
true, true, req.getSession().getServletContext());
}
}
return(uis);
}
*******************************************************************************
public static File DecodeFile(File theFile,String fileName2,Locale locale)
throws Exception
{
String fname = theFile.getPath();
FileInputStream fis1 = new FileInputStream(fname);
String fileName2_1= fileName2.substring(fname2+1, fileName2.length());
fileName2 = "C:\\TEMP\\"+fileName2_1;
File theFile1 = new File(fileName2);
FileOutputStream fos1 = new FileOutputStream (theFile1);
int off1 =0;
int bytes_read1;
byte [] buffer1 = new byte[4096];
while ((bytes_read1 = fis1.read(buffer1))!= -1)
{
//byte[] decoded = Base64.decodeBase64(buffer1);
byte[] encoded = WcmEncodingUtil.encodeBase64(locale,nextBytes);
fos1.write(decoded, off1, decoded.length);
}
fos1.close();
fis1.close();
System.out.println("filename2 : "+fileName2);
return theFile1;
}
|
Once the code changes are made, it is time to deploy the code into the workplace following these steps:
- Compile the Java file.
- Deploy the .jar file (in this case p8Toolkit.jar) to the folder where Workplace is deployed
Once the WcmGetContentServlet.java is compiled, it creates two class files. Copy them to the appropriate folders and then update the p8toolkit.jar file with both the class files, using these commands:
- Copying the class file to the appropriate folder:
copy /Y WcmGetContentServlet.class .\com\filenet\wcm\toolkit\server\servlet\ - Updating the jar with the new class file:
jar -uvf p8toolkit.jar com\filenet\wcm\toolkit\server\servlet\WcmUploadInputStream.class - Deploying the jar file back into the application:
copy /Y p8toolkit.jar "C:\Program Files\IBM\WebSphere\AppServer\profiles\AppSrv01\installedApps\idmdev18bNode01Cell\Workplace.ear\app_engine.war\WEB-INF\lib\
Note that all these operations are performed in the AESource directory:
< WAS node Cell path>\Workplace.ear\app_engine.war\AESource
Open and check-out in FileNet P8 Workplace occurs with the WcmGetServlet.java file, part of the com.filenet.wcm.toolkit.server.servlet package. This is the layer where you manipulate the filestream before sending it the user.
The function in question is streamContentToBrowser. This particular function makes a call to a function
called transferHeaders. The response object is sent to this function as shown in the code snippet below.
The transferHeaders function uses this request object and sets the contentLength of
the response object. Our implementation uses Base64 encoding, which increases the size of the encoded file. So we get the
stream encoded, and also calculate the encoded stream length, which is used in the transferHeaders function call.
The encoded stream is sent along the previous normal path. That is, we use it the tranferContent function call,
which writes it to the response stream
So that is the end of document open / check-out.
Listing 2. Document open/check-out
private void streamContentToBrowser(Document doc) throws IOException
{
TransportInputStream inStream = doc.getContentElement(iElement);
response.setHeader("ResultXml", "<?xml version=\"1.0\"?><response>
<errorcode>0</errorcode><description>" +
"Success</description></response>");
String filename = inStream.getFilename();
contentLength = (int) inStream.getContentSize();
sMimeType = inStream.getMimeType();
if (sMimeType != null && sMimeType.equalsIgnoreCase(MimeTypes.FILENET_PUBLISHTEMPLATE))
sMimeType = "text/xml";
if (contentLength <0 && getEnableFiletracking(dataStore,
WcmStringResources.getClientLocale(request)))
{
contentLength= getContentLength(inStream);
nStream = doc.getContentElement(iElement);
}
ServletOutputStream outStream = response.getOutputStream();
// Start of encoding
ByteArrayOutputStream outStream_Encoded = EncodeFile (inStream);
//End of encoding
ByteArrayInputStream inS_en = new ByteArrayInputStream(outStream_Encoded.toByteArray());
contentLength= encodedContentLength;
transferHeaders(filename, contentLength, sMimeType, response);
//transferContent(inStream, outStream);
transferContent(inS_en, outStream);
outStream.close();
inStream.close();
inS_en.close();
}
public static ByteArrayOutputStream EncodeFile(TransportInputStream inStream)
throws Exception
{
InputStream istream = inStream;
int nBytesRead = 0;
encodedContentLength=0;
ByteArrayOutputStream outStream_Encoded= new ByteArrayOutputStream();
OutputStream outFile = new FileOutputStream ("C:\\File_instream.txt");
for (byte[] nextBytes = new byte[8192]; nBytesRead != -1;
nBytesRead = inStream.read(nextBytes))
{
if(nBytesRead>0)
{
//byte[] encoded = Base64.encodeBase64(nextBytes);
byte[] encoded = WcmEncodingUtil.encodeBase64(nextBytes);
encodedContentLength=encodedContentLength+encoded.length;
outStream_Encoded.write(encoded, 0, encoded.length);
//WriteToLog(encoded);
outFile.write(encoded, 0, encoded.length);
}
}
outFile.close();
return outStream_Encoded;
}
|
Our solution leveraged the org.apache.commons.codec.binary.Base64 package for Base64 encoding and decoding.
Refer to the Resources section for more information on the package.
Alternatively you could use the WcmEncodingUtil class which has methods such as
WcmEncodingUtil.encodeBase64 and WcmEncodingUtil.decodeBase64. This class
can be used for the same purpose.
Understanding the underlying architecture
Some aspects of how the FileNet Web Application Toolkit works played an important part in solving this problem, spcifically the call sequence with respect to the save document function.
Figure 2 illustrates the model-view-controller (MVC) pattern used in WAT. For more information, see the FileNet documentation.
Figure 2. Sequence diagram
The modules belonging to each part are as follows:
- Model:
WcmSaveOperationis part of the com.filenet.wcm.apps.server.ui.operation package. This will call the appropriate data provider to do any operation on CE or PE. This class also implements the events that are called by the controller. In this case it implements theonSaveevent. (Also this derives ultimately from a base class calledWcmUiModule)WcmAuthoringDataProvideris part of the com.filenet.wcm.toolkit.server.dp package. The primary job of a data provider is to use to the CE and PE apis. In this implementation, the authoring data provider talks to CE to retrieve, add, or replace documents in CE.
- View:
WcmSaveContent.jspis found at "app_engine.war\UI-INF\jsp\ui\operations" folder in websphere's workplace deployment folder. It renders the HTML output the client. The HTML content is built in the model phase. This page is called from the model phase via a redirect.
- Controller:
WcmSaveContent.jspis found at "app_engine.war\operations" folder in WebSphere's workplace deployment folder. This page configures the controller. During the configuration, events that needs to be called in the model are recovered from the request object.- The controller registers the modules in the model phase. During the registration it calls
the
Initializemethod on each of the modules. In this example it will be WcmSaveOperation and WcmAuthoringDataProvider. - Once the initialization operations on the modules are complete, the controller calls the
handleEventwhich triggers the events. In this case it is theonSaveevent on WcmSaveOperation object.
This article was aimed at solving a potential security issue in a document management scenario, and also
explored a piece of the Web Application Toolkit architecture. There could be scenarios where you don't want to encode and decode some
of the documents in your workflow. This is
an implementation detail that you can figure out how to implement when you go through WcmUploadInputStream.
Note that the article explains only a partial overview sequence of save document. There are many
other modules that are involved, such as WcmUploadInputStream which we have used to
implement the business case. The information in this article provided a beginning as you start to explore this topic, and
a base for further exploration.
Thanks to Phong Chu, Technical Instructor Specialist, ECM Education Services, for reviewing this article and providing valuable comments.
- Learn more about FileNet P8 in the
FileNet product documentation.
- Learn about Base64 encoding in the
Wikipedia entry for Base64.
- Get more information about the
Class Base64 package from Apache used in this article.
- In the
ECM Zone
on developerWorks, get the resources you need to advance your skills in IBM Enterprise Content Management
products.

Bharath N.S. works as a Senior Staff Software Engineer at IBM India Software labs, with over ten years of experience in software development experience based on Microsoft technologies, and more recently, on Java and J2EE technologies. Bharath's hobbies include image processing and exploring new technologies.





