domingo, 22 de agosto de 2010

XML DIFF - Show differences in XML

This little routine will display the difference between 2 xml files analyzing the contents and returning a list of differences. There is a limitation with respect to the structure of XML to be compared
Depending on the content and structure of XML you will need to make some modifications in the implementation. In the comparison of XML that contains collections the analysis is done element by element and if there is an element out of order the return can not be expected. Ideally, the XML should be typed ( following a XMLSchema ).


Class TechParseCounter:
/**
* Desenvolvido por Ricardo Alberto Harari em 05/06/2005 - 20:43 - GMT-3:00
*
* Este codigo pode ser usado livremente, inclusive para fins comerciais
* desde que mantenha referencia ao autor original e não altere o fully qualified name desta classe
*
* Technique T.I. Ltda
* www.technique.com.br
*
* @author Ricardo A. Harari
*
*/

package com.technique.xmlUtil;

public class TechParseCounter {
private int counter;
public TechParseCounter(int i) {
counter = i;
}
public void add() {
counter++;
}
public int getCounter() {
return counter;
}
}



CLASS TechParseContentItem
/**
* Desenvolvido por Ricardo Alberto Harari em 05/06/2005 - 20:43 - GMT-3:00
*
* Este codigo pode ser usado livremente, inclusive para fins comerciais
* desde que mantenha referencia ao autor original e não altere o fully qualified name desta classe
*
* Technique T.I. Ltda
* www.technique.com.br
*
* @author Ricardo A. Harari
*
*/

package com.technique.xmlUtil;

public class TechParseContentItem {
static String[] ACTION_NAMES = new String[] {
"insert",
"update",
"delete"
};

public static int ACTION_INSERT = 0;
public static int ACTION_UPDATE = 1;
public static int ACTION_DELETE = 2;

public int action;

public String key;
public String attributeName;
public String oldValue;
public String newValue;

public TechParseContentItem(int action, String key, String attributeName, String oldValue, String newValue) {
this.action = action;
this.key = key;
this.attributeName = attributeName;
this.oldValue = oldValue;
this.newValue = newValue;
}

public String actionName() {
return ACTION_NAMES[action];
}

public String toString() {
String act = action == ACTION_INSERT ? "insert" : action == ACTION_UPDATE ? "update" : "delete";
return "action=[" + act + "], key=[" + key + "], attributeName=[" + attributeName + "], oldValue=[" + oldValue + "], newValue=[" + newValue + "]";
}

}


CLASS TechParseDiff
/**
* Desenvolvido por Ricardo Alberto Harari em 05/06/2005 - 20:43 - GMT-3:00
*
* Este codigo pode ser usado livremente, inclusive para fins comerciais
* desde que mantenha referencia ao autor original e não altere o fully qualified name desta classe
*
* Technique T.I. Ltda
* www.technique.com.br
*
* @author Ricardo A. Harari
*
*/

package com.technique.xmlUtil;

/**
* Technique T.I. Ltda
* www.technique.com.br
*
*
*/

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Hashtable;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

public class TechParseDiff extends DefaultHandler
{
/*
* separador de campos para montar a chave
*/
static String field_separator = "@5@_@<>@~5";
static String field_separator2 = "/";

private Hashtable ocorrencias = null;
private String[] levelBuffer = new String[100];
private String currentLevel = null;
private int level = 0;
private Hashtable xmlcontent = null;
private boolean started = false;
private StringBuffer currentCharBuffer = null;

public Hashtable retrieveDifference() {
return xmlcontent;
}

public TechParseDiff() {
super();
level = 0;
}

private void clearBuffer() {
currentCharBuffer = new StringBuffer();
started = false;
xmlcontent = new Hashtable();
level = 0;
currentLevel = "";
ocorrencias = new Hashtable();
}

protected void relaseBuffer() {
ocorrencias = null;
xmlcontent = null;
currentCharBuffer = null;
currentLevel = null;
}
private String formatKey(String key) {
return key.replaceAll(field_separator, field_separator2);
}
private String formatAttributeName(String key) {
int i = key.lastIndexOf(field_separator) + field_separator.length();
return key.substring(i, key.length());
}

/**
* compara 2 XMLs e coloca no Hashtable "xmlcontent" o resultado das diferencas encontradas.
* em xmlcontent voce terá objetos do tipo TechParseContentItem
*
* @param oldXml - stream do xml antigo
* @param newXml - stream do xml novo
* @throws SAXException - erro de parsing
* @throws IOException - erro de IO
*/
public void compare(InputStream oldXml, InputStream newXml) throws Exception {
try {
clearBuffer();
Hashtable difference = new Hashtable();
XMLReader xr = XMLReaderFactory.createXMLReader();
InputSource is = new InputSource(oldXml);
xr.setContentHandler(this);
xr.setErrorHandler(this);
xr.parse(is);
Hashtable ht1 = xmlcontent;

this.clearBuffer();
is = new InputSource(newXml);
xr.setContentHandler(this);
xr.setErrorHandler(this);
xr.parse(is);

Hashtable ht2 = xmlcontent;
Enumeration enum2 = ht2.keys();
//int inserts = 0;
while (enum2.hasMoreElements()) {
Object key2 = enum2.nextElement();
Object o2 = ht2.get(key2);
Object o1 = ht1.get(key2);
if (o1 == null) {
difference.put(key2, new TechParseContentItem(TechParseContentItem.ACTION_INSERT,
formatKey(key2.toString()),
formatAttributeName(key2.toString()),
null,
o2.toString()));
} else {
if (!o1.toString().equals(o2.toString())) {
difference.put(key2, new TechParseContentItem(TechParseContentItem.ACTION_UPDATE,
formatKey(key2.toString()),
formatAttributeName(key2.toString()),
o1.toString(),
o2.toString()));
}
ht1.remove(key2);
}
}
enum2 = ht1.keys();
while (enum2.hasMoreElements()) {
Object key1 = enum2.nextElement();
Object o1 = ht1.get(key1);
difference.put(key1, new TechParseContentItem(TechParseContentItem.ACTION_DELETE,
formatKey(key1.toString()),
formatAttributeName(key1.toString()),
o1.toString(),
null));
}
this.clearBuffer();
xmlcontent = difference;
} catch (Exception e) {
throw new Exception("Nao foi possivel gravar os dados de Log. Motivo:" + e.getMessage(), e);
}
}

public void endElement (String uri, String name, String qName) {
//super.endElement(uri, name, qName);
if ("".equals (uri)) {
removeLevel(qName);
} else {
removeLevel("{" + uri + "}" + name);
}
started = false;
}

private void addLevel(String name) {
levelBuffer[++level] = name;
currentLevel += field_separator + name;
Object o = ocorrencias.get(currentLevel);
if (o == null) {
ocorrencias.put(currentLevel, new TechParseCounter(1));
} else {
((TechParseCounter) o).add();
}
}

private void removeLevel(String name) {
if (currentCharBuffer.length() > 0) {
TechParseCounter ocorr = (TechParseCounter) ocorrencias.get(currentLevel);
String s = ocorr.getCounter() == 0 ? "" : "[" + ocorr.getCounter() + "]";
xmlcontent.put(currentLevel
+ s
+ field_separator + name,
currentCharBuffer.toString().trim());
currentCharBuffer = new StringBuffer();
}
String levelName = levelBuffer[level];
int j = currentLevel.length() - levelName.length() - field_separator.length();
currentLevel = j < 1 ? "" : currentLevel.substring(0, j);
levelBuffer[level--] = null;
}

public void startElement (String uri, String name, String qName, Attributes atts) {
if ("".equals (uri)) {
addLevel(qName);
} else {
addLevel("{" + uri + "}" + name);
}
int i = atts.getLength();
TechParseCounter ocorr = (TechParseCounter) ocorrencias.get(currentLevel);
String s = ocorr.getCounter() == 0 ? "" : "[" + ocorr.getCounter() + "]";
for (int j = 0; j < i; j++) {
if (atts.getValue(j) != null) {
xmlcontent.put(currentLevel + s + field_separator + atts.getQName(j), atts.getValue(j));
}
}
started = true;
}

public void characters (char ch[], int start, int length) {
if (!started) return;
for (int i = start; i < start + length; i++) {
switch (ch[i]) {
case '\\'|'"'|'\r'|'\n'|'\t':
break;
default:
currentCharBuffer.append(ch[i]);
break;
}
}
}

public void startDocument () {
//start
}

public void endDocument () {
//end
}

}



Sample Usage:
/**
* Technique TI Ltda - Project: techEngine
* @author Ricardo A. Harari
* com.technique.xmlUtil
*
* xml diff sample usage
*/

package com.technique.xmlUtil;

import java.io.ByteArrayInputStream;
import java.util.Enumeration;
import java.util.Hashtable;

public class TechDiffSample {

/**
* @param args
*/
public static void main(String[] args) {
String xml1 = "<document><stockoption>PETR4</stockoption><date>05/06/2005</date><value>1.20</value><stockoption>NET4</stockoption><date>04/06/2005</date><value>1.20</value></document>";
String xml2 = "<document><stockoption>PETR4</stockoption><date>05/06/2005</date><value>1.22</value><comment>ipsenlorem</comment><stockoption>NET4</stockoption><date>05/06/2005</date><value>1.20</value></document>";
TechParseDiff xmldiff = new TechParseDiff();
ByteArrayInputStream bais1 = new ByteArrayInputStream(xml1.getBytes());
ByteArrayInputStream bais2 = new ByteArrayInputStream(xml2.getBytes());
try {
xmldiff.compare(bais1, bais2);
xmldiff.toString();
Hashtable ht = xmldiff.retrieveDifference();
Enumeration en = ht.elements();
while (en.hasMoreElements()) {
TechParseContentItem item = (TechParseContentItem) en.nextElement();
System.out.println(item.toString());
}
} catch (Exception e) {
e.printStackTrace();
}

}

}


The result will show the differences between the two XML indicating the insert, updates and deletes. If you have collections in XML you will also have information on the order of the element [1, 2, 3, ...] that has changed.

action=[insert], key=[/document/comment[1]/comment], attributeName=[comment], oldValue=[null], newValue=[ipsenlorem]
action=[update], key=[/document/value[1]/value], attributeName=[value], oldValue=[1.20], newValue=[1.22]

action=[update], key=[/document/date[2]/date], attributeName=[date], oldValue=[04/06/2005], newValue=[05/06/2005]

This routine uses the SAX parser, so it can be used to compare huge files. With a little modification you can record the results of the comparisons in a database or files instead of storing in a hashtable.
This routine is part of an old framework I developed, the TechEngine.


Have fun!

domingo, 8 de agosto de 2010

JBoss JBPM - Generating an image at runtime (JPDL -> PNG)

This article is an example of how to dynamically generate an image of a process JBPM.
JBoss JBPM and other BPM tools make use of XML to describe the business processes.
The concept is then to do a parsing of the XML and generate a process image at runtime.
The original idea was obtained from a Chinese discussion group.
In this article, Mr. Yeyong presents a simple solution to generate at run-time an image of a process modeled in JBPM, parsing the JPDL.xml and using the resources of elementary geometry with AWT lib.
Using the same idea and reusing the source code of Mr. Yeyong I extend this concept to provide a visual representation of the steps already completed of the process.
The original article can be found at the following URL: http://jbpm.group.javaeye.com/group/blog/470760?page=2

The following is the source code which is basically composed of five classes

/**
* Technique TI Ltda - Project: PlanetaContabilWeb - www.planetacontabil.com.br
* @author yeyong - http://jbpm.group.javaeye.com/group/blog/470760?page=2
* @author Ricardo A. Harari - ricardo.harari@gmail.com
* @date 25/01/2010 12:18:45
* br.com.technique.process.render.graph
*
* TODO
*/

package br.com.technique.process.render.graph;

import java.awt.Point;
import java.awt.Rectangle;

/**
* @author yeyong
*
*/
public class GeometryUtils {
/**
 * ????(x1,y1)-(x2,y2)???
 *
 * @param x1
 * @param y1
 * @param x2
 * @param y2
 * @return
 */
public static double getSlope(int x1, int y1, int x2, int y2) {
  return ((double) y2 - y1) / (x2 - x1);
}

/**
 * ????(x1,y1)-(x2,y2)?y???
 *
 * @param x1
 * @param y1
 * @param x2
 * @param y2
 * @return
 */
public static double getYIntercep(int x1, int y1, int x2, int y2) {
  return y1 - x1 * getSlope(x1, y1, x2, y2);
}
/**
 * ???????
 *
 * @param rect
 * @return
 */
public static Point getRectangleCenter(Rectangle rect) {
  return new Point((int) rect.getCenterX(), (int) rect.getCenterY());
}

/**
 * ??????p0?p1?????????
 *
 * @param rectangle
 * @param p1
 * @return
 */
public static Point getRectangleLineCrossPoint(Rectangle rectangle, Point p1, int grow) {
  Rectangle rect = rectangle.getBounds();
  rect.grow(grow, grow);
  Point p0 = GeometryUtils.getRectangleCenter(rect);

  if (p1.x == p0.x) {
    if (p1.y < y ="="" slope =" GeometryUtils.getSlope(p0.x," slopeline =" GeometryUtils.getSlope(p0.x," yintercep =" GeometryUtils.getYIntercep(p0.x,"> slope - 1e-2) {
    if (p1.y < page="2"> nodes = new LinkedHashMap();
public static final int RECT_OFFSET_X = -7;
public static final int RECT_OFFSET_Y = -8;
public static final int DEFAULT_PIC_SIZE = 48;

/** R.Harari - activities list */
public Hashtable listActivities;


private final static Map nodeInfos = new HashMap();
static {
  nodeInfos.put("start", "start_event_empty.png");
  nodeInfos.put("end", "end_event_terminate.png");
  nodeInfos.put("end-cancel", "end_event_cancel.png");
  nodeInfos.put("end-error", "end_event_error.png");
  nodeInfos.put("decision", "gateway_exclusive.png");
  nodeInfos.put("fork", "gateway_parallel.png");
  nodeInfos.put("join", "gateway_parallel.png");
  nodeInfos.put("state", null);
  nodeInfos.put("hql", null);
  nodeInfos.put("sql", null);
  nodeInfos.put("java", null);
  nodeInfos.put("script", null);
  nodeInfos.put("task", null);
  nodeInfos.put("sub-process", null);
  nodeInfos.put("custom", null);
}

public JpdlModel(InputStream is) throws Exception {
  this(new SAXReader().read(is).getRootElement());
}

public JpdlModel(InputStream is, List listHistoryActivities) throws Exception {
    this(new SAXReader().read(is).getRootElement());
    if (listHistoryActivities != null) {
     listActivities = new Hashtable();
     for (HistoryActivityInstance hai : listHistoryActivities) {
      listActivities.put(hai.getActivityName(), hai);
     }
    }
}

@SuppressWarnings("unchecked")
private JpdlModel(Element rootEl) throws Exception {
  for (Element el : (List) rootEl.elements()) {
    String type = el.getQName().getName();
    if (!nodeInfos.containsKey(type)) { // ????????
      continue;
    }
    String name = null;
    if (el.attribute("name") != null) {
      name = el.attributeValue("name");
    }
    String[] location = el.attributeValue("g").split(",");
    int x = Integer.parseInt(location[0]);
    int y = Integer.parseInt(location[1]);
    int w = Integer.parseInt(location[2]);
    int h = Integer.parseInt(location[3]);

    if (nodeInfos.get(type) != null) {
      w = DEFAULT_PIC_SIZE;
      h = DEFAULT_PIC_SIZE;
    } else {
      x -= RECT_OFFSET_X;
      y -= RECT_OFFSET_Y;
      w += (RECT_OFFSET_X + RECT_OFFSET_X);
      h += (RECT_OFFSET_Y + RECT_OFFSET_Y);
    }
    Node node = new Node(name, type, x, y, w, h);
    parserTransition(node, el);
    nodes.put(name, node);
  }
}

@SuppressWarnings("unchecked")
private void parserTransition(Node node, Element nodeEl) {
  for (Element el : (List) nodeEl.elements("transition")) {
    String label = el.attributeValue("name");
    String to = el.attributeValue("to");
    Transition transition = new Transition(label, to);
    String g = el.attributeValue("g");
    if (g != null && g.length() > 0) {
      if (g.indexOf(":") < p =" g.split(" lines =" p[0].split(" exp ="="" p =" exp.split("> getNodes() {
  return nodes;
}
  public static Map getNodeInfos() {
  return nodeInfos;
}

}


/**
* Technique TI Ltda - Project: PlanetaContabilWeb - www.planetacontabil.com.br
* @author yeyong - http://jbpm.group.javaeye.com/group/blog/470760?page=2
* @author Ricardo A. Harari - ricardo.harari@gmail.com - improved to represent completed steps of a running process
* @date 25/01/2010 12:17:04
* br.com.technique.process.render.graph
*
* TODO
*/

package br.com.technique.process.render.graph;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;

import org.jbpm.api.history.HistoryActivityInstance;

/**
* @author
*
*/
public class JpdlModelDrawer {
public static final int RECT_OFFSET_X = JpdlModel.RECT_OFFSET_X;
public static final int RECT_OFFSET_Y = JpdlModel.RECT_OFFSET_Y;
public static final int RECT_ROUND = 15;

public static final int DEFAULT_FONT_SIZE = 12;

public static final Color DEFAULT_STROKE_COLOR = Color.decode("#03689A");
public static final Stroke DEFAULT_STROKE = new BasicStroke(2);

public static final Color DEFAULT_LINE_STROKE_COLOR = Color.decode("#808080");
public static final Stroke DEFAULT_LINE_STROKE = new BasicStroke(1);

public static final Color DEFAULT_FILL_COLOR = Color.decode("#F6F7FF");

/** R.Harari - nova cores para representar o estado das etapas */
public static final Color DEFAULT_FILL_COLOR_FINISHED = Color.decode("#C4FFC1");
public static final Color DEFAULT_FILL_COLOR_CURRENT = Color.decode("#FFFF97");
/** */


private final static Map nodeInfos = JpdlModel.getNodeInfos();

public BufferedImage draw(JpdlModel jpdlModel) throws IOException {
  Rectangle dimension = getCanvasDimension(jpdlModel);
  BufferedImage bi = new BufferedImage(dimension.width, dimension.height, BufferedImage.TYPE_INT_ARGB);
  Graphics2D g2 = bi.createGraphics();
  g2.setColor(Color.WHITE);
  g2.fillRect(0, 0, dimension.width, dimension.height);
  g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  Font font = new Font("Arial", Font.PLAIN, DEFAULT_FONT_SIZE);
  g2.setFont(font);
  Map nodes = jpdlModel.getNodes();
  drawNode(nodes, g2, font, jpdlModel.listActivities);
  drawTransition(nodes, g2);
  return bi;
}

/**
 * ?????????
 *
 * @return
 */
private Rectangle getCanvasDimension(JpdlModel jpdlModel) {
  Rectangle rectangle = new Rectangle();
  Rectangle rect;
  for (Node node : jpdlModel.getNodes().values()) {
    rect = node.getRectangle();
    if (rect.getMaxX() > rectangle.getMaxX()) {
      rectangle.width = (int) rect.getMaxX();
    }
    if (rect.getMaxY() > rectangle.getMaxY()) {
      rectangle.height = (int) rect.getMaxY();
    }
    for (Transition transition : node.getTransitions()) {
      List trace = transition.getLineTrace();
      for (Point point : trace) {
        if (rectangle.getMaxX() < width =" point.x;" height =" point.y;"> nodes, Graphics2D g2) throws IOException {
  g2.setStroke(DEFAULT_LINE_STROKE);
  g2.setColor(DEFAULT_LINE_STROKE_COLOR);
  for (Node node : nodes.values()) {
    for (Transition transition : node.getTransitions()) {
      String to = transition.getTo();
      Node toNode = nodes.get(to);
      List trace = new LinkedList(transition.getLineTrace());
      int len = trace.size() + 2;
      trace.add(0, new Point(node.getCenterX(), node.getCenterY()));
      trace.add(new Point(toNode.getCenterX(), toNode.getCenterY()));
      int[] xPoints = new int[len];
      int[] yPoints = new int[len];
      for (int i = 0; i < taskgrow =" 4;" smallgrow =" -2;" grow =" 0;" grow =" smallGrow;" grow =" taskGrow;" p =" GeometryUtils.getRectangleLineCrossPoint(node.getRectangle()," grow =" smallGrow;" grow =" taskGrow;" p =" GeometryUtils.getRectangleLineCrossPoint(toNode.getRectangle()," label =" transition.getLabel();"> 0) {
        int cx, cy;
        if (len % 2 == 0) {
          cx = (xPoints[len / 2 - 1] + xPoints[len / 2]) / 2;
          cy = (yPoints[len / 2 - 1] + yPoints[len / 2]) / 2;
        } else {
          cx = xPoints[len / 2];
          cy = yPoints[len / 2];
        }
        Point labelPoint = transition.getLabelPosition();
        if (labelPoint != null) {
          cx += labelPoint.x;
          cy += labelPoint.y;
        }
        cy -= RECT_OFFSET_Y + RECT_OFFSET_Y / 2;
        g2.drawString(label, cx, cy);
      }
    }
  }
}

private void drawArrow(Graphics2D g2, int x1, int y1, int x2, int y2) {
  final double len = 8.0;
  double slopy = Math.atan2(y2 - y1, x2 - x1);
  double cosy = Math.cos(slopy);
  double siny = Math.sin(slopy);
  int[] xPoints = { 0, x2, 0 };
  int[] yPoints = { 0, y2, 0 };
  double a = len * siny, b = len * cosy;
  double c = len / 2.0 * siny, d = len / 2.0 * cosy;
  xPoints[0] = x2 - (int) (b + c);
  yPoints[0] = y2 - (int) (a - d);
  xPoints[2] = x2 - (int) (b - c);
  yPoints[2] = y2 - (int) (d + a);
  
  g2.fillPolygon(xPoints, yPoints, 3);
}

/**
 * @param g2
 * @throws IOException
 */
private void drawNode(Map nodes, Graphics2D g2, Font font, Hashtable listActivities) throws IOException {
  for (Node node : nodes.values()) {
    String name = node.getName();

    if (nodeInfos.get(node.getType()) != null) {
      BufferedImage bi2 = ImageIO.read(getClass().getResourceAsStream(
          "icons/48/" + nodeInfos.get(node.getType())));
      g2.drawImage(bi2, node.getX(), node.getY(), null);
    } else {
      int x = node.getX();
      int y = node.getY();
      int w = node.getWitdth();
      int h = node.getHeight();
    
      HistoryActivityInstance hai = null;
      Color fillColor = DEFAULT_FILL_COLOR;

      if (listActivities != null) {
       hai = listActivities.get(name);
       if (hai != null) {
         if (hai.getEndTime() != null) {
          fillColor = DEFAULT_FILL_COLOR_FINISHED;
         } else {
          fillColor = DEFAULT_FILL_COLOR_CURRENT;
         }
       }
      }

      g2.setColor(fillColor);
      g2.fillRoundRect(x, y, w, h, RECT_ROUND, RECT_ROUND);
      g2.setColor(DEFAULT_STROKE_COLOR);
      g2.setStroke(DEFAULT_STROKE);
      g2.drawRoundRect(x, y, w, h, RECT_ROUND, RECT_ROUND);

      FontRenderContext frc = g2.getFontRenderContext();
      Rectangle2D r2 = font.getStringBounds(name, frc);
      int xLabel = (int) (node.getX() + ((node.getWitdth() - r2.getWidth()) / 2));
      int yLabel = (int) ((node.getY() + ((node.getHeight() - r2.getHeight()) / 2)) - r2.getY());
      g2.setStroke(DEFAULT_LINE_STROKE);
      g2.setColor(Color.black);
      g2.drawString(name, xLabel, yLabel);
    }
  }
}
}


/**
* Technique TI Ltda - Project: PlanetaContabilWeb - www.planetacontabil.com.br
* @author yeyong - http://jbpm.group.javaeye.com/group/blog/470760?page=2
* @author Ricardo A. Harari - ricardo.harari@gmail.com
* @date 25/01/2010 12:13:40
* br.com.technique.process.render.graph
*
* TODO
*/

package br.com.technique.process.render.graph;

import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;

public class Node {
private String name;
private String type;
private Rectangle rectangle;
private List transitions = new ArrayList();

public Node(String name, String type) {
  this.name = name;
  this.type = type;
}

public Node(String name, String type, int x, int y, int w, int h) {
  this.name = name;
  this.type = type;
  this.rectangle = new Rectangle(x, y, w, h);
}

public Rectangle getRectangle() {
  return rectangle;
}

public void setRectangle(Rectangle rectangle) {
  this.rectangle = rectangle;
}

public String getType() {
  return type;
}

public void setType(String type) {
  this.type = type;
}

public String getName() {
  return name;
}

public void setName(String name) {
  this.name = name;
}

public void addTransition(Transition transition) {
  transitions.add(transition);
}

public List getTransitions() {
  return transitions;
}

public void setTransitions(List transitions) {
  this.transitions = transitions;
}

public int getX() {
  return rectangle.x;
}

public int getY() {
  return rectangle.y;
}

public int getCenterX() {
  return (int) rectangle.getCenterX();
}

public int getCenterY() {
  return (int) rectangle.getCenterY();
}

public int getWitdth() {
  return rectangle.width;
}

public int getHeight() {
  return rectangle.height;
}
}


/**
* Technique TI Ltda - Project: PlanetaContabilWeb - www.planetacontabil.com.br
* @author yeyong - http://jbpm.group.javaeye.com/group/blog/470760?page=2
* @author Ricardo Alberto Harari - ricardo.harari@gmail.com
* @date 25/01/2010 12:14:48
* br.com.technique.process.render.graph
*
* TODO
*/

package br.com.technique.process.render.graph;

import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

public class Transition {
private Point labelPosition;
private List lineTrace = new ArrayList();
private String label;
private String to;

public Transition(String label, String to) {
  this.label = label;
  this.to = to;
}

public Point getLabelPosition() {
  return labelPosition;
}

public void setLabelPosition(Point labelPosition) {
  this.labelPosition = labelPosition;
}

public List getLineTrace() {
  return lineTrace;
}

public void setLineTrace(List lineTrace) {
  this.lineTrace = lineTrace;
}

public void addLineTrace(Point lineTrace) {
  if (lineTrace != null) {
    this.lineTrace.add(lineTrace);
  }
}

public String getLabel() {
  return label;
}
public void setLabel(String label) {
  this.label = label;
}

public String getTo() {
  return to;
}

public void setTo(String to) {
  this.to = to;
}

}  



Example of use:

JpdlModel jpdlModel = new JpdlModel(JbpmAberturaEmpresa.class.getResourceAsStream("aberturaEmpresa.jpdl.xml"), hai);
ImageIO.write(new JpdlModelDrawer().draw(jpdlModel), "png", new File("/tmp/myprocess.png"));

aberturaEmpresa.jpdl.xml -> is my business process located at the same package of JbpmAberturaEmpresa class. You should customize to retrieve your business process.
hai -> History activities - see bellow a method to retrieve the activities
/tmp/myprocess.png -> output path+file of the PNG image

Retrieving the history activities:

String execID = "";
/** if you are running as a java application - outside a j2ee container */
ProcessEngine processEngine = new Configuration().setResource("jbpm.cfg.xml").buildProcessEngine();

List hai = processEngine.getHistoryService().createHistoryActivityInstanceQuery()
.processInstanceId(execID)
.list();


Below is an example of generated image:
Green -> completed steps
Yellow -> current step
White -> uncompleted step




Was tested with the latest version of JBPM 4.4 and works normally.
The following is a direct access to source code containing also the icons. The icons are extracted from a JAR used by the modeler of the eclipse ide.

https://drive.google.com/open?id=14OKHx0EkrgsB2zrP62t1OGjKK9lK5fNX