Programmers Showing Initiative: Never allow problems stop development

It may seem unbelievable but sometimes a programmer’s job is made much more difficult by a cascade of events out of his or her control. It is when these moments occur that schedules get shot in the foot, stress levels increase to apoplectic stage, and copious amounts of stomach acid is secreted and digests the programmer from inside out!
It is moments like this that either make or break a programmer. They create opportunities either to shine, or to go spiraling down in flames. Even though the source of the problem is outside of the programmer’s control, pointing an accusing finger at someone or some other system does not get the job done. These are the moments that define programmers who (to quote the line from Heartbreak Ridge) "adapt, improvise, and overcome."

This past week became the crucible of one of these "moments from hell" for me. I was diligently working on completing a critical piece of functionality in Pega, one which will be driven by an agent. The core activities would perform perfectly when I tested them as myself but when running as the agent, they would go sputter-splat. Obscure errors would prevent cases from being created. Ah ha! What a perfect time to fire up SMA and use the Requestor Manager to hook the agent’s clipboard and run tracer on it. Yeah, that would do it. Easy as pie: this won’t take a moment to complete. Look at how easy it is to see the contents of the Clipboard data area using a browser:

Sample of the Pega Clipboard Viewer

(Note for those unfamiliar with Pega. The Clipboard is the active area in server memory where a user’s data is stored and manipulated. It consists of properties (key/value pairs) organized in Pages. Each Page is a special area in memory which may be classless or assigned to a specific class. If the latter, the property keys correspond to the class’s properties. Do not confuse the Pega Clipboard with the Windows clipboard.)
Yeah, right! Enter the real world. My attempts to snag the clipboard from SMA led to the following:

Error using SMA Requestor Manager to get Agent's Clipboard

Oh my Lord! What to do with a deadline looming? Without this tool, I am as blind as a proverbial bat and any "fix" I attach to this code is a shot in the dark. I did the only things I could do at the moment which were to open an urgent case with Pega Support to attempt to get a fix or workaround to the problem and to report back in my scrum standup that I had hit a serious impediment. Boom, boom, boom…the relentless drumbeat of a looming deadline would not be silenced by such ineffective procedures.

Boom, boom, BOOM!

What to do?

That evening I prayed really hard, thought a lot, and attempted to noodle anything I could leverage within Pega to accomplish my goals. Remember, I am really new to the Pega world (see my earlier post Getting Started With Pega Development) so I am not, by any stretch of the imagination, a Pega expert.

What I reasoned was that I have access to the Clipboard through Java using the tools object which implements the PublicAPI. Maybe, just maybe, I could walk the Clipboard tree and dump it somewhere and do something with it from there. Of course, the happy path is that before I write the first line of code, Pega would have a solution. Yeah, right! Ok, so where to dump the data? The log? No, not convenient. “Wait a moment, I am running on a Development system and have access to the Unix underpinnings, so I can easily dump files to the /tmp directory and snag them from there,” I reasoned. Good! Two problems were down, in theory, at least. Files could then be compared using a command line compare tool like WinMerge. Nice! A slight deviation in schedule to code this and then I could resume forward momentum on the main programming problem.

How to walk and decode Pega’s Clipboard

From my experience discussed in the prior article Using Java inner classes in Pega inline activity to handle Excel XLSX file, a complete inline class structure could be devised to handle parsing, storage, and formatting the contents of the Clipboard pages. I approached this solution the same way I did the XLSX parser by developing and sorting individual classes using Eclipse.

Once everything was reasonably laid out which took a couple hours of coding, most spent poring through Pega’s Javadocs (converted into a WinHelp file using javadoc2chm from Baresovi.cz which enables word search). I then assembled an inline class list for inclusion in a Pega activity.

One of the neatest side-effects is that the class structure pretty well did all the work of stripping the data from the page, recursing into subpages, and formatting the output. This meant that the actual code to drive the rest of the functionality was relatively minor. I chose to write the formatted output string into a local variable, pick it up in a later step and output it to file. Ultimately, this decision decoupled the decoding/formatting logic from the output logic.

So the activity to handle the dumping of a Clipboard page structure is as simple as this:

The DumpClipboard activity

It takes a pair of parameters, the name of the Clipboard page to dump (e.g. OperatorID) and the pathname to the file to dump to. The code is a lot more interesting.

/**
* Interface for a raw page.
*
* @author Christopher Laforet
*/
class IPage
    {

    }

/**
* Base abstract class containing a clipboard item and sharing the basic behavior of all such items.
*
* @author Christopher Laforet
*
*/

abstract class ClipboardItem
  {
  private String _key;
  private String _messages;

  protected ClipboardItem(String Key)
      {
      _key = Key;
      }

  public String getKey()
      {
      return _key;
      }

  public abstract String getValue();
  }


/**
* Contains clipboard property that contains a single page.
*
* @author Christopher Laforet
*
*/

class PageClipboardItem extends ClipboardItem
  {
  private IPage _page;

  public PageClipboardItem(String Key,IPage Page)
      {
      super(Key);
      _page = Page;
      }

  public String getValue()
      {
      return "Page";
      }

  public IPage getPage()
      {
      return _page;
      }
  }


/**
* Contains a property that contains a pagelist.
*
* @author Christopher Laforet
*
*/

class PageListClipboardItem extends ClipboardItem
  {
  private java.util.HashMap _pages = new java.util.HashMap();

  public PageListClipboardItem(String Key)
      {
      super(Key);
      }

  public String getValue()
      {
      return "PageList";
      }

  public void addPage(int Index,IPage Page)
      {
      _pages.put(new Integer(Index),Page);
      }

  public IPage getPage(int Index)
      {
      Integer index = new Integer(Index);
      if (_pages.containsKey(index))
          return (IPage)_pages.get(index);
      return null;
      }

  public Iterator iterator()
      {
      return _pages.values().iterator();
      }
  }

/**
* Contains a basic property in the clipboard.
*
* @author Christopher Laforet
*
*/

class PropertyClipboardItem extends ClipboardItem
  {
  private String _value;

  public PropertyClipboardItem(String Key,String Value)
      {
      super(Key);
      _value = Value;
      }

  public String getValue()
      {
      return _value;
      }
  }


/**
* Container for a page's data.
*
* @author Christopher Laforet
*
*/

class Page extends IPage
  {
  private String _pageName;
  private String _className;
  private String _messages;
  private String _parentPageName = "";
  private java.util.HashMap _contents = new java.util.HashMap();

  public Page(String PageName,String ClassName,String Messages)
      {
      _pageName = PageName;
      _className = ClassName == null ? "" : ClassName;
      _messages = Messages;
      }

  public String getPageName()
      {
      return _pageName;
      }

  public String getClassName()
      {
      return _className;
      }

  public String getMessages()
      {
      return _messages == null ? "" : _messages;
      }

  public void setParentPageName(String ParentPageName)
      {
      _parentPageName = ParentPageName;
      }

  public String getParentPageName()
      {
      return _parentPageName;
      }

  public Iterator iterator()
      {
      return _contents.values().iterator();
      }

  public void addItem(ClipboardItem Item)
      {
      _contents.put(Item.getKey(),Item);
      }

  /**
   * Retrieves the page data in a printable form.
   *
   */
  public String toString()
      {
      StringBuffer sb = new StringBuffer(16384);

      sb.append("-------------------------------------------------------------\r\n");
      sb.append("PAGE DUMP: " + getPageName());
      sb.append(" -- Class: ");
      if (getClassName().length() == 0)
          sb.append("Undefined");
      else
          sb.append(getClassName());
      sb.append("\r\n");
      sb.append("-------------------------------------------------------------\r\n");

      sb.append("\r\nParent page: ");
      if (getParentPageName().length() > 0)
          sb.append(getParentPageName().trim());
      else
          sb.append("<none>");
      sb.append("\r\n");

      if (getMessages().trim().length() > 0)
          {
          sb.append("\r\nPage messages: ");
          sb.append(getMessages().trim());
          sb.append("\r\n");
          }

      sb.append("\r\nProperties on page:\r\n");
      sb.append("------------------\r\n");

      java.util.ArrayList pages = new java.util.ArrayList();
      java.util.ArrayList pageLists = new java.util.ArrayList();
      for (Iterator it = _contents.keySet().iterator(); it.hasNext();)
          {
          String key = (String)it.next(); // read in alpha order


          ClipboardItem item = (ClipboardItem)_contents.get(key);

          sb.append("  ");
          sb.append(item.getKey());
          sb.append(" = ");
          sb.append(item.getValue());
          sb.append("\r\n");

          if (item instanceof PageClipboardItem)
              pages.add(item);
          else if (item instanceof PageListClipboardItem)
              pageLists.add(item);
          }

      sb.append("\r\n");

      // recurse into subpages...
      for (Iterator it = pages.iterator(); it.hasNext();)
        {
        PageClipboardItem item = (PageClipboardItem)it.next();
        sb.append(item.getPage().toString());
        }

      // recurse into subpage lists....
      for (Iterator it = pageLists.iterator(); it.hasNext();)
          {
          PageListClipboardItem item = (PageListClipboardItem)it.next();

          for (Iterator lit = item.iterator(); lit.hasNext();)
            {
            Page page = (Page)lit.next();
            sb.append(page.toString());
            }
          }

      return sb.toString();
      }
  }



/**
* Attempts to load the page and walk its structure deriving data as it goes.
*
* @author Christopher Laforet
*
*/

class ClipboardPageTree
  {
  private Page _page;

  public ClipboardPageTree(String PageName)
      {
      ClipboardPage clipboardPage = tools.findPage(PageName,true);
      if (clipboardPage == null)
          return;

      _page = loadPageContents(clipboardPage,null);
      }

  private Page loadPageContents(ClipboardPage PageToLoad,Page Parent)
      {
      Page page = new Page(PageToLoad.getName(),PageToLoad.getClassName(),PageToLoad.getMessagesAll());
      if (Parent != null)
          page.setParentPageName(Parent.getPageName());
      else
          {
          ClipboardPage parent = PageToLoad.getParentPage();
          if (parent != null)
              page.setParentPageName(parent.getName());
          }

      for (Iterator it = PageToLoad.values().iterator(); it.hasNext();)
          {
          ClipboardProperty property = (ClipboardProperty)it.next();
          ClipboardItem item = null;

          switch (property.getMode())
              {
              case ImmutablePropertyInfo.MODE_JAVAOBJECT:
                  item = new PropertyClipboardItem(property.getName(),"Java object");
                  break;
              case ImmutablePropertyInfo.MODE_JAVAOBJECT_LIST:
                  item = new PropertyClipboardItem(property.getName(),"Java object list");
                  break;
              case ImmutablePropertyInfo.MODE_JAVAOBJECT_GROUP:
                  item = new PropertyClipboardItem(property.getName(),"Java object group");
                  break;
              case ImmutablePropertyInfo.MODE_JAVAPROPERTY:
                  item = new PropertyClipboardItem(property.getName(),"Java property");
                  break;
              case ImmutablePropertyInfo.MODE_JAVAPROPERTY_LIST:
                  item = new PropertyClipboardItem(property.getName(),"Java property list");
                  break;
              case ImmutablePropertyInfo.MODE_STRING:
                  item = new PropertyClipboardItem(property.getName(),property.getStringValue());
                  break;
              case ImmutablePropertyInfo.MODE_STRING_LIST:
                  item = new PropertyClipboardItem(property.getName(),"String list: " + property.toString());
                  break;
              case ImmutablePropertyInfo.MODE_STRING_GROUP:
                  item = new PropertyClipboardItem(property.getName(),"String group: " + property.toString());
                  break;
              case ImmutablePropertyInfo.MODE_PAGE:
                  {
                  ClipboardPage subPage = property.getPageValue();
                  if (subPage != null)
                      item = new PageClipboardItem(property.getName(),loadPageContents(subPage,page));
                  }
                  break;
              case ImmutablePropertyInfo.MODE_PAGE_LIST:
                  {
                  PageListClipboardItem plci = new PageListClipboardItem(property.getName());
                  item = plci;

                  int pageID = 1;
                  for (Iterator pit = property.iterator(); pit.hasNext(); pageID++)
                      {
                      ClipboardProperty subPage = (ClipboardProperty)pit.next();

                      plci.addPage(pageID,loadPageContents(subPage.getPageValue(),page));
                      }
                  }
                  break;
              case ImmutablePropertyInfo.MODE_PAGE_GROUP:
                  item = new PropertyClipboardItem(property.getName(),"TODEVELOP: Page Group");
                  break;
              case ImmutablePropertyInfo.MODE_UNKNOWN:
              default:
                  item = new PropertyClipboardItem(property.getName(),"Unknown property type");
                  break;
              }

          if (item != null)
              page.addItem(item);
          }
      return page;
      }

  /**
   * Retrieves the page data in a printable form.
   *
   */
  public String toString()
      {
      if (_page == null)
          return "No page to print\r\n";
      return _page.toString();
      }
  }


String pageName = tools.getParamValue("PageNameToDump");
// testing -- pageName = "pyPortal";
try
  {
  ClipboardPageTree tree = new ClipboardPageTree(pageName);
  FileContents = tree.toString();
  }
catch (Exception ee)
  {
  throw new PRRuntimeException("Error opening page to dump called " + pageName,ee);
  }

Notice the cascading of the classes. One of the limitations in Java inner classes is that there is no forward-chaining of class references. In fact, this is the reason I created the dummy IPage class. When I developed the code in Eclipse, the Page class was nicely visible to all classes but once everything was inlined, it led to problems. The solution was to inherit from a top-level, dummy class called IPage and make all forward references to Page reference IPage instead.

For the record, one of my peeves with Pega is that even though it runs on Java 5, it does not support the generic lists and consequently the foreach methodology. This is why all my containers are defined in the old manner and my iterations are centered on the Iterator class. Generics introduce a great type-safety control and convenience that it really sucks to go back to the way it used to be.

Most of the code is merely container classes. Where things become interesting in the ClipboardPageTree and Page classes. Notice the recursive nature of loadPageContents() method in ClipboardPageTree. As a page may contain a property which itself is a page, or even a list of pages, there needed to be a way to walk into them and parse their contents. This recursion visible is also in toString() method in the same class. I decided to dump child classes at the end of their parent classes in the dump printout.

There are a few items that need to be addressed but they were not critical for my immediate needs. The Pega Javadoc is a bit hazy on how to iterate String Lists, String Groups, and Page Groups. Oh well, maybe some enterprising readers can solve these items and post comments with the details.

The following code handles the actual dumping of the file itself. It is really simple stuff and is provided for completion.

String pathName = tools.getParamValue("PathName");
try
  {
  java.io.Writer writer =
       new java.io.OutputStreamWriter(
         new java.io.FileOutputStream(pathName));
  writer.write(FileContents);
  writer.flush();
  writer.close();
  }
catch (Exception ee)
  {
  throw new PRRuntimeException("Error writing dump data to file " + pathName,ee);
  }

The code was then wired into my code as activity calls and then I ran the activity as myself to capture a baseline set of files. Then I ran the activity from the agent and captured its output files for comparison.

Calling DumpClipboard Sample

Calling DumpClipboard Example

Working with Clipboard dumps

Both sets of files were transferred to my work machine and then examined pair by pair using WinMerge. I was immediately able to spot critical differences which I then remedied step by step. After each set of changes, I would recapture the agent’s Clipboard data and once again compare its data with the baseline. This iterative process led to the fix.

Clipboard page comparison showing an obvious error message

WinMerge showing obvious problems in Agent

The long and the short of it is that, while this is not the best way to capture Clipboard data, I was able to move forwards and finish the assignment. It was a day later than it was due, but a day beats the alternative. To be fair, Pega has been very attentive in providing possible workarounds and suggestions, but there has not been a final solution to the problem yet.

Final thoughts

As a newcomer to Pega, I certainly continued learning about how it is wired together by doing this assignment. For one, I have a much better understanding of the Java API that Pega provides. I also now know more about the Clipboard structure and the importance of key pages and properties within Pega and Pega SmartInvestigate. Finally, understanding both of these allowed me to make leaps of logic to how activities are wired and the services that are provided in the built-in Pega rules.

The lesson learned is that a developer must not allow himself/herself to not be stonewalled by issues. Flexibility pays many dividends.

About claforet

I have been photographing since my college days. My current gear is a Nikon D-700 along with a plethora of lenses. My first serious camera was a Canon EF back in the early 80s. A Canon A-1 soon followed. Since then, I also have had a Minolta Maxxum, Nikon FM, Nikon F4, and Nikon Point and Shoot in my film days. I have had and maintained a private full color and B&W lab on and off for much of that time. My photos can be found at https://www.flickr.com/photos/claforet/ Photography and painting are my "sanity breaks" that save me from my day-to-day software development existence! I host a group in Facebook called "Painting as a Second Language" for anyone who is interested in painting as an outlet for the day-to-day pressures. Please look it up and join if you think it will meet your needs. Recently, I have also branched into the video world and am learning how to shoot video better and how to edit compelling video sequences. My learning experiences will be part of this blog and my videos can be seen at http://www.vimeo.com/claforet I live in, and photograph mostly around, North Carolina. I love traveling so there are many shots from states around us, out West, and other places. My daughter has been bitten by the photography bug too. She has spent time in a B&W lab and loves the excitement generated by just the smell of the chemicals. Maybe one day she will take over where I leave off....
This entry was posted in Philosophical ramblings, Software architecture and development and tagged , , , , , , , , . Bookmark the permalink.

1 Response to Programmers Showing Initiative: Never allow problems stop development

  1. claforet says:

    Just a note to say that, in spite of Pega support’s excellent efforts in attempting to figure out the source of this problem, it has not yet been resolved. My point is that the project that was hampered by the problem has been finished since last week. Do not let problems stop your progress nor, if you can help it, depend upon a magical “fix” that might never come. Be innovative and use initiative to overcome hurdles and keep moving forwards.

Leave a comment