Joe Developer

Friday Jul 21, 2006

XDoclet, Struts, and Spring integration code generation

Did I mention I'm not that fond of writing config files?

So I'm working on this project - standard Struts app on the front end, with a bunch of Spring magic behind it. I like Spring. Struts - well it's Struts... I do like the ability to integrate the 2 though (is there anything Spring doesn't integrate with?) using the DelegatingRequestProxy method.

This method goes like so - replace the classes in struts-config.xml with this DelegatingActionProxy. When Struts accesses one, the url is used as the lookup in Spring - and the class returned (your actual action) has been all Spring injected/AOPed etc...

This is great, save 2 things.

       
  • struts-config.xml doesn't explicitly tell you what class a URL is mapping to - not good for a dev that's new to a codebase, particularly if there isn't a strong naming convention etc in place
  •    
  • Its yet another Spring file to manage - and a rather boring one at that. I'm not wanting to inject lots of special things - just beans.
I didn't even write the first round of said Spring file - I jumped straight to XDoclet. Anything that mundane must be generated!

Here's the struts-spring-config.xml template:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
"http://www.springframework.org/dtd/spring-beans.dtd">

   <!--
    | XDoclet generated spring-config file to configure the dependencies
    | into delegated proxy actions.  This is used with the modified 
    | struts-config template that will optionally write the delegation class
    | rather than the actual class to the struts-config file.
    +-->
   
   <!--
     | Author Brian Greene
     | brian@sourceallies.com
     +-->
<beans>

 <!-- ========== Manual Action Configurations =================================== -->
  <XDtMerge:merge file="struts-beans-config.xml">
  <!--
    Define your action bean dependencies in a file called struts-beans-config.xml and place
    it in your merge directory.
  -->
  </XDtMerge:merge>  

  <!-- ========== Action Mapping Dependency Declarations =================================== -->
  <XDtClass:forAllClasses type="org.apache.struts.action.Action">
   <XDtClass:forAllClassTags tagName="struts:action">
    <XDtClass:ifDoesntHaveClassTag tagName="struts:action" paramName="no-spring">
      <bean name="<XDtClass:classTagValue tagName="struts:action" paramName="path"/>"
            class="<XDtClass:fullClassName/>">
      	<XDtClass:forAllClassTags tagName="spring:depend">
         <property name="<XDtClass:classTagValue tagName="spring:depend" paramName="name"/>">
            <ref bean="<XDtClass:classTagValue tagName="spring:depend" paramName="bean"/>" />
         </property>
        </XDtClass:forAllClassTags>           
      </bean>
   </XDtClass:ifDoesntHaveClassTag>
 </XDtClass:forAllClassTags>
</XDtClass:forAllClasses>   
</beans>  

Simple little template. It generates something that looks like:
   .
   .
   .
    <bean name="/login" class="com.sourceallies.myproject.web.action.LoginAction">
         <property name="userService">
           <ref bean="userService"/>
         </property>
    </bean>
   .
   .
   .
So included in my list of Spring config files, I include this generated file. I could have used auto wiring - but wanted to be more explicit at this stage. We're not done yet though - the struts config file is still going to reference LoginAction.

Since I'm generating struts-config.xml, I'm off to modify the template a bit to support this new feature. This is one of the things I like about XDoclet - for most simple extensions you don't really have to do any work - make up a tag and go.
Here's how my new made up tag (and attribute) looks on LoginAction:
/**
 * @author BGreene
 * @struts.action
 * 	path="/login"
 *              useSpringProxy="true"
 *  
 * @spring.depend
 *  name="userService"
 *  bean="userService"
There are 2 new things here - the useSpringProxy="true" attribute of the @struts.action tag, and the @spring.depend tag.
useSpringProxy="true" is used to tell the new struts-config.xml template that I want to use the DelegatingActionProxy.
@spring.depend is what is used to generate the spring-struts-config.xml file above.
The generated struts-config file looks like (excerpt from <action-mappings>):
   .
   .   
 <!--using the spring proxy.  Actual class: com.sourceallies.myproject.web.action.LoginAction -->
<action
path="/login"
type="org.springframework.web.struts.DelegatingActionProxy"
unknown="false"
validate="true"
>
<forward
name="login"
path=".login"
redirect="false"
/>
   .
   .
Note - the modified template includes a comment to tell you the Actual name of the class the URL is mapping to - lest you'd have to dig even more to find it. Again, a new dev on the project may not know WHY the DelegatingActionProxy is the class - but if you know anything about Struts you'll read the comment (hopefully) and at least know what class is servicing a URL.

Finally, the new struts_config_xml.xdt template. Please note if you're going to use this, you'll need to actually replace the template file in the xdoclet-apache jar, as it relates to other templates etc...

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd">

<struts-config>

<!-- ========== Data Sources Definitions =================================== -->
<XDtMerge:merge file="struts-data-sources.xml">
<!--
Define your Struts data sources in a file called struts-data-sources.xml and place
it in your merge directory.
-->
</XDtMerge:merge>


<!-- ========== Form Bean Definitions =================================== -->
<form-beans>
<XDtMerge:merge file="xdoclet/modules/apache/struts/resources/struts_dynaform.xdt">
</XDtMerge:merge>
<XDtClass:forAllClasses type="org.apache.struts.action.ActionForm">
<XDtClass:forAllClassTags tagName="struts:form" superclasses="false">
<form-bean
name="<XDtClass:classTagValue tagName="struts:form" paramName="name"/>"
type="<XDtClass:fullClassName/>"
/>
</XDtClass:forAllClassTags>
</XDtClass:forAllClasses>

<XDtMerge:merge file="struts-forms.xml">
<!--
If you have non XDoclet forms, define them in a file called struts-forms.xml and
place it in your merge directory.
-->
</XDtMerge:merge>
</form-beans>

<!-- ========== Global Exceptions Definitions =================================== -->
<XDtMerge:merge file="global-exceptions.xml">
<!--
Define your exceptions in a file called global-exceptions.xml and place
it in your merge directory.
-->
</XDtMerge:merge>

<!-- ========== Global Forward Definitions =================================== -->
<XDtMerge:merge file="global-forwards.xml">
<!--
Define your forwards in a file called global-forwards.xml and place
it in your merge directory.
-->
</XDtMerge:merge>

<!-- ========== Action Mapping Definitions =================================== -->
<XDtConfig:ifConfigParamNotEquals paramName="Controller" value="">
<action-mappings type="<XDtConfig:configParameterValue paramName="Controller"/>">
</XDtConfig:ifConfigParamNotEquals>
<XDtConfig:ifConfigParamEquals paramName="Controller" value="">
<action-mappings>
</XDtConfig:ifConfigParamEquals>
<XDtClass:forAllClasses type="org.apache.struts.action.Action">
<XDtClass:forAllClassTags tagName="struts:action">
<XDtClass:ifHasClassTag tagName="struts:action" paramName="useSpringProxy">
<!-- using the spring proxy.  Actual class: <XDtClass:fullClassName/> --> 
</XDtClass:ifHasClassTag>
<action
path="<XDtClass:classTagValue tagName="struts:action" paramName="path"/>"
<XDtClass:ifHasClassTag tagName="struts:action" paramName="useSpringProxy">
type="org.springframework.web.struts.DelegatingActionProxy"
</XDtClass:ifHasClassTag>
<XDtClass:ifDoesntHaveClassTag tagName="struts:action" paramName="useSpringProxy">
	    <XDtClass:ifHasClassTag tagName="struts:action" paramName="type">
	      type="<XDtClass:classTagValue tagName="struts:action" paramName="type"/>"
	    </XDtClass:ifHasClassTag>
	    <XDtClass:ifDoesntHaveClassTag tagName="struts:action" paramName="type">
	      type="<XDtClass:fullClassName/>"
	    </XDtClass:ifDoesntHaveClassTag>
</XDtClass:ifDoesntHaveClassTag>
<XDtClass:ifHasClassTag tagName="struts:action" paramName="name">
name="<XDtClass:classTagValue tagName="struts:action" paramName="name"/>"
<XDtClass:ifHasClassTag tagName="struts:action" paramName="attribute">
attribute="<XDtClass:classTagValue tagName="struts:action" paramName="attribute"/>"
</XDtClass:ifHasClassTag>
<XDtClass:ifHasClassTag tagName="struts:action" paramName="prefix">
prefix="<XDtClass:classTagValue tagName="struts:action" paramName="prefix"/>"
</XDtClass:ifHasClassTag>
<XDtClass:ifHasClassTag tagName="struts:action" paramName="suffix">
suffix="<XDtClass:classTagValue tagName="struts:action" paramName="suffix"/>"
</XDtClass:ifHasClassTag>
scope="<XDtClass:classTagValue tagName="struts:action" paramName="scope" default="request" values="request,session"/>"
<XDtClass:ifHasClassTag tagName="struts:action" paramName="input">
input="<XDtClass:classTagValue tagName="struts:action" paramName="input"/>"
</XDtClass:ifHasClassTag>
</XDtClass:ifHasClassTag>
<XDtConfig:ifConfigParamGreaterOrEquals paramName="Version" value="1.1">
<XDtClass:ifHasClassTag tagName="struts.action" paramName="roles">
roles="<XDtClass:classTagValue tagName="struts.action" paramName="roles"/>"
</XDtClass:ifHasClassTag>
</XDtConfig:ifConfigParamGreaterOrEquals>
<XDtClass:ifHasClassTag tagName="struts:action" paramName="parameter">
parameter="<XDtClass:classTagValue tagName="struts:action" paramName="parameter"/>"
</XDtClass:ifHasClassTag>
unknown="<XDtClass:classTagValue tagName="struts:action" paramName="unknown" values="true,false" default="false"/>"
validate="<XDtClass:classTagValue tagName="struts:action" paramName="validate" values="true,false" default="true"/>"
>
<XDtClass:forAllClassTags tagName="struts:action-set-property">
<set-property
<XDtClass:ifHasClassTag tagName="struts:action-set-property" paramName="id">
id="<XDtClass:classTagValue tagName="struts:action-set-property" paramName="id"/>"
</XDtClass:ifHasClassTag>
property="<XDtClass:classTagValue tagName="struts:action-set-property" paramName="property"/>"
value="<XDtClass:classTagValue tagName="struts:action-set-property" paramName="value"/>"
/>
</XDtClass:forAllClassTags>
<XDtClass:forAllClassTags tagName="struts:action-exception">
<exception
key="<XDtClass:classTagValue tagName="struts:action-exception" paramName="key"/>"
type="<XDtClass:classTagValue tagName="struts:action-exception" paramName="type"/>"
<XDtClass:ifHasClassTag tagName="struts:action-exception" paramName="className">
className="<XDtClass:classTagValue tagName="struts:action-exception" paramName="className"/>"
</XDtClass:ifHasClassTag>
<XDtClass:ifHasClassTag tagName="struts:action-exception" paramName="handler">
handler="<XDtClass:classTagValue tagName="struts:action-exception" paramName="handler"/>"
</XDtClass:ifHasClassTag>
<XDtClass:ifHasClassTag tagName="struts:action-exception" paramName="path">
path="<XDtClass:classTagValue tagName="struts:action-exception" paramName="path"/>"
</XDtClass:ifHasClassTag>
<XDtClass:ifHasClassTag tagName="struts:action-exception" paramName="scope">
scope="<XDtClass:classTagValue tagName="struts:action-exception" paramName="scope"/>"
</XDtClass:ifHasClassTag>
/>
</XDtClass:forAllClassTags>
<XDtClass:forAllClassTags tagName="struts:action-forward">
<forward
<XDtClass:ifHasClassTag tagName="struts:action-forward" paramName="className">
className="<XDtClass:classTagValue tagName="struts:action" paramName="className"/>"
</XDtClass:ifHasClassTag>
<XDtClass:ifHasClassTag tagName="struts:action-forward" paramName="contextRelative">
contextRelative="<XDtClass:classTagValue tagName="struts:action-forward" paramName="contextRelative"/>"
</XDtClass:ifHasClassTag>
name="<XDtClass:classTagValue tagName="struts:action-forward" paramName="name"/>"
path="<XDtClass:classTagValue tagName="struts:action-forward" paramName="path"/>"
redirect="<XDtClass:classTagValue tagName="struts:action-forward" paramName="redirect" default="false" values="true,false"/>"
/>
</XDtClass:forAllClassTags>
</action>
</XDtClass:forAllClassTags>
</XDtClass:forAllClasses>

<XDtMerge:merge file="struts-actions.xml">
<!-- If you have non XDoclet actions, define them in a file called struts-actions.xml and place it in your merge directory. -->
</XDtMerge:merge>
<XDtMerge:merge file="actions.xml">
</XDtMerge:merge>
</action-mappings>

<XDtMerge:merge file="struts-controller.xml">
<!-- Define your Struts controller in a file called struts-controller.xml and place it in your merge directory. -->
</XDtMerge:merge>

<XDtMerge:merge file="struts-message-resources.xml">
<!-- Define your Struts message-resources in a file called struts-message-resources.xml and place it in your merge directory. -->
</XDtMerge:merge>

<XDtMerge:merge file="struts-plugins.xml">
<!-- Define your Struts plugins in a file called struts-plugins.xml and place it in your merge directory. -->
</XDtMerge:merge>

</struts-config><
Finding the modifications to the template is left as an excercise for the reader.

This is being used with success against XDoclet 1.2.2, and using the 1.2.2 XDoclet-apache lib with the rest of xdoclet 1.2.3.

Thursday Jul 13, 2006

jBPM/JPDL GUI - a middle of the road approach

I've been using jBPM for a while, and really like it as a BPM tool. I've actually found a few uses outside the original purpose, but that's another post.

Like most tools though, I'm forced to code in XML. I'm not saying I don't want to code in XML, but it can get a bit tedious. Mix that with the love that the analysts and process owners have for pictures, and you'll be wanting a GUI for jBPM.

There is a GUI for jBPM. I've played with it in various incarnations and releases for a while, but always found it lacking. I don't particularly want a flowchart of my process, rather I wanted a structure that showed the semantics of my process that did some other work for me.

With a large process to author and a deadline for completion approaching, I did what any joe developer would do - I wrote a tool that would allow me to author the process in a tool and then export the XML.

This is a small screenshot of the editor:



Why write a different tool? A couple of reasons.

  • Dave Thomas says the issue with a GUI is WYSIAYG - What You See Is All You Get. If there is a feature in the system that you know how to use but the tool doesn't expose it, you can't use it unless...
  • I needed a tool wouldn't lose data in my process definition - even if it didn't support editing it in the GUI, it should keep the XML that I started with.
  • The last time I used the JPDL editor from JBoss, it was rather tied to their way of doing things. I'm not doing things that way, and a tool should be as unobtrusive as possible.
  • I hadn't written a fat client in a while, and it looked like a fun problem :-)
So how did I fair with my deadline? I used 2/3 of the time writing the first incarnation of the tool. I used 1/6 of the time authoring the process - and still came in with time to spare. The tool has since saved a lot of time in authoring other processes, so a win overall as far as I'm concerned.

Is is a flow chart? No. can I use it to create one? Certainly. The tool doesn't hide the process language from the developer - and it shouldn't. The holy grail of management drawing pictures that turn into software is a myth, and I'm not aiming to chase it.

Wednesday Jul 12, 2006

Code Generation

I like code generation. A lot. build time, run time, all the time.

Latest Code generation tool: apt-jelly.

The short story - a wrapper around the annotation processor in JDK5 that exposes the annotations to a series of templates for code generation.

Originally it just used Jelly scripting (yeck), and I stayed away, but now it supports FreeMarker - much more friendly.

Now I'm a big fan of XDoclet - I've had it generating 1000's of LOC and config for EJB 2.1 projects (lifesaver), and currently use it to generate all sorts of artifacts (hbm, struts-config, a struts-config-spring integration bean file...), but it is a bit restrictive, and writing templates that have more than mediocre transformations (tag handlers) is possible, but a bit more involvement than I'm looking for.

Contrast that with FreeMarker templates... Classes and types are first class citizens, macros are a really possibility, and writing an annotation to process is a snap.

Death knell for XDoclet with me - the 1.x branch is no longer under active dev... like many OS projects, the lead has moved on to a complete re-write to address architectural issues in the 1.x line, but the 1.x line is the one that has all the cool plugins.... Heck, I even got templates from Oracle's tech support at one point. I'm not giving up on XDoclet, and will probably use it for some time to come (with a patched version of XJavadoc to support JDK5 source files), but new heavy lifting will be done with apt-jelly.

apt-jelly is short of documentation and examples (I'll post an example here at some point), but it is open source, and relatively easy to pick up. Now I just need a FreeMarker debugger...

apt-jellyis the sourceforge site. It is a seriously fledgling project, but having used it, I think there's some potential there until/unless Sun or some such gifts us with an easier way to use apt for code generation.

DBUnit Ant Task and Foreign Key Constraints - Fix



I have a fix for the DBUnit Foreign Key constraint on ant import issue. The next few paragraphs are history for those that haven't used this wonderful tool. If you're just looking for the fix, skip the next few paragraphs. So I use DBUnit. Not really in the full way it was intended for - database unit testing, but as a nice, simple, Ant scriptable tool to move data in and out of the DB....

It works really well - point to a DB, specify the standard JDBC stuff, and wa-la - you have a nice, DB agnostic XML export of data. Lots of folks use this bit of the tool. I need to have several sets of data that can be reloaded into the DB on command - the standard stuff consisting of a few user accounts, roles, the stuff of drop down lists, etc. These exports are easy to version control, and great for resetting a test DB, moving new seed data to a staging/testing instance, and getting a new development environment going. Not to mention that the import will run against different DBs, allowing easy importing of the seed data into a new platform.

If that sounds like a commercial for the technique - it is :) I like it, and having started to use it I never want to explain how to do the backup and restore a SQLServer DB or the like.

Then that day comes where I am a bit more picky about the tables I do and don't want exported, and I get the standard Foreign Key Constraint issue. Having manually specified the tables, I am responsible for exporting them in an order that will respect FK constraints on import. This messing about gets more and more annoying, but we live with it.

Then we start wanting to export and import ALL data in the DB... and even letting DBUnit export all the tables, it can't sort out the right order to ensure that FK constraints are respected... I know they're valid in the DB. What I'd really like is to turn off the checking for the import. Most DBs allow that right? Yup.

MySQL though, only allows it on a session specific basis - the life of one JDBC connection. I tried a number of ways to activate it globally to no avail. DBUnit though, gives no option to get at the connection its using.

TA DA - it's open source, so I can figure this out... 3 iterations later, I have a solution I'm happy with. Didn't have to really modify the DBUnit code, and I'm able to get the job done by adding a new kind of "step", intercepting the connection that DBUnit will use to perform the load BEFORE it does its magic.

The code is in 2 little classes.

This is the extension to the DBUnitTask that we're going to use instead of the standard DBUnitTask, it allows additions to the steps that DBUnit is going to execute.

/*
 * DBUnitTaskExt.java
 * Created on: Jul 9, 2006
 * 
 */
package org.dbunit.ant;

import org.dbunit.ant.DbUnitTask;

/**
 * This extension to the DBUnitTask allows the addition of <code>Script</code>s
 * to the DBUnit task.
 * This is useful for executing pre and post scripts when performing data-import and export.
 * @author Brian Greene
 *
 */
public class DBUnitTaskExt extends DbUnitTask 
{
	
	public void addScript(Script s)
	{
		getSteps().add(s);
	}
}
This is the actual class that does the script parsing and executing. Happily, I was able to "borrow" and only slightly modify a bit of code from Ant 1.6.5's SQLExec class.
/*
 * Script.java
 * Created on: Jul 9, 2006
 */
package org.dbunit.ant;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.StringTokenizer;

import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.SQLExec;
import org.apache.tools.ant.taskdefs.SQLExec.DelimiterType;
import org.dbunit.DatabaseUnitException;
import org.dbunit.ant.DbUnitTaskStep;
import org.dbunit.database.IDatabaseConnection;

/**
 * This class can be used to insert scripts into the series of Steps that are
 * executed by the DBUnit task.
 * 
 * This class borrows some functionality from Ant's SQLExec task related to the
 * actual parsing of the SQL.
 * 
 * @author Brian
 * 
 */
public class Script implements DbUnitTaskStep {

	private File src;

	private String delimiter = ";";

	private Connection conn;

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.dbunit.ant.DbUnitTaskStep#execute(org.dbunit.database.IDatabaseConnection)
	 */
	public void execute(IDatabaseConnection connection)
			throws DatabaseUnitException {
		try {
			conn = connection.getConnection();
			if(src == null || !src.exists())
				throw new DatabaseUnitException("The src attribute must be supplied.");
			executeScript(src);
		} catch (SQLException e) {
			throw new DatabaseUnitException("Error in getConnection()... :"
					+ e.getMessage(), e);
		}
	}

	/**
	 * This method was pulled and slightly modified from the SQLExec task from
	 * Ant 1.6.5
	 * 
	 * @param f
	 *            the File containing the script.
	 */
	private void executeScript(File f) {
		try {

			StringBuffer sql = new StringBuffer();
			String line = "";

			BufferedReader in = new BufferedReader(new FileReader(f));

			while ((line = in.readLine()) != null) {
				line = line.trim();
				if (line.startsWith("//")) {
					continue;
				}
				if (line.startsWith("--")) {
					continue;
				}
				StringTokenizer st = new StringTokenizer(line);
				if (st.hasMoreTokens()) {
					String token = st.nextToken();
					if ("REM".equalsIgnoreCase(token)) {
						continue;
					}
				}

				sql.append(" " + line);

				// SQL defines "--" as a comment to EOL
				// and in Oracle it may contain a hint
				// so we cannot just remove it, instead we must end it

				if (line.indexOf("--") >= 0) {
					sql.append("\n");
				}
				String delimiterType = SQLExec.DelimiterType.NORMAL;
				String delimiter = ";";
				if ((delimiterType.equals(DelimiterType.NORMAL) && sql
						.toString().endsWith(delimiter))
						|| (delimiterType.equals(DelimiterType.ROW) && line
								.equals(delimiter))) {
					execSQL(sql.substring(0, sql.length() - delimiter.length()));
					sql.replace(0, sql.length(), "");
				}
			}
			// Catch any statements not followed by ;
			if (!sql.equals("")) {
				execSQL(sql.toString());
			}
		} catch (Exception e) {
			// TODO: handle exception
		}
	}

	/**
	 * This method was pulled and slightly modified from the SQLExec task from
	 * Ant 1.6.5 Exec the sql statement.
	 */
	protected void execSQL(String sql) throws SQLException {
		// Check and ignore empty statements
		if ("".equals(sql.trim())) {
			return;
		}
		ResultSet resultSet = null;
		try {
			//log("SQL: " + sql, Project.MSG_VERBOSE);
			boolean ret;
			int updateCount = 0, updateCountTotal = 0;
			Statement statement = conn.createStatement();
			ret = statement.execute(sql);
			updateCount = statement.getUpdateCount();
			resultSet = statement.getResultSet();
			do {
				if (!ret) {
					if (updateCount != -1) {
						updateCountTotal += updateCount;
					}
				} else {
					// we always print results.
					printResults(resultSet);
				}
				ret = statement.getMoreResults();
				if (ret) {
					updateCount = statement.getUpdateCount();
					resultSet = statement.getResultSet();
				}
			} while (ret);

//			log(updateCountTotal + " rows affected", Project.MSG_VERBOSE);

			// we're always printing results.
			StringBuffer line = new StringBuffer();
			line.append(updateCountTotal + " rows affected");
//			log(line.toString(), Project.MSG_INFO);

			SQLWarning warning = conn.getWarnings();
			while (warning != null) {
//				log(warning + " sql warning", Project.MSG_VERBOSE);
				warning = warning.getNextWarning();
			}
			conn.clearWarnings();
		} catch (SQLException e) {
//			log("Failed to execute: " + sql, Project.MSG_ERR);
			// we've no concept of continue on error - we always fail.
			throw e;
		} finally {
			if (resultSet != null) {
				resultSet.close();
			}
		}
	}

	/**
	 * This method was pulled and slightly modified from the SQLExec task from
	 * Ant 1.6.5 print any results in the result set.
	 * 
	 * @param rs
	 *            the resultset to print information about
	 * @throws SQLException
	 *             on SQL problems.
	 * @since Ant 1.6.3
	 */
	protected void printResults(ResultSet rs) throws SQLException {
		if (rs != null) {
//			log("Processing new result set.", Project.MSG_INFO);
			ResultSetMetaData md = rs.getMetaData();
			int columnCount = md.getColumnCount();
			StringBuffer line = new StringBuffer();

			for (int col = 1; col < columnCount; col++) {
				line.append(md.getColumnName(col));
				line.append(",");
			}
			line.append(md.getColumnName(columnCount));
//			log(line.toString(), Project.MSG_INFO);
			line = new StringBuffer();
			while (rs.next()) {
				boolean first = true;
				for (int col = 1; col <= columnCount; col++) {
					String columnValue = rs.getString(col);
					if (columnValue != null) {
						columnValue = columnValue.trim();
					}

					if (first) {
						first = false;
					} else {
						line.append(",");
					}
					line.append(columnValue);
				}
//				log(line.toString(), Project.MSG_INFO);
				line = new StringBuffer();
			}
		}
//		log("", Project.MSG_INFO);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.dbunit.ant.DbUnitTaskStep#getLogMessage()
	 */
	public String getLogMessage() {
		return "Script step";
	}

	public File getSrc() {
		return src;
	}

	public void setSrc(File src) {
		this.src = src;
	}

}

Usage in the build file:
            <target name="data-import"  depends="init, recreate-db">
		<taskdef name="dbunit" classname="org.dbunit.ant.DBUnitTaskExt" classpathref="classpath.main" />
		<dbunit driver="${jdbc.driver}" url="${jdbc.url}" userid="${jdbc.userid}" password="${jdbc.password}">
			<classpath refid="classpath.main" />
			<script src="etc/sql/${data.movement.script.prefix}.preScript.sql"/>
			<operation type="INSERT" src="${dumpFile}.xml" format="xml" />
		</dbunit>
	</target>
This usage is specialized to only perform one insert based on a parameter(used in the build to load several different datasets based on the dumpFile parameter), as the recreate-db dependency will handle the initial setup of the DB (gotta love the Hibernate tools to generate those scripts :) ). I suppose you could use it to just string together a bunch of scripts though. So here's the etc/sql/${data.movement.script.prefix}.preScript.sql for MySQL to disable FK checking:
            SET @@FOREIGN_KEY_CHECKS=0;
Now, I have read that there are ways to append this to the MySQLConnectorJ connection URL, but try as I might it didn't always work. And besides, that just solves the problem for MySQL, not the other DBs I'm going to use. Those of you familiar with DBUnit will recognize the "operation" elements - you can list as many as you want. I just added the option to add as many scripts as I want into the mix, using the same JDBC connection. This also allows an easy way to do pre-export, post-export, or pre/post import work in additon to the work related to the FK problem. Is there a better way to solve the problem? I'm curious to know. I do know that this is a more concise way to chain a bunch of scripts together rather than using a whole mess of SQL tasks (a by product of the solution). If this solves the issue for you, great. If you think there's a more elegant solution that will still allow me to do the job between multiple DBs, please enlighten me - I'm always up for an easier solution. (this is in the lazy category for a reason).

Calendar

Feeds

Search

Links

Navigation

Referrers