When I first read the abstract for Dreaming In Code by Scott Rosenberg, I figured it to be light reading to give my mind a break from other, more technical books on my nightstand.  Little did I realize that once I read past chapter 8, this little book would spark so many questions that I would have sleepless nights wondering "will we ever learn how to create software quickly and efficiently?" [Read More] Permalink  Comments [0]
These days, everyone's got something to say about Groovy.  Whether it's "the good, the bad or the ugly," Groovy is making great leaps with all its deserved, or undeserved, publicity.  With every tool available to the software developer, there are pros and cons; scenarios in which the tool works miracles or reaps havoc.  The main determining factor of success or failure is attitude. [Read More] Permalink  Comments [0]

For the past year, I've dual-booted my work laptop in Windows in order to connect to our IBM z/OS mainframe using the secure TN3270 emulator supported in our environment.  Tired of dual-booting, I finally decided to figure out how to avoid booting into Windows.  Quite simple really.  I have already been using x3270 to connect to Unix System Services for z/OS and just read the MANPAGE to find out that I could use:

x3270 L:zos.host.name:23

to make a secure TN3270 connection.  Now I ... finally! ... have no reason to ever boot into Windows.

Permalink  Comments [0]
Start and stop OpenVPN in Debian/Ubuntu when your interfaces come up/down by adding an openvpn stanza to your /etc/network/interfaces configuration file.
[Read More] Permalink  Comments [0]

Sometimes, the world of a developer can seem overwhelming.  With so many choices at our disposal, there are a lot of questions that must be asked and answered.  Which web framework do I use?  Which design pattern is best suited for this problem?  Do I use Java or a dynamic scripting language?  How much test coverage is too much?  Too little?  What shortcuts are acceptable in order to meet deadlines?  Driving down the developer path sometimes reminds me of driving through Manhattan during rush hour on a Friday afternoon; if you're not alert and on your toes you just might find yourself in the middle of a 10 car pile-up.

But not everyone wants to drive in Manhattan during rush hour.  Some like the relaxed and picturesque drive on the roads of a quiet suburb outside of Denver, Colorado, with the convertible top down and the wind blowing through their hair.  Some like the straight lines of Florida roads, in their souped up Corvette, with their radar detector and the occasional opportunity to "let it breath!"  Some like the twists and turns of the Smoky Mountains, on two wheels, staring death right in the eyes at every turn as their knees scrape asphalt.  Some just have all the time in the world and are in no rush to get anywhere, oblivious of the other road patrons around them.

In many ways, software development is like driving.  The language we use, as with the vehicle we drive, is the tool we use to get from point A to point B.  Coupe, sedan, station wagon, minivan, motorcycle; Java, C#, C++, FORTRAN, Python, Perl.  A project that you work on is like taking a road trip.  The project plan and the AAA trip planner serve the function of laying out the optimum path to get you to your goal.  Once you start on your merry way, it's understood that the possibility exists that you will be confronted with some decisions to avoid undesirable situations.  A domain model change can be like an unannounced road construction.  A use-case change can be like a sudden downpour.

There are many analogies between driving to work in the morning and driving down the developer path.  And maybe, taking a moment to stop and think about those analogies will help one to understand what their goals are as a developer.  A vehicle will take you but so far before it's useful life has ended.  The same is true about the development tools you use.  Automobiles pretty much all work the same; you steer, you go and you stop.  Programming languages pretty much all work the same.  Syntax and semantics aside, they all fulfill the ultimate goal of dictating what the computer should do.  Where we fit into the picture is in coming up with paradigms and methodologies that best fit our character, goals, ambitions and dreams.

This is my (hopefully not so feeble) attempt in bringing about thought-provoking and stimulating ideas from the developer community.  What analogies have you been introduced to that helped you to "think outside the box?"  To drive you to "go that extra mile" and come up with some innovative idea that (and it needn't necessarily be an original idea in the community, but original to you) had you say to yourself, "why didn't I think of that before?"

Permalink  Comments [0]

I've recently upgraded Groovy from 1.0-JSR-6 to 1.0 and started getting "No signature of method: static ... for argument types: ..." errors.  Upgrading further to version 1.1-beta3 exhibited the same behavior.  As an example, the following Groovy script will generate an error using Groovy version 1.0:

class S1 {
static String concat(String s, String c) {
  return s + c
}
}

class S2 extends S1 {
  static String concat(String s, Integer i) {
    return s + i
  }
}

println "Calling S2.concat(String,Integer) ..."
println S2.concat('foo', 1)
def m = S2.getMethod('concat', [String, String] as Class[])
println "S2.concat(String,String)=${m}\n\nCalling S2.concat(String,String) ..."
println S2.concat('foo', '1')

After a bit of searching, I found a Groovy Jira issue which explains the Groovy behavior for static method calls on subclasses is that "a static method is callable only on the class it is defined on."  This means care must be taken to ensure subclass inherited static method calls are avoided in certain versions of Groovy.

A number of approaches to get around this are:

  1. If you can, upgrade to Groovy version 1.1-RC1 which allows inherited static method calls.
  2. Change all inherited static method calls to use the class in which the static method is declared.
  3. Change all inherited static method calls to use an instance method call.
  4. Modify the subclass to include static methods delegating to the superclass static methods.
  5. Use Groovy version 1.0-JSR-6.

Java developers are accustomed to subclasses inheriting static methods from their superclass.  So Groovy's behavior with static methods should not be overlooked as a Groovy gotcha.  I've tested this behavior in versions 1.0-JSR-6, 1.0 and 1.1-beta3. 

UPDATE: Moments after posting this blog, Groovy version 1.1-RC1 was released.  I tested version 1.1-RC1 today (2007/10/14) and have found the above snippet will work properly.  Looking at the groovy.lang.MetaClassImpl.invokeStaticMethod() code shows that static methods are now being searched for by traversing up the class hierarchy.

Permalink  Comments [0]

With Groovy and Apache Commons Net, Sending SMTP authenticated email can be pretty easy.  SMTP AUTH is used by some ISPs to deter spam (or Unsolicited Commercial Emails, Unsolicited Bulk Emails, junk e-mail).  The SMTP AUTH extension is defined in RFC 2554 and the PLAIN SASL mechanism is defined in RFC 4616.  With this information, my favorite Mail User Agent and Wireshark, I got to work writing a prototype Groovy class to send SMTP AUTH email using the PLAIN SASL mechanism.  The SendEmailAuth class has a sendMessage() method that does all the work.  It accepts method parameters for the host name, host port, the user's email address, the user's password, a list of recipient email addresses, the message subject and the email message to send.  Using Apache Commons Net's SMTPClient, SimpleSMTPHeader and SMTPReply, the underlying protocol details are handled; all that's left is to pass in the necessary information to send a valid message.

import org.apache.commons.net.smtp.SimpleSMTPHeader
import org.apache.commons.net.smtp.SMTPClient
import org.apache.commons.net.smtp.SMTPReply

class SendEmailAuth {
static boolean sendMessage( \
String host, \
int port, \
String from, \
String pw, \
List recipients, \
String subject, \
String message)
{
assert host != null
assert host.trim() != ''
assert port != null
assert port > 0
assert port < 65535
assert from != null
assert pw != null
assert recipients != null
assert recipients.size() >= 1
assert subject != null
assert subject.trim() != ''
assert message != null
assert message.trim() != ''

int timeout = 2000
int soTimeout = 10000

def client = new SMTPClient()
client.defaultTimeout = timeout
println "Set default timeout to ${client.defaultTimeout}."

println "Connecting to ${host} ..."
try {
client.connect(host, port)
} catch (Throwable t) {
println "Error connecting to ${host}: ${t.localizedMessage}"
return
}
println "Connected to ${host}."
println client.replyString
int replyCode = client.replyCode
if (!SMTPReply.isPositiveCompletion(replyCode)) {
println "Server negative response with code ${replyCode}!"
disconnect(client)
return
}

client.soTimeout = soTimeout

replyCode = client.sendCommand("EHLO", "localhost")
println client.replyString

String authStr = '~' + from + '~' + pw
byte[] authBytes = authStr.bytes
authBytes[0] = 0
authBytes[from.size() + 1] = 0

replyCode = client.sendCommand("AUTH", "PLAIN " + authBytes.encodeBase64())
println client.replyString

if (!client.setSender(from)) {
println "Error setting sender!"
}
println client.replyString

recipients.each {
println "Adding recipient ${it} ..."
if (!client.addRecipient(it)) {
println "Error adding recipient!"
}
println client.replyString
}

def header = new SimpleSMTPHeader(from, recipients[0], subject)
if (recipients.size() > 1) {
recipients[1..-1].each {
println "Adding CC recipient ${it} ..."
header.addCC(it)
}
}
println "Headers ...\n${header}"

if (!client.sendShortMessageData(header.toString() + message)) {
println "Error sending message!"
}
println client.replyString

disconnect(client)
}

static void disconnect(SMTPClient client) {
if ((client != null) && (client.connected)) {
println "Disconnecting client ..."
try {
client.disconnect()
println "Client disconnected."
} catch (Throwable t) {
println "Error disconnecting client: ${t.localizedMessage}!"
}
}
}
}

A Groovy client would use the SendEmailAuth class as follows:

host = 'smtpauth.myisp-DOT-com'
port = 1234
from = 'me-AT-myisp.com'
pw = 'mypassword'
recipients = ['you-AT-anotherisp-DOT-com']
subject = 'Test'
message = 'Testing!'

SendEmailAuth.sendMessage(host, port, from, pw, recipients, subject, message)
Permalink  Comments [1]

Since the power of electricity was harnessed, people have been trying to get machines to think.  From vacuum tubes to DNA, Tic Tac Toe games have been implemented using many different methodologies.  In this implementation of the Tic Tac Toe game, the Drools Rules Engine is used to determine the computer's move and handle game play.

Rather than create an implementation that would play the traditional 3x3 grid, I wanted something that could be used to play on any grid from 3x3 to 10x10 and where either the computer or player could go first.  I created a TicTacToe class to maintain game play.  I'll let the code speak for itself, but to sum things up, the default constructor will create a game with a 3x3 grid where the computer moves first and the initial game status is NOTSTARTED.  The player can make her move by using setCellValue(x, y).  The game continues until there's a winner or there are no more moves possible.

package com.cruzalong;

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

public class TicTacToe {
public static enum Status {NOTSTARTED, STARTED, FINISHED}
public static enum RowType {X, Y, DIAGONAL}
public static enum CellValue {
UNSET, O, X;

public String toString() {
if (UNSET == this) {
return " ";
} else {
return this.name();
}
}
}

private int cellCount;
private CellValue startingPlayer = CellValue.O;
private Status status = Status.NOTSTARTED;
private CellValue[][] table;
private List<CellValue> tableList;
private int tableSize;
private CellValue winner = CellValue.UNSET;

public TicTacToe() {
this(3, CellValue.O);
}

public TicTacToe(final int ts, final CellValue sp) {
tableSize = ts;
startingPlayer = sp;
if (tableSize < 3) tableSize = 3;
if (tableSize > 10) tableSize = 10;
cellCount = (int)Math.pow(tableSize, 2);
reset();
}

public int getCellCount() {
return cellCount;
}

public synchronized CellValue getCellValue(int x, int y) {
if (x >= tableSize || y >= tableSize) {
return (CellValue)null;
} else {
return table[y][x];
}
}

public synchronized void setCellValue(int x, int y) {
setCellValue(x, y, CellValue.X);
}

public synchronized void setCellValue(int x, int y, CellValue cv) {
if (status == Status.STARTED && x >= 0 && y >= 0
&& x < tableSize && y < tableSize
&& cv != CellValue.UNSET && table[y][x] == CellValue.UNSET) {
table[y][x] = cv;
tableList.set(getTableListIndex(x, y), cv);
setWinner(x, y);
}
}

public int[] getCellXY(int i) {
int y = (i - (i % tableSize)) / tableSize;
int x = i % tableSize;
return new int[] {x, y};
}

public List<Integer> getCornerCellIndices() {
List<Integer> cornerCells = new ArrayList<Integer>(4);
cornerCells.add(Integer.valueOf(0));
cornerCells.add(Integer.valueOf(getTableSize() - 1));
cornerCells.add(Integer.valueOf(getCellCount() - getTableSize()));
cornerCells.add(Integer.valueOf(getCellCount() - 1));
return cornerCells;
}

public boolean isDiagonalCell(int x, int y) {
return isDiagonalDownCell(x, y) || isDiagonalUpCell(x, y);
}

public boolean isDiagonalDownCell(int x, int y) {
if (x == y) {
return true;
} else {
return false;
}
}

public boolean isDiagonalUpCell(int x, int y) {
if (x == tableSize - 1 - y) {
return true;
} else {
return false;
}
}

public List<CellValue> getRow(int row, RowType type) {
List<CellValue> rowList = new ArrayList<CellValue>(tableSize);
if (row < 0 || row >= tableSize) {
// Don't do anything
} else if (type == RowType.X) {
for (int i = 0; i < tableSize; i++) {
rowList.add(table[i][row]);
}
} else if (type == RowType.Y) {
for (int i = 0; i < tableSize; i++) {
rowList.add(table[row][i]);
}
} else if (type == RowType.DIAGONAL && row == 0) {
for (int i = 0; i < tableSize; i++) {
rowList.add(table[i][i]);
}
} else if (type == RowType.DIAGONAL && row > 0) {
for (int i = 0; i < tableSize; i++) {
rowList.add(table[tableSize - 1 - i][i]);
}
}
return Collections.unmodifiableList(rowList);
}

public CellValue getStartingPlayer() {
return startingPlayer;
}

public Status getStatus() {
return status;
}

public void setStatus(Status status) {
this.status = status;
}

public CellValue[][] getTable() {
return table;
}

public List<CellValue> getTableList() {
return Collections.unmodifiableList(tableList);
}

private void buildTableList() {
tableList = new ArrayList<CellValue>(cellCount);
for (int i = 0; i < cellCount; i++) {
int[] xy = getCellXY(i);
tableList.add(table[xy[1]][xy[0]]);
}
}

public int getTableListIndex(int x, int y) {
return (y * tableSize) + x;
}

public int getTableSize() {
return tableSize;
}

public CellValue getWinner() {
return winner;
}

private boolean isWinner(List<CellValue> row) {
boolean same = false;
if (row.size() > 0) {
CellValue cv = CellValue.UNSET;
same = true;
for (CellValue v: row) {
if (v == CellValue.UNSET) {
same = false;
break;
} else if (cv == CellValue.UNSET) {
cv = v;
} else if (v != cv) {
same = false;
}
}
}
return same;
}

private void setWinner(int x, int y) {
boolean winner = false;
winner = isWinner(getRow(x, RowType.X));
if (!winner) {
winner = isWinner(getRow(y, RowType.Y));
}
if (!winner && isDiagonalCell(x, y)) {
winner = isWinner(getRow(0, RowType.DIAGONAL));
if (!winner) {
winner = isWinner(getRow(tableSize - 1, RowType.DIAGONAL));
}
}
if (winner) {
this.winner = table[y][x];
status = Status.FINISHED;
} else if (!tableList.contains(CellValue.UNSET)) {
this.winner = CellValue.UNSET;
status = Status.FINISHED;
}
}

public void reset() {
table = new CellValue[tableSize][tableSize];
for (int y = 0; y < tableSize; y++) {
for (int x = 0; x < tableSize; x++) {
table[y][x] = CellValue.UNSET;
}
}
buildTableList();
status = Status.NOTSTARTED;
winner = CellValue.UNSET;
}

public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("Current Status: ");
sb.append(status);
sb.append("\n");
for (CellValue[] cvs: table) {
sb.append("|");
for (CellValue cv: cvs) {
sb.append(cv);
sb.append("|");
}
sb.append("\n");
}
if (Status.FINISHED == status
&& !getTableList().contains(CellValue.UNSET)) {
sb.append("Winner: ");
if (winner == CellValue.UNSET) {
sb.append("NONE");
} else {
sb.append(winner);
}
sb.append("\n");
}
return sb.toString();
}
}

I've created a Main class that will create a TicTacToe instance using command-line arguments to set the grid size and which player starts first, X (the human player) or O (the computer player).  A Swing GUI is created using a grid of buttons for each cell.

package com.cruzalong;

import java.awt.Color;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.Border;
import org.drools.RuleBase;
import org.drools.WorkingMemory;
import org.drools.event.DebugWorkingMemoryEventListener;
import org.drools.io.RuleBaseLoader;
import org.drools.io.RuleSetLoader;

/**
* Drools 2.5 Tic-Tac-Toe game example.
* @author Dan Cruz
* @see http://legacy.drools.codehaus.org/
*/
public class Main {
public static void main(String[] args) {
int size = 3;
TicTacToe.CellValue sp = TicTacToe.CellValue.O;
if (args.length > 0 && args[0].matches("\\d+")) {
size = Integer.valueOf(args[0]);
}
if (args.length > 1 && "x".equals(args[1].toLowerCase())) {
sp = TicTacToe.CellValue.X;
}
tictactoe(size, sp);
}

private static WorkingMemory getWorkingMemory() {
RuleSetLoader rsl = new RuleSetLoader();
RuleBaseLoader rbl = new RuleBaseLoader();
try {
rsl.addFromInputStream(Main.class.getClassLoader()
.getResourceAsStream("com/cruzalong/tictactoe.drl"));
rbl.addFromRuleSetLoader(rsl);
} catch (Throwable t) {
t.printStackTrace();
}
RuleBase rb = rbl.buildRuleBase();
WorkingMemory wm = rb.newWorkingMemory();
wm.addEventListener(new DebugWorkingMemoryEventListener());
return wm;
}

private static void tictactoe(final int size,
final TicTacToe.CellValue sp) {
final TicTacToe ttt = new TicTacToe(size, sp);
final WorkingMemory wm = getWorkingMemory();

GridBagLayout layout = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
Font font = new Font(Font.MONOSPACED, Font.BOLD, 24);
Border blackLine = BorderFactory.createLineBorder(Color.BLACK);
JPanel panel = new JPanel();
panel.setLayout(layout);
panel.setBorder(blackLine);

final List<JButton> buttons =
new ArrayList<JButton>(ttt.getCellCount());
for (int i = 0; i < ttt.getCellCount(); i++) {
int[] xy = ttt.getCellXY(i);
JButton button = button(wm, ttt, xy[0], xy[1], font);
c.gridx = xy[0];
c.gridy = xy[1];
panel.add(button, c);
buttons.add(button);
}
wm.setApplicationData("buttons", buttons);

c.gridx = 0;
c.gridwidth = ttt.getTableSize();

final JLabel label = new JLabel("Click on a square.");
JButton reset = new JButton("Reset");

reset.setHorizontalAlignment(AbstractButton.CENTER);
reset.setVerticalAlignment(AbstractButton.CENTER);
reset.setActionCommand("clicked");
reset.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if ("clicked".equals(e.getActionCommand())) {
ttt.reset();
for (int i = 0; i < buttons.size(); i++) {
JButton button = buttons.get(i);
int[] xy = ttt.getCellXY(i);
button.setText(ttt.getCellValue(xy[0], xy[1])
.toString());
button.setEnabled(true);
}
label.setText("Click on a square.");
try {
for (Object o: wm.getObjects()) {
wm.retractObject(wm.getFactHandle(o));
}
wm.clearAgenda();
wm.assertObject(ttt);
wm.fireAllRules();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
});
c.gridy++;
panel.add(reset, c);

c.gridy++;
panel.add(label, c);
wm.setApplicationData("label", label);

panel.setOpaque(true);
panel.setVisible(true);

JFrame frame = new JFrame("Tic Tac Toe");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(panel);
frame.pack();

frame.setVisible(true);

try {
wm.assertObject(ttt);
wm.fireAllRules();
} catch (Throwable t) {
t.printStackTrace();
}
}

private static JButton button(final WorkingMemory wm,
final TicTacToe ttt, final int x, final int y, final Font font) {
JButton button = new JButton(TicTacToe.CellValue.UNSET.toString());
button.setHorizontalAlignment(AbstractButton.CENTER);
button.setVerticalAlignment(AbstractButton.CENTER);
button.setFont(font);
button.setActionCommand("clicked");
button.addActionListener(new ActionListener() {
private WorkingMemory m = wm;
private TicTacToe t = ttt;
private int ax = x;
private int ay = y;
public void actionPerformed(ActionEvent e) {
if (t.getStatus() != TicTacToe.Status.STARTED) {
t.setStatus(TicTacToe.Status.STARTED);
}
boolean validEvent = false;
TicTacToe.CellValue value = null;
if ("clicked".equals(e.getActionCommand())) {
validEvent = true;
value = TicTacToe.CellValue.X;
}
if (validEvent) {
t.setCellValue(ax, ay, value);
((JButton)e.getSource())
.setText(t.getCellValue(ax, ay).toString());
((JButton)e.getSource()).setEnabled(false);
try {
m.modifyObject(m.getFactHandle(t), t);
m.fireAllRules();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
});

return button;
}
}

For the Drools rules, the following rules have been created:

  • StartComputer: If the game has not been started and the computer is the first player, then start the game.
  • StartPlayer: If the game has not been started and the player is the first player, then start the game and clear the agenda.
  • Finished: If the game is finished, display the winner in the label and disable all buttons.
  • CreateWeights: initialize a list of weights for all cells in the grid with an initial weight of -1.
  • WeightForValidCellValue: set the weight for cells that have been played to 0.
  • WeightForOppositeCorner: if the human player started first and has played in a corner, assign a higher weight to the opposite corner and a weight of 0 to all other cells.
  • WeightForUnsetCellValue: for cells that have not been played, calculate the cell's weight based on the number of cells in the horizontal, vertical and diagonal rows that have a played value in them, giving more weight to cells which have been played by the human player.
  • MaxWeightMultiples: After all cells have been assigned a weight, if there are multiple cells with the same maximum weight value, review all horizontal, vertical and diagonal rows again and increase the cell's weight for each cell played by the computer.  If there's still no single cell with a maximum weight, pick one at random and give it a higher weight.
  • SetComputerCellValue: Once a single cell has a maximum weight value, set the cell's value to O, set the corresponding button's text value to O and disable the button.
<?xml version="1.0" encoding="UTF-8"?>
<rule-set name="tictactoe"
xmlns="http://drools.org/rules"
xmlns:java="http://drools.org/semantics/java"
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="http://drools.org/rules rules.xsd
http://drools.org/semantics/java java.xsd"
>
<import>com.cruzalong.TicTacToe</import>
<import>java.util.Collections</import>
<import>java.util.Iterator</import>
<import>java.util.List</import>
<import>javax.swing.JButton</import>

<application-data identifier="buttons">java.util.List</application-data>
<application-data identifier="label">javax.swing.JLabel</application-data>

<rule name="StartComputer">
<parameter identifier="t">
<class>TicTacToe</class>
</parameter>
<java:condition>t.getStatus() == TicTacToe.Status.NOTSTARTED</java:condition>
<java:condition>t.getStartingPlayer() == TicTacToe.CellValue.O</java:condition>
<java:consequence>
t.setStatus(TicTacToe.Status.STARTED);
drools.modifyObject(t);
</java:consequence>
</rule>

<rule name="StartPlayer">
<parameter identifier="t">
<class>TicTacToe</class>
</parameter>
<java:condition>t.getStatus() == TicTacToe.Status.NOTSTARTED</java:condition>
<java:condition>t.getStartingPlayer() == TicTacToe.CellValue.X</java:condition>
<java:consequence>
t.setStatus(TicTacToe.Status.STARTED);
drools.clearAgenda();
</java:consequence>
</rule>

<rule name="Finished">
<parameter identifier="t">
<class>TicTacToe</class>
</parameter>
<java:condition>t.getStatus() == TicTacToe.Status.FINISHED</java:condition>
<java:consequence>
String winner = "NONE";
if (t.getWinner() != TicTacToe.CellValue.UNSET) {
winner = t.getWinner().toString();
}
label.setText("The winner is " + winner + ".");
for (Iterator it = buttons.iterator(); it.hasNext();) {
JButton button = (JButton)it.next();
button.setEnabled(false);
}
drools.retractObject(t);
</java:consequence>
</rule>

<rule name="CreateWeights" no-loop="true">
<parameter identifier="t">
<class>TicTacToe</class>
</parameter>
<java:condition>t.getStatus() == TicTacToe.Status.STARTED</java:condition>
<java:consequence><![CDATA[
import java.util.ArrayList;
List weights = new ArrayList(t.getCellCount());
for (int x = 0; x < t.getCellCount(); x++) {
weights.add(new Integer(-1));
}
drools.assertObject(weights);
]]></java:consequence>
</rule>

<rule name="WeightForValidCellValue" no-loop="true">
<parameter identifier="t">
<class>TicTacToe</class>
</parameter>
<parameter identifier="weights">
<class>List</class>
</parameter>
<java:condition>t.getStatus() == TicTacToe.Status.STARTED</java:condition>
<java:condition>t.getTableList().contains(TicTacToe.CellValue.X) || t.getTableList().contains(TicTacToe.CellValue.O)</java:condition>
<java:condition>weights.contains(new Integer(-1))</java:condition>
<java:consequence><![CDATA[
List values = t.getTableList();
boolean modified = false;
for (int i = 0; i < values.size(); i++) {
TicTacToe.CellValue cv = (TicTacToe.CellValue)values.get(i);
if (cv != TicTacToe.CellValue.UNSET) {
weights.set(i, new Integer(0));
modified = true;
}
}
if (modified) drools.modifyObject(weights);
]]></java:consequence>
</rule>

<rule name="WeightForOppositeCorner">
<parameter identifier="t">
<class>TicTacToe</class>
</parameter>
<parameter identifier="weights">
<class>List</class>
</parameter>
<java:condition>t.getStatus() == TicTacToe.Status.STARTED</java:condition>
<java:condition>t.getTableList().contains(TicTacToe.CellValue.UNSET)</java:condition>
<java:condition>weights.contains(new Integer(-1))</java:condition>
<java:condition>Collections.frequency(t.getTableList(), TicTacToe.CellValue.X) == 1</java:condition>
<java:condition>t.getCornerCellIndices().contains(new Integer(t.getTableList().indexOf(TicTacToe.CellValue.X)))</java:condition>
<java:consequence><![CDATA[
int xCrnr = t.getTableList().indexOf(TicTacToe.CellValue.X);
int opCrnr = t.getCellCount() - 1 - xCrnr;
for (int i = 0; i < weights.size(); i++) {
weights.set(i, new Integer(0));
}
weights.set(opCrnr, new Integer((int)Math.pow(t.getCellCount(), 2)));
drools.modifyObject(weights);
]]></java:consequence>
</rule>

<rule name="WeightForUnsetCellValue" no-loop="true">
<parameter identifier="t">
<class>TicTacToe</class>
</parameter>
<parameter identifier="weights">
<class>List</class>
</parameter>
<java:condition>t.getStatus() == TicTacToe.Status.STARTED</java:condition>
<java:condition>t.getTableList().contains(TicTacToe.CellValue.UNSET)</java:condition>
<java:condition>weights.contains(new Integer(-1))</java:condition>
<java:consequence><![CDATA[
List values = t.getTableList();
boolean modified = false;
for (int i = 0; i < values.size(); i++) {
TicTacToe.CellValue cv = (TicTacToe.CellValue)values.get(i);
if (cv == TicTacToe.CellValue.UNSET) {
int[] xy = t.getCellXY(i);
int weight = 0;
if (t.isDiagonalCell(xy[0], xy[1])) weight++;
List row = t.getRow(xy[0], TicTacToe.RowType.X);
int xCnt = Collections.frequency(row, TicTacToe.CellValue.X);
int oCnt = Collections.frequency(row, TicTacToe.CellValue.O);
int uCnt = Collections.frequency(row, TicTacToe.CellValue.UNSET);
weight += (xCnt * 2) + oCnt;
if (uCnt == 1 && t.getTableSize() - xCnt == 1) weight += t.getTableSize() * 2;
if (uCnt == 1 && t.getTableSize() - oCnt == 1) weight += t.getTableSize();
row = t.getRow(xy[1], TicTacToe.RowType.Y);
xCnt = Collections.frequency(row, TicTacToe.CellValue.X);
oCnt = Collections.frequency(row, TicTacToe.CellValue.O);
uCnt = Collections.frequency(row, TicTacToe.CellValue.UNSET);
weight += (xCnt * 2) + oCnt;
if (uCnt == 1 && t.getTableSize() - xCnt == 1) weight += t.getTableSize() * 2;
if (uCnt == 1 && t.getTableSize() - oCnt == 1) weight += t.getTableSize();
if (t.isDiagonalCell(xy[0], xy[1])) {
if (t.isDiagonalDownCell(xy[0], xy[1])) {
row = t.getRow(0, TicTacToe.RowType.DIAGONAL);
xCnt = Collections.frequency(row, TicTacToe.CellValue.X);
oCnt = Collections.frequency(row, TicTacToe.CellValue.O);
uCnt = Collections.frequency(row, TicTacToe.CellValue.UNSET);
weight += (xCnt * 2) + oCnt;
if (uCnt == 1 && t.getTableSize() - xCnt == 1) weight += t.getTableSize() * 2;
if (uCnt == 1 && t.getTableSize() - oCnt == 1) weight += t.getTableSize();
}
if (t.isDiagonalUpCell(xy[0], xy[1])) {
row = t.getRow(t.getTableSize() - 1, TicTacToe.RowType.DIAGONAL);
xCnt = Collections.frequency(row, TicTacToe.CellValue.X);
oCnt = Collections.frequency(row, TicTacToe.CellValue.O);
uCnt = Collections.frequency(row, TicTacToe.CellValue.UNSET);
weight += (xCnt * 2) + oCnt;
if (uCnt == 1 && t.getTableSize() - xCnt == 1) weight += t.getTableSize() * 2;
if (uCnt == 1 && t.getTableSize() - oCnt == 1) weight += t.getTableSize();
}
}
weights.set(i, new Integer(weight));
modified = true;
}
}
if (modified) drools.modifyObject(weights);
]]></java:consequence>
</rule>

<rule name="MaxWeightMultiples" no-loop="true">
<parameter identifier="t">
<class>TicTacToe</class>
</parameter>
<parameter identifier="weights">
<class>List</class>
</parameter>
<java:condition>t.getStatus() == TicTacToe.Status.STARTED</java:condition>
<java:condition><![CDATA[Collections.frequency(weights, Collections.max(weights)) > 1]]></java:condition>
<java:consequence><![CDATA[
import java.util.Random;

Integer max = (Integer)Collections.max(weights);
int freq = Collections.frequency(weights, max);
List values = t.getTableList();
boolean modified = false;
for (int i = 0; i < values.size(); i++) {
TicTacToe.CellValue cv = (TicTacToe.CellValue)values.get(i);
if (weights.get(i).equals(max)
&& cv == TicTacToe.CellValue.UNSET) {
int[] xy = t.getCellXY(i);
int weight = max.intValue();
List row = t.getRow(xy[0], TicTacToe.RowType.X);
for (Iterator it = row.iterator(); it.hasNext();) {
TicTacToe.CellValue r = (TicTacToe.CellValue)it.next();
if (r == TicTacToe.CellValue.O) {
weight++;
}
}
row = t.getRow(xy[1], TicTacToe.RowType.Y);
for (Iterator it = row.iterator(); it.hasNext();) {
TicTacToe.CellValue r = (TicTacToe.CellValue)it.next();
if (r == TicTacToe.CellValue.O) {
weight++;
}
}
if (t.isDiagonalCell(xy[0], xy[1])) {
for (int dci = 0; dci < t.getTableSize(); dci++) {
if (t.isDiagonalDownCell(xy[0], xy[1])) {
weight++;
if (t.getCellValue(dci, dci) == TicTacToe.CellValue.O) {
weight++;
}
}
if (t.isDiagonalUpCell(xy[0], xy[1])) {
weight++;
if (t.getCellValue(t.getTableSize() - 1 - dci, dci) == TicTacToe.CellValue.X) {
weight++;
}
}
}
}
weights.set(i, new Integer(weight));
modified = true;
}
}
max = (Integer)Collections.max(weights);
freq = Collections.frequency(weights, max);

if (Collections.frequency(weights, max) > 1) {
int[] idx = new int[freq];
int cnt = 0;
for (int i = 0; i < weights.size(); i++) {
if (max.equals(weights.get(i))) idx[cnt++] = i;
}
int rnd = new Random().nextInt(idx.length);
weights.set(idx[rnd], new Integer(max.intValue() + 1));
modified = true;
}

if (modified) drools.modifyObject(weights);
]]></java:consequence>
</rule>

<rule name="SetComputerCellValue">
<parameter identifier="t">
<class>TicTacToe</class>
</parameter>
<parameter identifier="weights">
<class>List</class>
</parameter>
<java:condition>t.getStatus() == TicTacToe.Status.STARTED</java:condition>
<java:condition>t.getTableList().contains(TicTacToe.CellValue.UNSET)</java:condition>
<java:condition>Collections.frequency(weights, Collections.max(weights)) == 1</java:condition>
<java:consequence><![CDATA[
Integer max = (Integer)Collections.max(weights);
int idx = weights.indexOf(max);
int[] xy = t.getCellXY(idx);
t.setCellValue(xy[0], xy[1], TicTacToe.CellValue.O);
((JButton)buttons.get(idx)).setText(TicTacToe.CellValue.O.toString());
((JButton)buttons.get(idx)).setEnabled(false);
drools.retractObject(weights);
if (t.getStatus() == TicTacToe.Status.FINISHED) {
drools.modifyObject(t);
}
]]></java:consequence>
</rule>
</rule-set>

I hope to also do an implementation using the latest Drools and play around with the Drools Eclipse plugin.  My thoughts are still up in the air regarding whether the ability to use different languages (Java, Groovy, Python, C#, etc.) in the rules is a desireable feature and when it should be used.  It does expose Drools to a greater set of developers though.  And there are certain constructs which are easier done in Groovy than Java.  I also see a Drools module for CLIPS.

Permalink  Comments [1]

Recently, I've been having problems with my system freezing whenever I tried to run Eclipse while an NFS share was mounted.  Little did I realize, every time I've been clicking on my Eclipse application launcher in my Gnome desktop panel, Eclipse was using the GNU Interpreter for Java (GIJ) JVM and not the JRockit JVM I had configured in my JAVA_HOME and PATH environment variables.  Remembering I had had issues with GIJ and NFS before, I knew this was the cause of my system freezes.  But why wasn't Eclipse using the Java executable I had in my PATH?

Eclipse will search the PATH environment variable for the Java executable to use.  In Ubuntu Edgy Eft, GIJ is the default JVM that is installed.  Since Debian's update-alternatives is used, /usr/bin/java is linked to /etc/alternatives/java, which is then linked to /usr/bin/gij-wrapper-4.1.  I personally don't like to use APT to maintain Java and all the different libraries I install; it takes too long for the latest versions to be available and it's cumbersome to install and maintain different versions of Java, Ant, Eclipse, etc. using APT.  So I install everything in my $HOME/java directory and can keep all the different versions I want and switch back and forth as needed.

One way to configure the JAVA_HOME, PATH and other environment variables is in the /etc/environment file.  This file is used for system-wide environment variables, and they are available to interactive and non-interactive shells and scripts.  At a per-user level, environment variables may be exported from the $HOME/.bashrc file.  This will expose the environment variables in the interactive Bash shell and scripts which are executed from an interactive shell.  Since I like to give Eclipse a little more heap size than is given by default, I have a $HOME/bin/eclipse.sh script to start Eclipse as follows:

#!/bin/sh
"${ECLIPSE_HOME}/eclipse" -vm "${JAVA_HOME}/bin/java" -vmargs -Xms128m -Xmx128m &

This works fine when starting Eclipse from an interactive shell since my login shell is /bin/bash and the $HOME/.bashrc file is processed.  But this will not work if I add a custom application launcher to my menu or panel to run the script.  This is because Gnome uses MIME to determine the file type, files with content that start with #!/bin/sh are matched to MIME type application/x-shellscript, Gnome runs the script in a non-interactive /bin/sh shell (I've yet to find where application/x-shellscript is linked to /bin/sh, but I do know the shebang has nothing to do with it) and, since it's non-interactive, no startup files are automatically read and sourced into the shell environment.

So, I did a lot of digging for a solution and found a few different ways around this here, here, here, here, here, here, and here.  But the solution I finally ended up with, and was most comfortable with, was to create a $HOME/.profile file to initialize my environment variables.  This seems the best solution because the /etc/gdm/Xsession sources the $HOME/.profile file, which makes the exported environment variables available to Gnome and should make it available to X and KDE applications as well.  Also, the $HOME/.profile file would be used for interactive /bin/sh shells and scripts and, after adding a few lines in the $HOME/.bashrc file to source the $HOME/.profile file, the interactive Bash shell will also see them.  I've changed my eclipse.sh script to:

#!/bin/sh
[ ! -t 0 -a -z "${JAVA_HOME}" -a -f "${HOME}/.profile" ] && . "${HOME}/.profile"
${ECLIPSE_HOME}/eclipse -vm "${JAVA_HOME}/bin/java" -vmargs -Xms128m -Xmx128m &

The "! -t 0" test will check that the 0 (standard input) file descriptor is not open and associated to a terminal (a non-interactive shell).  Actually, the script did not need to be changed; but since this script follows me onto different systems, I added the change to hopefully make it more environment-independent.  Now JRockit is used by Eclipse and my system won't freeze if I have an NFS share mounted.

Permalink  Comments [0]

In addition to the six transaction propagation attributes found in Enterprise Java Bean (EJB) Container-Managed Transactions (CMT), Spring Framework supports a seventh propagation attribute called nested.  So what is nested propagation in a transaction?  Before answering that question, let's take a little walk through the history of television (don't quote me on this history).

In the 1930's, television became commercially available.  It quickly became a huge hit.  But, in a time when the husband worked and the wife stayed home, poor hubby was getting a bit jealous when he came home and his wife went on and on about all the telly she had watched during the day.  Men, being the ingenious inventors we are, banged our heads together for 40 years before coming up with a viable solution which allowed them to watch shows anytime.  This invention was called the Videocassette Recorder (VCR).  This was great; programs could be recorded and watched at their leisure.  But it had its limitations; if you wanted to record two programs that were on at the same time, you needed two VCRs.  And since it took a college degree to figure out how to set this up properly, men were happy with it.

By around year 2000, the Women's Rights Movement had matured and women were fed up with the outdated, male invention that was the VCR and needed something better to suit their needs.  So after a few months, they came up with the Digital Video Recorder (DVR).  The DVR did not require tapes that only recorded a few minutes of telly, but instead had a hard drive with a lot of space to record whole seasons of shows.  Most DVRs come with two receivers; so you can record one show and watch another show at the same time.  If you had three receivers in your DVR unit, you could record two programs and watch telly at the same time.  You could even pause a show you're watching, switch to another program, come back to your paused show and continue watching it after the second show had finished.  WOW; everyone's happy now (at least for another 20 years).

So, my version of the history of television aside, what does this have to do with Spring Framework transactions with nested propagation?  What DVR brought to the table for the tube is analogous to what nested propagation brings to transactions.  You can "bookmark" a transaction at a certain point (pause the program), do some other work in the transaction (watch another program), come back to your transaction  at the point it was "bookmarked" (go back to your paused program) and continue the transaction at that point (press play on your paused program).  This bit of Spring DVR feature is accomplished by use of Java Database Connectivity (JDBC) 3.0 Savepoints.

If you have enabled nested transactions, are using a JDBC driver that supports savepoints, and are using a Spring Framework DataSourceTransactionManager or a PlatformTransactionManager implementation that uses JDBC Savepoints for nested transactions, then you can take advantage of nested transactions.  This could be used, for example, to break up a transaction that has a lot of SELECTs, INSERTs and UPDATEs into multiple nested transactions without the extra overhead of starting and stopping transactions and initializing and releasing resources.

One thing to be careful of if you are using Object-Relational Mapping (ORM) solutions; the ORM resource and JDBC Connection are bound to the nearest transaction in the call stack.  For example, suppose we have three services: A.serviceA(), B.serviceB() and C.serviceC().  A.serviceA() calls B.serviceB() then calls C.serviceC().  A.serviceA() is configured with REQUIRES_NEW propagation and B.serviceB() and C.serviceC() are configured with NESTED propagation.  B.serviceB() uses Hibernate and C.serviceC() uses JDBC.  The resources will be bound to the transaction around the A.serviceA() method.  If B.serviceB() executes a HibernateTemplate.delete() command, the execution of the DELETE statement will be queued by Hibernate until the Session is flushed or closed at the end of the transaction (after leaving the A.serviceA() method).  C.serviceC() will not know about the queued DELETE statement in the Hibernate Session; this may or may not cause problems.  In the case of ORM solutions, it's probably best to configure operations which modify data with REQUIRES_NEW propagation; especially if these operations will be used in a service that also uses JDBC operations.

Permalink  Comments [0]

Spring Framework's Data Access Object (DAO) support provides integration of heterogeneous Java Database Connectivity (JDBC) and Object-Relational Mapping (ORM) products.  Spring Framework makes it easy to integrate these solutions, easing tasks pertaining to resource management and transaction management.  This allows for an environment in which multiple correlated projects may exist together, using DAO implementations that use different persistence strategies.

DAO Support

Spring Framework provides excellent DAO support.  With Spring Framework 2.0.6, DAO support is provided for JDBC, Hibernate, iBATIS, Java Data Objects (JDO), Java Persistence API (JPA), Oracle's TopLink and Common Client Interface (CCI).  Support for other ORM solutions is provided through the Spring Modules project; currently, there's support for Apache's ObjectRelationalBridge (OJB) and O/R Broker.  By extending one of the abstract DaoSupport sub-classes, your DAO implementation will have available to it a Spring Framework template and some convenience methods for accessing resources.  The Spring Framework template object provides convenience methods for common operations with the underlying persistence solution.  It also provides an execute method which allows for executing arbitrary code in a application callback object within the constraints of a transaction.

Transaction Management Support

Spring Framework's transaction management support provides an abstraction layer that shields developers from having to implement boiler-plate code to handle resources and transactions and from having to know different transaction management APIs.  All of Spring Framework's many DAO support solutions are aware of Spring Framework transactions, no matter what the underlying transaction management implementation is.  With Spring Framework, you can use non-invasive declarative transaction demarcation or programmatic transaction demarcation.

Example

With all of these options available for data persistence and transaction management, things can get a bit tricky when trying to integrate multiple projects which use disparate persistence solutions.  If Spring Framework is chosen as a solution, and existing code can be refactored to use an appropriate Spring Framework DAO abstration, resource and transaction management become a trivial configuration task and code is not cluttered with boiler-plate resource and transaction management code.

DataSource

A single data source is accessed using JDBC, Hibernate and iBATIS.  To focus on DAO and transaction management, a very simple domain model is used.  The database includes three tables: AA, BB and CC.  All tables have an auto-generated ID INT primary key column, a VERSION TIMESTAMP column and a TXT VARCHAR(8) column.  The BB table also has a AA_ID foreign key column and the CC table also has a BB_ID foreign key column.

Figure 1: Entity Relationship Diagram
AA BB CC Tables

The database is implemented using the H2 Database Engine.  The corresponding Spring bean definition is:

Table 1: DataSource Spring Bean Definition
<bean id="dataSource"
class="org.springframework.jdbc.datasource.SingleConnectionDataSource"
destroy-method="destroy">
<property name="driverClassName" value="org.h2.Driver"/>
<property name="url" value="jdbc:h2:mem:example"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
<property name="autoCommit" value="false"/>
<property name="suppressClose" value="true"/>
</bean>

DAO

This example code is not meant to be a proper implementation; it only contains enough code to show different DAO implementations using different persistence solutions which participate in the same transaction.  Also, it's most unlikely that multiple persistence solutions would be used for different tables within the same subject area; doing so means you can't use your ORM solution of choice to manage table relationships.  The DAO implementations implement the following simple Dao interface:

Table 2: Dao Interface
package example.dao;

public interface Dao<T> {
int create(T obj);
void deleteById(int id);
T getById(int id);
}

JDBC DAO

Here is the code for the JdbcDao implementation handling DAO operations for the AA table:

Table 3: JdbcDao Class
package example.dao.support;

import example.dao.Dao;
import example.domain.Aa;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcDaoSupport;

public class JdbcDao extends SimpleJdbcDaoSupport implements Dao<Aa> {
public int create(Aa obj) {
getSimpleJdbcTemplate().update("insert into aa (id, version, txt) "
+ "values (default, default, ? ) ",
new Object[] {obj.getTxt()});
return getSimpleJdbcTemplate().queryForInt("select identity() ");
}

public void deleteById(int id) {
getSimpleJdbcTemplate().update("delete from aa where id = ? ",
new Object[] {id});
}

public Aa getById(int id) {
return getSimpleJdbcTemplate().queryForObject(
"select * from aa where id = ? ", new AaRowMapper(), id);
}

private class AaRowMapper implements ParameterizedRowMapper<Aa> {
public Aa mapRow(ResultSet rs, int rowNum) throws SQLException {
Aa aa = new Aa();
aa.setId(rs.getInt("id"));
aa.setVersion(rs.getTimestamp("version"));
aa.setTxt(rs.getString("txt"));
return aa;
}
}
}

The corresponding Spring bean definition is:

Table 4: JDBC DAO Spring Bean Definition
<bean id="jdbcDao" class="example.dao.support.JdbcDao">
<property name="dataSource"><ref local="dataSource"/></property>
</bean>

Hibernate DAO

Here is the code for the HibernateDao implementation handling DAO operations for the BB table:

Table 5: HibernateDao Class
package example.dao.support;

import example.dao.Dao;
import example.domain.Bb;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public class HibernateDao extends HibernateDaoSupport implements Dao<Bb> {
public int create(Bb obj) {
return ((Integer)getHibernateTemplate().save(obj)).intValue();
}

public void deleteById(int id) {
Bb bb = getById(id);
getHibernateTemplate().delete(bb);
}

public Bb getById(int id) {
return (Bb)getHibernateTemplate().get(Bb.class, id);
}
}

The corresponding Spring bean definitions are:

Table 6: Hibernate DAO Spring Bean Definitions
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource"><ref local="dataSource"/></property>
<property name="hibernateProperties">
<value>hibernate.dialect=org.hibernate.dialect.H2Dialect</value>
</property>
<property name="mappingResources">
<list>
<value>/example/domain/Bb.hbm.xml</value>
</list>
</property>
</bean>

<bean id="hibernateDao" class="example.dao.support.HibernateDao">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>

iBATIS DAO

Here is the code for the IbatisDao implementation handling DAO operations for the CC table:

Table 7: IbatisDao Class
package example.dao.support;

import example.dao.Dao;
import example.domain.Cc;
import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;

public class IbatisDao extends SqlMapClientDaoSupport implements Dao<Cc> {
public int create(Cc obj) {
return (Integer)getSqlMapClientTemplate().insert("insertCc", obj);
}

public void deleteById(int id) {
getSqlMapClientTemplate().delete("deleteCcById", id);
}

public Cc getById(int id) {
return (Cc)getSqlMapClientTemplate().queryForObject("selectCcById", id);
}
}

The corresponding Spring bean definitions are:

Table 8: iBATIS DAO Spring Bean Definitions
<bean id="sqlMapClient"
class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="example/domain/SqlMapConfig.xml"/>
<property name="dataSource"><ref local="dataSource"/></property>
</bean>

<bean id="ibatisDao" class="example.dao.support.IbatisDao">
<property name="sqlMapClient"><ref local="sqlMapClient"/></property>
</bean>

Service

The service interface has one no-argument method called runExample.  The service implementation class' constructor receives a DataSource, JdbcDao, HibernateDao and IbatisDao reference.  Since an in-memory H2 database is being used, the constructor calls the private createTables() method to initialize the tables in the database.  The runExample() method will create a record in the AA, BB and CC tables, get a corresponding domain model object for the records that were created and delete the records that were created.

Here is the code for a simple service interface and implementation:

Table 9: Service interface and ServiceImpl Class
package example.service;

public interface Service {
void runExample();
}

package example.service.support;

import example.dao.Dao;
import example.domain.Aa;
import example.domain.Bb;
import example.domain.Cc;
import example.service.Service;
import java.sql.Connection;
import java.sql.Statement;
import javax.sql.DataSource;

public class ServiceImpl implements Service {
private final DataSource ds;
private final Dao<Bb> hibernateDao;
private final Dao<Cc> ibatisDao;
private final Dao<Aa> jdbcDao;

public ServiceImpl(DataSource ds, Dao<Bb> hibernateDao, Dao<Cc> ibatisDao,
Dao<Aa> jdbcDao) {
this.ds = ds;
this.hibernateDao = hibernateDao;
this.ibatisDao = ibatisDao;
this.jdbcDao = jdbcDao;
createTables();
}

public void runExample() {
Aa aa = new Aa();
aa.setTxt("AATEST1");
int aaId = jdbcDao.create(aa);

Bb bb = new Bb();
bb.setTxt("BBTEST1");
bb.setAaId(aaId);
int bbId = hibernateDao.create(bb);

Cc cc = new Cc();
cc.setTxt("CCTEST1");
cc.setBbId(bbId);
int ccId = ibatisDao.create(cc);

aa = jdbcDao.getById(aaId);
bb = hibernateDao.getById(bbId);
cc = ibatisDao.getById(ccId);

ibatisDao.deleteById(ccId);
hibernateDao.deleteById(bbId);
jdbcDao.deleteById(aaId);
}

private void createTables() {
Connection conn = null;
Statement st = null;
try {
conn = ds.getConnection();
st = conn.createStatement();
String sql = "create table aa ( id identity primary key, "
+ "version timestamp default current timestamp, "
+ "txt varchar(8) ) ";
st.execute(sql);
sql = "create table bb ( id identity primary key, "
+ "version timestamp default current timestamp, "
+ "txt varchar(8), "
+ "aa_id int, foreign key (aa_id) references aa(id) ) ";
st.execute(sql);
sql = "create table cc ( id identity primary key, "
+ "version timestamp default current timestamp, "
+ "txt varchar(8), "
+ "bb_id int, foreign key (bb_id) references bb(id) ) ";
st.execute(sql);
} catch (Throwable t) {
t.printStackTrace();
} finally {
try {
if (st != null) st.close();
} catch (Throwable t) {
t.printStackTrace();
}
try {
if (conn != null) conn.close();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}

The corresponding Spring bean definition is:

Table 10: Service Spring Bean Definition
<bean id="service" class="example.service.support.ServiceImpl">
<constructor-arg index="0"><ref local="dataSource"/></constructor-arg>
<constructor-arg index="1"><ref local="hibernateDao"/></constructor-arg>
<constructor-arg index="2"><ref local="ibatisDao"/></constructor-arg>
<constructor-arg index="3"><ref local="jdbcDao"/></constructor-arg>
</bean>

Client

The client application is a simple class with a main() method that initializes an ApplicationContext, gets the service bean, executes the service's runExample() method and closes the ApplicationContext.

Table 11: Client Main Class
package example;

import example.service.Service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext(
"/example/spring.xml");
Service service = (Service)ac.getBean("service");
service.runExample();
((ConfigurableApplicationContext)ac).close();
}
}

Declarative Transaction Demarcation

The Spring Framework transaction management configuration follows:

Table 12: Spring Transaction Management Configuration
<bean id="hibernateTxManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
<property name="nestedTransactionAllowed" value="true"/>
</bean>

<aop:config>
<aop:pointcut id="jdbcDaoMethods"
expression="execution(* example.dao.support.JdbcDao.*(..))"/>
<aop:advisor advice-ref="jdbcTxAdvice" pointcut-ref="jdbcDaoMethods"/>
</aop:config>

<tx:advice id="jdbcTxAdvice" transaction-manager="hibernateTxManager">
<tx:attributes>
<tx:method name="create"
propagation="MANDATORY" isolation="REPEATABLE_READ"/>
<tx:method name="deleteById"
propagation="MANDATORY" isolation="REPEATABLE_READ"/>
<tx:method name="*" propagation="NESTED"
isolation="READ_UNCOMMITTED" read-only="true"/>
</tx:attributes>
</tx:advice>

<aop:config>
<aop:pointcut id="hibernateDaoMethods"
expression="execution(* example.dao.support.HibernateDao.*(..))"/>
<aop:advisor advice-ref="hibernateTxAdvice"
pointcut-ref="hibernateDaoMethods"/>
</aop:config>

<tx:advice id="hibernateTxAdvice" transaction-manager="hibernateTxManager">
<tx:attributes>
<tx:method name="create"
propagation="REQUIRES_NEW" isolation="READ_COMMITTED"/>
<tx:method name="deleteById"
propagation="REQUIRES_NEW" isolation="READ_COMMITTED"/>
<tx:method name="*" propagation="NESTED"
isolation="READ_COMMITTED" read-only="true"/>
</tx:attributes>
</tx:advice>

<aop:config>
<aop:pointcut id="ibatisDaoMethods"
expression="execution(* example.dao.support.IbatisDao.*(..))"/>
<aop:advisor advice-ref="ibatisTxAdvice"
pointcut-ref="ibatisDaoMethods"/>
</aop:config>

<tx:advice id="ibatisTxAdvice" transaction-manager="hibernateTxManager">
<tx:attributes>
<tx:method name="create"
propagation="REQUIRED" isolation="SERIALIZABLE"/>
<tx:method name="deleteById"
propagation="REQUIRED" isolation="SERIALIZABLE"/>
<tx:method name="*" propagation="NESTED"
isolation="REPEATABLE_READ" read-only="true"/>
</tx:attributes>
</tx:advice>

<aop:config>
<aop:pointcut id="serviceMethods"
expression="execution(* example.service.support.*.*(..))"/>
<aop:advisor advice-ref="serviceTxAdvice"
pointcut-ref="serviceMethods"/>
</aop:config>

<tx:advice id="serviceTxAdvice" transaction-manager="hibernateTxManager">
<tx:attributes>
<tx:method name="*"
propagation="REQUIRES_NEW" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>

The HibernateTransactionManager is being used to ensure the Spring Framework will manage both the DataSource and Hibernate Session resources within transactions.  There are other techniques for enabling the Spring Framework to manage a DataSource and Hibernate Session within transactions.  The important thing here is that the HibernateTransactionManager may be used to manage the DataSource resource and other Spring Framework persistence solutions will correctly participate in transactions, as long as they are all configured to use the same DataSource and PlatformTransactionManager.

Note that the propagation and isolation transaction attributes for the JDBC, Hibernate and iBATIS DAO methods have very different settings for each.  Granted this is not a realistic scenario, it proves the point that Spring Framework transaction management is very flexible and robust.  However, care must be exercised when using Hibernate.  Hibernate will queue certain SQL statements and won't execute them until the transaction is complete.  For example, if the propagation for the HibernateDao.deleteById() method is changed from REQUIRES_NEW to REQUIRED, a database referential integrity constraint violation will occur due to the DELETE SQL for the BB record being queued for execution at the end of the pre-existing transaction (around the Service.runExample() method) and the DELETE SQL for the AA record occurring before the delayed BB record DELETE.  The constraint violation could be avoided by flushing the Hibernate Session after deleting the record, but that then introduces transaction management code into you DAO implementation, and having the Spring Framework manage transactions for you would be ideal.

Also note that nested transactions have been enabled in the HibernateTransactionManager, allowing the use of JDBC 3.0 Savepoints within transactions (as long as you are using a driver that supports Savepoints).  With nested transactions, it's important to remember that Hibernate does not understand nested transactions, it's the JDBC Connection that does.

Conclusion

The Spring Framework provides an application with excellent DAO support and transaction management through the use of an DaoSupport subclass, PlatformTransactionManager implementation and one of the many templates and corresponding application callbacks.  DAO implementations that use different persistence strategies for the same data source will properly participate in existing transactions.  Some examples of where this would be useful include:

  • You are using an ORM solution but require the use of JDBC for certain aspects of your data model;
  • you are integrating multiple projects that use different ORM solutions for their subject area in the data model;
  • you have an in-house transaction management and ORM implementation that you would like to integrate with other third-party ORM solutions;
  • you want to migrate from one ORM solution to another; and
  • you have multiple databases with multiple DAO implementations using different ORM solutions and want them to participate in a Java Transaction API (JTA) global transaction.
Permalink  Comments [0]