Archive for August, 2011

Read/Write content node(s) or files from/into the CRX repository

Almost all of the content (either as specific types or unstructured) is stored in the CRX repository, as part of the ADEP platform. You may also want to add your custom content into the repository as part of a larger process or workflow, and then retrieve the same (maybe at a later stage).

While you could do this using classic JCR APIs, the Adobe Digital Enterprise Platform provides convenient APIs (via the delivered SDKs) to do so.

Below is a sample CRXFileManager that performs the following using these APIs:

  • Read nodes/files from CRX
  • Write nodes/files into CRX
  • Read a list of nodes/files from CRX, given a folder node

Here’s how the CRXFileManager looks like:


import java.io.BufferedInputStream;
import java.io.InputStream;


import java.util.ArrayList;
import java.util.List;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;


import org.apache.commons.io.IOUtils;
import org.apache.sling.api.resource.ResourceResolver;


import org.springframework.beans.factory.annotation.Autowired;


import com.adobe.livecycle.content.File
import com.adobe.livecycle.content.repository.FileService;
import com.adobe.livecycle.content.sling.ResourceResolverHolder;


public class CRXFileManager {

@Autowired
private FileService fileService;

/**
* Write content (given as input stream) in repository and returns the path.
*/
public String createContentInCRXRepository(InputStream inputStream, String mimeType)
{
try
{
   File file = fileService.createFile(inputStream, mimeType, null, null);
   Session currentSession = getSession();
currentSession.save();
return file.getTempPath();
}
catch (Exception e)
{
// log or throw
}
}


/**
* Read content (given the absolute path) from the repository.
*/
public static byte[] retrieveContentFromCRXRepository(String absPath)
{
byte[] result = null;
try
{
Node ntFileNode = getSession().getNode(absPath);
Node ntResourceNode = ntFileNode.getNode("jcr:content");
InputStream is = ntResourceNode.getProperty("jcr:data").getBinary().getStream();
BufferedInputStream bin = new BufferedInputStream(is);
result = IOUtils.toByteArray(bin);
bin.close();
is.close();
}
catch (Exception ex)
{
// log or throw
}
return result;
}

/**
* Read list of nodes (given the absolute folder path) from the repository.
*/
public static List<Node> retrieveNodesFromCRXRepository(String absFolderPath)
{
List<Node> nodes = new ArrayList<Node>();
try
{
Node ntFolderNode = getSession().getNode(absFolderPath);
NodeIterator ni = ntFileNode.getNodes();
while (ni.hasNext()) {
nodes.add((Node) ni.next(););
}
}
catch (Exception ex)
{
// log or throw
}
return nodes;
}


private static Session getSession() throws RepositoryException {
ResourceResolver resolver = ResourceResolverHolder.getResourceResolver();
return resolver.adaptTo(Session.class);
}

}

Of course, you would need to have the ADEP SDK setup in your eclipse. Specifically, you would need the lc-content-api.jar library from the ADEP SDK (found under \etc\aep\sdks\riaservices\riacore\10.0.0.0\java).

Auditing actions in the Correspondence Management solution

The Correspondence Management solution allows a number of actions, such as Create, Edit/Update, Revert, etc., over assets within the solution. Here is an approach to maintain an audit trail for each such action performed on an asset.

Before you go ahead with this approach, ensure that you have your development environment setup for the solution.

Overview

The overall approach used here is to register an AuditHandler that is registered against each action, such that it is invoked (from within the infrastructure) when the corresponding actions are performed over an asset. The handler can then have any custom implementation for managing the audit log, varying from as simple as a message in a log file to more complex management such as a database tuple.

Implementation

Here are the specific steps you need to take to enable the above.

  1. Create an AuditHandler class, that implements the com.adobe.livecycle.content.repository.action.ActionHandler interface, in the CorrespondenceManagementSolutionTemplate/Services project. You are then required to implement the preProcess() and postProcess() methods which are called before and after an action in performed respectively. One may choose to perform the audit logging in any one (or maybe both) of these methods. Here’s how your AuditHandler skeleton would look like:

    package com.adobe.icc; 
    
    import java.util.Map;
    import com.adobe.icc.dbforms.obj.Asset;
    import com.adobe.livecycle.content.repository.action.ActionHandler;
    import com.adobe.livecycle.content.repository.action.ActionType; 
    
    /**
     * Custom audit handler that audits actions in the CM solution.
     */
    public class AuditHandler implements ActionHandler { 
    
        private static final String PREFIX = "PREFIX";
        /**
         * This method is invoked just prior to the action being performed.
         */
        public void preProcess(String nodeType, ActionType actionType, Object obj, Map<String, Object> arg1)
        {
            Asset asset = (Asset) obj; // obj is the Asset object, and can hence be cast.
            // perform your audit logging here - for e.g., a log statement - logger.info("Asset being created [" + asset.getName() + "] by user : " + SecurityContextHolder.getContext().getAuthentication().getName());
        } 
    
        /**
         * This method is invoked just after the action has been performed.
         */
        public void postProcess(String nodeType, ActionType actionType, Object obj, Map<String, Object> arg1) {
            // perform your audit logging here -  - for e.g., a log statement - logger.info("Asset created [" + asset.getName() + "] by user : " + SecurityContextHolder.getContext().getAuthentication().getName());
        }
    }
  2. Add the following entry to the <bp:blueprint> section of CorrespondenceManagementSolutionTemplate\Services\resources\META-INF\spring\osgi-context.xmlfile:
    
    <bp:reference interface="com.adobe.livecycle.content.repository.RepositoryService" />
    
    
  3. Update the com.adobe.icc.bootstrap.AssetDefinitionDeployer class, under CorrespondenceManagementSolutionTemplate\Services\src\com\adobe\icc\bootstrap, to add a private member variable repositoryService of type com.adobe.livecycle.content.repository.RepositoryService:
    
    @Autowired
    private RepositoryService repositoryService;
    
    
  4. Register your handler for the necessary actions. To do so, add the following code to the afterPropertiesSet()method:
    
    List<ActionHandler> handlers = new ArrayList<ActionHandler>();
    handlers = new ArrayList<ActionHandler>();
    handlers.add(new AuditHandler()); 
    
    // registering the handler against the CREATE action of a Text asset.
    repositoryService.addActionHandlers(ActionHandlerService.APP_ROOT_GLOBAL, TextModule.class.getName(), ActionType.CREATE, handlers);
    
    // registering the handler against the EDIT/UPDATE action of a Text asset.
    repositoryService.addActionHandlers(ActionHandlerService.APP_ROOT_GLOBAL, TextModule.class.getName(), ActionType.UPDATE, handlers); 
    
    // register for other actions, as applicable
  5. Rebuild and redeploy the Solution template to get your changes in affect. For information on rebuilding and redeploying, see Building and deploying the CM Solution Template

Dynamic/Runtime Log level configuration

There is often a need to modify/change the log level of your (J2EE) application or parts of your application, without a server restart. These may be done for various purposes, debugging being one very obvious need.

Here’s a simple way to achieve the same, wherein we once you have deployed the EAR (if your WAR is wrapped within it) or WAR, you can simply place a JSP file at the root folder of your WAR file, and viola!, you have a custom log level configuration console.

In a typical JBOSS installation this would be placed under :   <jboss home>\server\<profile>\tmp\deploy\<EAR – if wrapped within>\<WAR>

In a typical WAS installation this would be placed under : <WebSphere Installation Directory>\AppServer\profiles\<profile>\installedApps\<node name>\<EAR – if wrapped within>\<WAR>

Of course, you should have configured the log4j in your application properly.

Here is the configureLogging.jsp:


<%@ page language="java" contentType="text/html;charset=UTF-8"%>
<%@ page import="org.apache.log4j.Level"%>
<%@ page import="org.apache.log4j.LogManager"%>
<%@ page import="org.apache.log4j.Logger"%>
<%@ page import="java.util.HashMap"%>
<%@ page import="java.util.Enumeration"%>
<%@ page import="java.util.Set"%>
<%@ page import="java.util.Arrays"%>
<html>
<head>
<title>Log Level Configuration</title>
<style type="text/css">
<!--
#content {
    margin: 0px;
    padding: 0px;
    text-align: center;
    background-color: green;
    border: 1px solid #000;
    width: 100%;
}

body {
    position: relative;
    margin: 10px;
    padding: 0px;
    color: #333;
}

h1 {
    margin-top: 20px;
    font: 1.5em Verdana, Arial, Helvetica sans-serif;
}

h2 {
    margin-top: 10px;
    font: 0.75em Verdana, Arial, Helvetica sans-serif;
    text-align: left;
}

a,a:link,a:visited,a:active {
    color: blue;
    text-decoration: none;
    text-transform: uppercase;
}

table {
    width: 100%;
    background-color: #000;
    padding: 3px;
    border: 0px;
}

th {
    font-size: 0.75em;
    background-color: #eee;
    color: #000;
    padding-left: 5px;
    text-align: center;
    border: 1px solid #eee;
    white-space: nowrap;
}

td {
    font-size: 0.75em;
    background-color: #fff;
    white-space: nowrap;
}

td.center {
    font-size: 0.75em;
    background-color: #fff;
    text-align: center;
    white-space: nowrap;
}

.filterForm {
    font-size: 0.9em;
    background-color: #000;
    color: #fff;
    padding-left: 5px;
    text-align: left;
    border: 1px solid #000;
    white-space: nowrap;
}

.filterText {
    font-size: 0.75em;
    background-color: #ccc;
    color: #000;
    text-align: left;
    border: 1px solid #ccc;
    white-space: nowrap;
}

.filterButton {
    font-size: 0.75em;
    background-color: brown;
    color: white;
    padding-left: 5px;
    padding-right: 5px;
    text-align: center;
    border: 1px solid #ccc;
    width: 100px;
    white-space: nowrap;
}
–>
</style>
</head>
<body onLoad=”javascript:document.logFilterForm.logNameFilter.focus();”>

    <%
        String containsFilter = “Contains”;
        String beginsWithFilter = “Begins With”;

        String[] logLevels = { “debug”, “info”, “warn”, “error”, “fatal”,
                “off” };
        String targetOperation = (String) request.getParameter(“operation”);
        String targetLogger = (String) request.getParameter(“logger”);
        String targetLogLevel = (String) request
                .getParameter(“newLogLevel”);
        String logNameFilter = (String) request
                .getParameter(“logNameFilter”);
        String logNameFilterType = (String) request
                .getParameter(“logNameFilterType”);
    %>
    <div id=”content”>
        <h1>Log Level Configuration</h1>

        <div>

            <form action=”configureLogging.jsp” name=”logFilterForm”>
                Filter Loggers:&nbsp;&nbsp; <input name=”logNameFilter” type=”text”
                    size=”50″ value=”<%=(logNameFilter == null ? “” : logNameFilter)%>”
                    class=”filterText” /> <input name=”logNameFilterType” type=”submit”
                    value=”<%=beginsWithFilter%>” />&nbsp; <input
                    name=”logNameFilterType” type=”submit” value=”<%=containsFilter%>”
                    class=”filterButton” />&nbsp; <input name=”logNameClear”
                    type=”button” value=”Clear”
                    onmousedown=’javascript:document.logFilterForm.logNameFilter.value=””;’ />
                <input name=”logNameReset” type=”reset” value=”Reset”
                    class=”filterButton” />

                <param name=”operation” value=”changeLogLevel” />
            </form>
        </div>

        <table cellspacing=”1″>
            <tr>
                <th width=”25%”>Logger</th>

                <th width=”25%”>Parent Logger</th>
                <th width=”15%”>Current Level</th>

                <th width=”35%”>Change Log Level To</th>
            </tr>

            <%
                Enumeration loggers = LogManager.getCurrentLoggers();

                HashMap loggersMap = new HashMap(128);
                Logger rootLogger = LogManager.getRootLogger();

                if (!loggersMap.containsKey(rootLogger.getName())) {

                    loggersMap.put(rootLogger.getName(), rootLogger);
                }

                while (loggers.hasMoreElements()) {
                    Logger logger = (Logger) loggers.nextElement();

                    if (logNameFilter == null || logNameFilter.trim().length() == 0) {

                        loggersMap.put(logger.getName(), logger);
                    } else if (containsFilter.equals(logNameFilterType)) {

                        if (logger.getName().toUpperCase()
                                .indexOf(logNameFilter.toUpperCase()) >= 0) {

                            loggersMap.put(logger.getName(), logger);
                        }

                    } else {
                        // Either was no filter in IF, contains filter in ELSE IF, or begins with in ELSE
                        if (logger.getName().startsWith(logNameFilter)) {

                            loggersMap.put(logger.getName(), logger);
                        }

                    }
                }
                Set loggerKeys = loggersMap.keySet();

                String[] keys = new String[loggerKeys.size()];

                keys = (String[]) loggerKeys.toArray(keys);

                Arrays.sort(keys, String.CASE_INSENSITIVE_ORDER);
                for (int i = 0; i < keys.length; i++) {

                    Logger logger = (Logger) loggersMap.get(keys[i]);

                    // MUST CHANGE THE LOG LEVEL ON LOGGER BEFORE GENERATING THE LINKS AND THE
                    // CURRENT LOG LEVEL OR DISABLED LINK WON’T MATCH THE NEWLY CHANGED VALUES
                    if (“changeLogLevel”.equals(targetOperation)
                            && targetLogger.equals(logger.getName())) {

                        Logger selectedLogger = (Logger) loggersMap
                                .get(targetLogger);

                        selectedLogger.setLevel(Level.toLevel(targetLogLevel));
                    }

                    String loggerName = null;
                    String loggerEffectiveLevel = null;
                    String loggerParent = null;
                    if (logger != null) {
                        loggerName = logger.getName();
                        loggerEffectiveLevel = String.valueOf(logger
                                .getEffectiveLevel());
                        loggerParent = (logger.getParent() == null ? null : logger
                                .getParent().getName());

                    }
            %>
            <tr>
                <td><%=loggerName%></td>

                <td><%=loggerParent%></td>
                <td><%=loggerEffectiveLevel%></td>
                <td>
                    <%
                        for (int cnt = 0; cnt < logLevels.length; cnt++) {

                                String url = “configureLogging.jsp?operation=changeLogLevel&logger=”
                                        + loggerName
                                        + “&newLogLevel=”
                                        + logLevels[cnt]
                                        + “&logNameFilter=”
                                        + (logNameFilter != null ? logNameFilter : “”)
                                        + “&logNameFilterType=”
                                        + (logNameFilterType != null ? logNameFilterType
                                                : “”);

                                if (logger.getLevel() == Level.toLevel(logLevels[cnt])
                                        || logger.getEffectiveLevel() == Level
                                                .toLevel(logLevels[cnt])) {
                    %> [<%=logLevels[cnt].toUpperCase()%>] <%
                        } else {
                    %> <a href=’<%=url%>’>[<%=logLevels[cnt]%>]</a>&nbsp; <%
     }
         }
 %>
                </td>
            </tr>

            <%
                }
            %>
        </table>
    </div>
</body>
</html>

Once you copy this JSP under root of your web app, you can access it by entering http://[host]:[port]/[context root]/configureLogging.jsp

You should see something like this on your screen:

ADEP and the Correspondence Management Solution

With the release of the Adobe Digital Enterprise Platform, you are now presented with the all new Correspondence Management Solution. Powered by the Digital Enterprise Platform, the solution brings in a whole new set of exciting features and enhanced user experience for correspondence authoring and generation. By virtue of the new platform, the solution is now easily extendable and customizable, and is packaged with a set of production-ready building blocks, reusable components and technical guides to help accelerate development and deployment of customer experience applications.

Check out the ADEP Developer Center for trial downloads. Please take time to watch this short solution introduction video.

Here are some useful links related to the solution, that one must refer to:

Installation and Configuration guides:

Overall Solution guides:

Detailed technical guides for the Building Blocks used in the solution:

For those dealing with customizations and solution extensions may find this handy:

You may also check out some more information on adobe.com @ Customer Communications.

Troubleshoot the Create Correspondence interface of the Adobe Correspondence Management Solution

Create Correspondence interface is the agent facing (flex based) application of the Adobe Correspondence Management Solution that is used to create the final correspondence, using a given Letter template.

In the event of an unexpected behavior or an unlikely error or to get more details on the request processing, there could be a need to troubleshoot the application. Following are some of the ways to do so.

General Flash Logs

Flash Log output is obviously critical when debugging a flex application. All general log messages, including the Form Bridge log output that is redirected from both the PDF and the HTML layers, is output to the Flash logs.

Of course, you would need Flash Debug player, which can be downloaded from the flash download site, to enable such logging. You can appropriately configure the logs based on your operating system.

The application also keeps an in-memory log of all messages that can be displayed using Ctrl + Alt + D, once initialized (note that you must have your mouse focus on the Flex application when you do so). The main purpose of this is so that we can get log contents even when the application isn’t run in a Flash Debug Player.

URL parameters

The Create Correspondence application can be enforced to log additional information by invoking it with some special URL parameters. Note that this can adversely affect the application performance, given the large amount of data being logged.

cmDataDebug

This flag (that can be given as a URL parameter) is used to specifically enable data output to the Flash Log. The output in such cases can be very large (for a complex templates that use large data) but sometimes it is critical to see the generated data the way it is sent to the PDF.

One can set it by including the following parameter in the URL parameters when invoking the Create Correspondence application:

cmDataDebug=1

This is an ON/OFF flag, so anything other than “0″ will activate the mode.

cmFbDebug

This is yet another flag, that is used to enable debugging and logging in the Form Bridge layers, and help troubleshoot communication issues. One can set it by including the following parameter in the URL parameters when invoking the Create Correspondence application:

cmFbDebug=[a,[b,[c,[d]]]]

where,
a = debug level until a connection is established (0 = none; 9 = highest/most verbose)
b = debug level after a connection is established (0 – none; 9 – highest/most verbose)
c = delay in seconds (can be a decimal, as in .1) after the HTML has created the PDF <object> and the time it sets its messageHandler (its definition of listener functions) on that object
d = delay in seconds (can be a decimal, as in .001) after the PDF has got as far as the ‘docReady’ message, until it starts to send it’s “initialized” connection message.

The above parameters can hence be tweaked according to the intent of troubleshooting. For instance, if there is a communication issue wherein the PDF takes longer to be ‘ready’ (may be due to a slow connection), you can tweak (increase) the value of  ‘d‘ to a value that sets enough delay for the communication to be successful. If there is a need to log detailed HTML-to-PDF communication messages for a user interaction on the flex app., you can set an appropriate value for ‘b‘.