Author Archive

unhandy windows key shortcuts with eclipse

Finding the advanced keyboard settingsSome developers around me were complaining that every once in a while the keyboard settings would change while programming eclipse. I was not aware, but it turned out I changed my settings years ago to prevent this from happening. Here is the cause:

The default company settings of Windows XP are to have the keyboard layout key shortcuts enabled by default.

These are located in ‘Control Panel’ > ‘Regional and Language Options’ > ‘Languages’ > ‘Details’ > ‘Key Settings’ > ‘Change Key Sequence’

‘CTRL + shift’ or ‘left Alt + shift’ switches between keyboard layouts. For instance between US and Dutch/NL layout. For the Dutch layout, for instance, you need to type quotes (“) followed by a space to display them in the editor. Very unhandy for programmers.

This combination is also used in Eclipse: for instance “shift + ctrl + T” to popup the Type selector.

Disable the microsoft windows key binding by unchecking the designated checkboxes!

How thread safe are Struts2 Interceptors?

Struts2 In a project where we are using struts2 we wanted to create two custom interceptors. One to start a Transaction at the start of the interceptor stack and one to close that Transaction at the end of the interceptor stack. This would cause all (read only) database calls for the prepare method and all the conversion and validation to run in one transaction.

Instead of heaving two, very similar, interceptors to start and stop transactions, it’s more convenient to have only one with a parameter to switch start and stop mode. But hold on… the Struts2 docs state clearly that interceptors must have no state. Is setting a parameter on an interceptor, that is stored in a member variable not just that: state?

And if there is only one interceptor, that will surely fall over when used simultaneously in two or more request, each with different parameters to the interceptor. (Remember that you can override interceptor parameters per stack, or even per action, think of the “excludeMethods” parameter of the validate interceptor). This cannot be a design flaw, or what?

Unable to find an answer quickly in my Struts2 book or with Google, I put Struts2 to the test. For the first time I tried running Eclipse europa (on the MacBook Pro) with the Web Tools Platform and I was pleasantly surprised at its first time ease of use. Just go to the Eclipse update feature and select WTP. And I had installed Tomcat 6 already, so pointing to the installation directory for the server configuration was enough to get started.

Next, I downloaded the struts2-blank-2.0.11.1.war and imported this into the server configuration. Setting a breakpoint in the example code worked just fine. Just browsed to

http://localhost:8080/struts2-blank-2.0.11/index.html

and bingo: Eclipse stopped at the breakpoint.

Now for the test. First I changed the example.xml to include a new Interceptor:


		
			
			
				
green
				
   				
  				
red
  				
			
		

        
            /example/HelloWorld.jsp
            
orange
            
        

       
            /example/HelloWorld.jsp
            
blue
            
        

Next, implementation of the interceptor:

package example;

import java.util.logging.Logger;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

public class ThreadSafeInterceptor implements Interceptor {

	private static final long serialVersionUID = 1964558869343105305L;

	private Logger log = Logger.getLogger("threadsafe");
	private String toggle;  

	public void destroy() {
		log.info("Object: " + this);
	}

	public void init() {
		log.info("Object: " + this);
	}

	public String intercept(ActionInvocation ai) throws Exception {
		log.entering(this.toString() , "intercept()");
		log.info("Object: " + this + " toggle before: " + toggle);
		String result = ai.invoke();
		log.info("Object: " + this + " toggle after: " + toggle);
		return result;
	}

	public String getToggle() {
		return toggle;
	}

	public void setToggle(String toggle) {
		log.info("Object: " + this + " setToggle("+toggle+")");
		this.toggle = toggle;
	}

}

If there was only one interceptor, and the parameters would be set to change color, it must show up that the interceptor gets confused while running!

Here is what the console showed:

WARNING: Settings: Could not parse struts.locale setting, substituting default VM locale
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor setToggle
INFO: Object: example.ThreadSafeInterceptor@89c698 setToggle(green)
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor init
INFO: Object: example.ThreadSafeInterceptor@89c698
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor setToggle
INFO: Object: example.ThreadSafeInterceptor@4d3241 setToggle(red)
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor init
INFO: Object: example.ThreadSafeInterceptor@4d3241
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor setToggle
INFO: Object: example.ThreadSafeInterceptor@e5a19e setToggle(orange)
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor init
INFO: Object: example.ThreadSafeInterceptor@e5a19e
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor setToggle
INFO: Object: example.ThreadSafeInterceptor@d5cdab setToggle(blue)
Apr 22, 2008 10:04:40 PM example.ThreadSafeInterceptor init
INFO: Object: example.ThreadSafeInterceptor@d5cdab
Apr 22, 2008 10:04:40 PM com.opensymphony.xwork2.util.ObjectTypeDeterminerFactory 

Ahh… at startup actually three instances are created! One for each interceptor-ref! So each one holds just one set of parameters, one state. Also note that first the toggle parameter is set and then the init is called. So you could potentially use the parameter value in the init method. Hmm, does it make sense to define the private toggle field as final as well, so it cannot change?

Next in the log, when pointing the browser to

http://localhost:8080/struts2-blank-2.0.11/example/HelloWorld.action

:

Apr 22, 2008 10:07:36 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@e5a19e toggle before: orange
Apr 22, 2008 10:07:36 PM com.opensymphony.xwork2.validator.ActionValidatorManagerFactory 
INFO: Detected AnnotationActionValidatorManager, initializing it...
Apr 22, 2008 10:07:36 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@4d3241 toggle before: red
Apr 22, 2008 10:07:37 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@4d3241 toggle after: red
Apr 22, 2008 10:07:37 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@e5a19e toggle after: orange

Orange, red, red, orange… looks good, the green interceptor is hidden from this stack! Note that only the first occurence of the two interceptors in the stack is overridden. Mind this when using the “param-prepare-param” stack with possible parameter overrides for the param interceptor!

Calling this url:

http://localhost:8080/struts2-blank-2.0.11/example/HelloWorldBlue.action

shows:

Apr 22, 2008 10:08:10 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@d5cdab toggle before: blue
Apr 22, 2008 10:08:10 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@4d3241 toggle before: red
Apr 22, 2008 10:08:10 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@4d3241 toggle after: red
Apr 22, 2008 10:08:10 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@d5cdab toggle after: blue

As expected it uses the blue instance of the ThreadSafeInterceptor.

Adding two param fields to see if both interceptors can be “replaced” with new ones for one action fails. Using a second black param entry as follows:

       
            /example/HelloWorld.jsp
            
blue
black
            
        

shows:

Apr 22, 2008 10:51:06 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@9d963f toggle before: black
Apr 22, 2008 10:51:06 PM com.opensymphony.xwork2.validator.ActionValidatorManagerFactory 
INFO: Detected AnnotationActionValidatorManager, initializing it...
Apr 22, 2008 10:51:06 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@1b5077 toggle before: red
Apr 22, 2008 10:51:07 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@1b5077 toggle after: red
Apr 22, 2008 10:51:07 PM example.ThreadSafeInterceptor intercept
INFO: Object: example.ThreadSafeInterceptor@9d963f toggle after: black

The blue one does not even get created! Bummer… Well, we might be stretching the framework a bit, but it’s good to know the interceptors are really thread safe. And it brings the question: is it smart to use two of the same interceptors in one stack?

Time for one more try, lets make a second interceptor entry for the same class with a different name: threadsafe2.

		
			
			
			
				
green
				
   				
  				
red
  				
			
		

Now we can use:

       
            /example/HelloWorld.jsp
            
blue
black
            
        

And this just works! The console writes out: blue, black, black, blue. Great!

Pijpen op Marktplaats

Niet zo lang geleden zag ik een presentatie van iemand van Yahoo, over de architectuur van Yahoo Pipes. Ik kende die site nog niet, maar na de presentatie wilde ik er snel iets mee doen. Wat een geweldige user interface, in een browser met Javascript. Je sleept en plaatst wat kleine componenten bij elkaar die vervolgens door pijpen aan elkaar geknoopt worden. De gegevens stromen via die pijpen van componentje naar componentje. Een kind kan de was doen. Zo heb je componenten om web pagina’s of RSS feeds op te halen en componentjes die kunnen filteren of die tekst kunnen vervangen met behulp van reguliere expressies. En je kan makkelijk geografische informatie toevoegen en de resultaten op een kaart weergeven.

Mijn idee was om op een zinnige manier de zoekresultaten van Marktplaats onderhanden te nemen. Dat was makkelijker dan ik dacht, want de resultaten zijn met een rss feed op te halen. Je ziet het rss symbooltje rechts onder in de zoek resultaten pagina. Na een uurtje proberen had ik een simpele pipe klaar. Zie het resultaat hieronder of speel met de pipe zelf. Je kan de pipe ook eenvoudig toevoegen aan je iGoogle pagina, dan kan je nieuwe zoekresultaten meteen spotten.

Terug op marktplaats zag ik dat ze deze kaart functionaliteit al zelf bieden, dus veel voegt het niet toe… maar grappig is het wel en er is vast nog iets heel intelligents mee te bouwen.

P.S. je kan zo’n pipe clonen en een eigen versie maken…

Het lijkt erop dat de kaart in Firefox grijs blijft?! Is dit een bug?

laws, laws, laws

What drives the social networks in a technical sense (I heard this in one of the IT Conversations I think):

  • Metcalfe’s law
  • Moore’s law
  • Maslow’s pyramid
  • Do you know them all? It is an interesting mind game to see how they all interact…

    Java and the Client, will they ever be happy?

    In a recent interview (well, that’s a big word, I was asked a question about Java…) I mentioned that there is still a future for Java on the Client. But is there?

    There are so many Rich Internet Application frameworks and solutions nowadays: Flex, Lazlo, Ajax-based, Silverlight, etc… it dazzles the mind. What to pick? Why learn yet another tool, configuration and/or language?

    As a Java developer, would it not be wonderful to do everything in Java. Drop the XML configuration and remoting. No JSPs or inside-out HTML, tags with HTML, Javascript, XHTML, CSS, and what not…

    And I was amazed with the requirements of web applications: master-detail updates, dynamic drop downs, complex grids with excel behaviour, drag and drop, tabs… and all the roundtrips that are needed to get things done. We’ve come a long way now (see Google apps), but I think there is still something to be said for plain old Java GUI’s.

    That was the entry point for a little project that I wrote. I’ve tagged it the “290% Java project” (stole that from the Oracle statement of a couple of years ago: “300% Java…”): Java in the Data layer, the Application Server layer and the Client layer.

    How to accomplish this? Create a Swing or RCP “lite weight” client. Make this available via Java Web Start. Communicate simple POJOs via the Burlap and/or Hessian protocol, using a Servlet in Tomcat Server. Also use those protocols to store the data on disk (no database!). The remote server calls are defined by a clean Java interface. To create a Swing UI use a tool such as JFormDesigner (I hear good things of Matisse nowadays).

    The client app runs both on Windows and the Mac, using Java 5 or 6.

    This app is now being used for almost 2 years in a row, daily, both at home and at the workplace by around 10 people. It is like a bulletin board for a group, that is closed. Everybody in the group can read all messages, but you can specify who the message is intended for. Furthermore, it shows you directly if the message has been read by all that it is intended for (a feature that I did not come acros in email or other bulletin boards), and when it was read.

    It was such a pleasure to create a UI in Java after all the struggles in the Browser world.

    I presented this approach to a group of Java developers. I was surprised to learn that there is fear to start using this “all Java architecture” approach. The reasons being that there skills to program Swing or RCP are hard to find, JSP and JSF programmers are much easier to find. (Is this true?) Also, Swing seems to be considered hard to do and still has a reputation of being slow. Well… look at NetBeans; great job for a Swing ui! I just love the MVC model of it.

    Some other points:

  • Rolling out a new version of the app is as simple as installing a new server side app: just put the news jars on a central site and Java Web Start will automatically get that new version when users open the application.
  • You can make a shortcut from the desktop to a JWS program.
  • You can use the same POJO model for your data on the server and on the client.
  • The model can be effectively cached in the client app. You can make it smart. In my proof of concept there is a version check of the model on the server. If it has not been changed, there is no need to refetch it. If it has changes (e.g. new or updated messages), only send the delta.
  • You can make use of third party jars on the client machine. For instance to generate PDF docs on the fly using iText. This relieves the server of a lot of memory and processor consumption.
  • You can make truely amazing interfaces using Java 2D, Java 3D, jGoodies, and maybe even JavaFX (there is a JavaFX demo on the Hessian RIA page, I discovered recently!)
  • You can use peer-to-peer architectures to communicate via clients directly (a la Azureus :-) )
  • There are some things to be aware of, or “some interesting issues”:

  • How to deal with security and authorisation.
  • How to secure your data over the line. Note that I used https for all communication. It even works behind corporate firewalls!
  • You need to sign all jars to make communication available, or file system access. Not a big deal with a few lines of ant script.
  • You also need to sign with a trusted certificate, so you will not get annoying popups during the app startup. These are quite expensive for a proof of concept (couple of hunderds of Euros). Although you can use a free Thawte personal certificate that is intended for e-mail.
  • There are some caching issues with JWS. Workaround that by putting version numbers in the jnlp filename or url.
  • What about using a very lite weight jump start client, based on Rich Client Platform of eclipse? You would only need Java Web Start for that little client. And then you can use the OSGi plugin model to dynamically load your app (or portal like app!) with the already present upgrade feature in eclipse.
  • All in all, I really like this concept. Who dares to make the jump??? I think it is really suited for intranet apps that have complex GUI requirements, and need to be very responsive…

    Let me know!

    Simple Screenshot tool for Windows

    Here is a handy little tool that I use to make screenshots on windows: ScreenHunter.
    It can make screenshots of a selected window, but also of any part of the screen. Including mousepointer and drop down menus if needed! And the most simple edition is free, but effective.

    Thoughts on Proximity Information

    More and more phones, laptops and other devices that you can carry around have bluetooth and/or wifi capabilities.

    This opens up interesting use of local wifi sites. In London center area there is free wifi internet access. Every 5 minutes an advertisement shows and the bandwidth is limited. You can also pay and subscribe to the service. That disables the advertisements and provides more bandwidth.

    When I visited the Westminster area I had access to a freely available wifi connection. Unfortunately, the connection did not let me get online.

    Instead it supplied a couple of web pages with local information: car park information and prices, a list of public buildings in the vicinity, local museums opening times,… Very clever…

    An interesting concept: the wifi channel acts like a local information and advertisement bulletin board.

    This can be done anywhere: proximity information! Think of music festivals that show the latest news and actual schedules. Or conferences that show background information for visitors.

    Local contacts: when you walk into a hotel you automatically are assigned some phone numbers in your phone for room service and complaints. These are removed when you leave the building.

    Bluetooth matching: State your interests with a bluetooth list on a trade fair. It will make your phone buzz when you walk past a stand that matches one or more of your interests… what is the progress there? I have heard of bluetooth dating on the streets this way, called toothing. Or was it a hoax?

    Anyway, now that I have a wifi enabled phone, every now and again I scan the available networks wherever I am. And the shear number of them surprises me. It is a pity that a lot of them are not or public use. (And I love the names that some people think of for their wireless networks…)

    Whatever happened to that project I was mailed about two years ago: get a wireless modem for 5 bugs. That was cheap! The deal was that you had to share your connection with everybody.

    To make it useable and safe, it actually provided two wifi channels: one private for your local network, and one public. Now that wifi phones are available, this makes so much more sense!!!

    update 9 april 2008: Recently I was at a pop concert in a big hall, and being surrounded by all these people I thought to check the bluetooth phones around me. And I was surprised to see so many devices listed! Half of the time it was ust the phone name, but the other half were names of the owners: “Suusje”, “Peter Br”, … So with bluetooth you can actually spot who is around you…

    Processing QCON 2008

    It has been over a week since I visited the Qcon conference in London. It was a great experience that gave me lots of new thoughts and inspiration.

    Here are my Top Quotes and Thoughts over the three days of sessions:

  • Stateless Architectures push the “state” problem to the database, the trend is to reclaim the state in the application server and services, and put state close to the user
  • To make really scalable solutions: divide, split, partition, work asynchronously, no state
  • Transactions are not that important
  • “If you can’t split it, it doesn’t scale”
  • Clouds and Grids are very interesting and usable today, see Amazon S3 and EC2
  • Java APIs are out, new (scripting, functional, non-typesafe) languages are in
  • There are a lot of competing shared object cache solutions out there! (Terracotta, Coherence, Cache, Gigaspaces, …)
  • REST is an ESB, the Web is a perfect working example of an (Enterprise) Service Bus
  • Continuous Performance Testing is not used by many teams
  • extreme: Continuous Production Deployment, some projects deploy each half hour to production (Flickr?)!
  • new API’s: less is better than wrong API’s
  • I like this phrase: “continuous health”
  • “the re-celabration of drag ‘n drop” (now with Ajax/Web 2.0)
  • you need a “compute cloud” when you get slashdotted!
  • “logic as a service”
  • the ultimate mobile app: “show me the nearby restrooms!”
  • first build something simple, then think of all the “…ilities”
  • what would (Yahoo) pipes do inside the enterprise? Think of it of a dynamic and flexible “business objects” for all the information flowing around
  • the concept of “openess of data”: a lot of data gets out into the open and becomes publicly owned. See the conclusion of the book: “Everything is Miscelleaneous”, businesses do not own their data anymore, only their product. Business trend is transparency.
  • what about privacy issues in a product such as “Google Health”?
  • “coupled by latency”: functionally or logically uncoupled components can still be coupled this way!
  • make failover part of your regular upgrades
  • the need for hierarchies dissappears when you make everything findable and/or taggable
  • If everything is findable and taggable we can get rid of the intrinsic or explicit relation ships: relationships are tags that appear by just tagging.
  • the accountability of software also holds for organic food: effective, reliable, reasonably priced. Compare bloathed software to industrial food: unneccessary features are like unneccessary additives.
  • Agile: where business meets development
  • scale in – scale out – scale up
  • what is this “Map-Reduce” I keep hearing about?
  • Idempotent things scale well
  • dynamic relocation of people at conferences due to Twitter: am I at the right track? (e.g. “This talk is great” or “Boring talk, try to leave now!”
  • If you tie a quote or idea to a session, I’ll tag you a star!

    Many Eyes, Many Perspectives

    A while ago I was pleasantly surprised to hear about a project called “Many Eyes” that is hosted (and sponsored?) by IBM alphaware. That project was featured on IT Conversations. Not only the title of the project, but also the goal, reminded me a lot of the article that I got published during the last year of my Software Engineering study. The article was called “So many users, so many perspectives, sharing glasses and throwing remote controls”. It was about visualizing (dynamic) datasets and sharing the visualisations (using the glasses metaphor) with other online users, to aid collaboration between them.

    The Many Eyes project does something similar: upload a (static) dataset (a text, or excel sheet, …) and choose a kind of graph to analyse the data. Then you can share this graph with others, publish it, and discuss it online.

    The similarities in name and goal struck me…

    I wonder if you can hook up some cloud processing on big datasets and then presenting the results with Many Eyes…

    Resources:

  • Many Eyes
  • So Many Users, So Many Perspectives article
  • the Many Eyes talk
  • a Groovy Database Compare

    In a typical software development environment there are a ton of databases for each of the stages: development databases, system test database, …, up to the production database.

    Tools that compare databases come in quite handy in these cases. Database meta-data or schema’s can be compared to discover which new tables, columns or views were added, removed or changed. And the data in particular tables can be diff’ed. For instance to check if a list of static data values has changed.

    I tried some tools when I wanted to compare database tables in two similar databases, but they would not suffice. A simple diff between the two tables would show too much information. It was not useful.

    The table contained the list of all database update scripts that were run on that particular database. Scripts sometimes failed, or were run multiple times. I had a simple question: what scripts were run on one database, but not on the other.

    I needed a smart compare solution to filter out double entries, or entries that were failed runs, run on other dates, etc. Simply skipping some columns from the compare would not suffice.

    So…. scripting to the rescue. I composed a Groovy script that makes complex compares possible, using the default Java Set collections.

    Here is the code:

    /**
     * This groovy script compares two database tables.
     *
     * Usage: groovy compareDatebaseTables.groovy
     *
     * Note: the database drivers must be on the classpath
     * Use something like:
     *   set CLASSPATH=%CLASSPATH%;C:oracle_driversclasses12.zip
     *
     * 2008 Peter Paul Bakker
     */
    import groovy.sql.Sql
    import java.io.File
    import java.util.Properties
    
    class DatabaseUpdate implements Comparable {
    	String scriptNumber
    	Date runDate
    	String scriptFile
    	String result
    
    	public DatabaseUpdate(String scriptNumber, Date runDate,
    		String scriptFile, String result) {
    
    		this.scriptNumber = scriptNumber
    		this.runDate = runDate
    		this.scriptFile = scriptFile
    		this.result = result
    	}
    
    	/*
    		The DatabaseUpdate is considered equal when the scriptnumber
    		and the result string are equal
    	*/
    	public boolean equals(Object o) {
    		return this.scriptNumber.equals(o.scriptNumber)
    			&& this.result.equals(o.result)
    	}
    
    	public int hashCode() {
    		return this.scriptNumber.hashCode()
    			+ this.result.hashCode()
    	}
    
    	public String toString() {
    		return "${this.scriptNumber}t${this.runDate}" +
    			"t${this.scriptFile}t${this.result}"
    	}
    
    	public int compareTo(Object o) {a
    
    		// convert null to "null" to avoid nullpointers
    		String compResult = (this.result == null ? "null" : this.result)
    		String compOtherResult = (o.result == null ? "null" : o.result)
    
    		int resultScriptNumber = this.scriptNumber.compareTo(o.scriptNumber)
    		// scriptnumber is leading in the order
    		if (resultScriptNumber != 0) { return resultScriptNumber }
    		else { return  compResult.compareTo(compOtherResult) }
    	}
    
    }
    
    public Set getDatabaseUpdates(connectionString, user, password ) {
    	println("Start fetch data from ${connectionString}")
    
    	Set databaseUpdates = new TreeSet()
    
    	// get the complete list of scripts
    	sql = Sql.newInstance(connectionString, user, password,
                      "oracle.jdbc.driver.OracleDriver") 
    
    	sql.eachRow("select * from DB_UPDATES") {
    		databaseUpdates.add(
    			new DatabaseUpdate(
    				it.script_number, it.run_date, it.script_file, it.result))
    	}
    	sql.close()
    
    	return databaseUpdates
    }
    
    public void writeDatabaseUpdates(filename, databaseUpdates) {
    	println("Writing data to ${filename}")
    	file = new java.io.File(filename)
    	writer = file.newPrintWriter()
    	databaseUpdates.each { writer.println(it) }
    	writer.close()
    }
    
    public void doYourThing() {
    	// read in the config file
    	def props = new Properties();
    	props.load(new java.io.FileInputStream('compareDatabaseScripts.props'))
    
    	DATABASE1_NAME = props.getProperty("DATABASE1_NAME")
    	DATABASE1_ADDRESS = props.getProperty("DATABASE1_ADDRESS")
    	DATABASE1_PORT = props.getProperty("DATABASE1_PORT")
    	DATABASE1_LOGIN = props.getProperty("DATABASE1_LOGIN")
    	DATABASE1_PASSWORD = props.getProperty("DATABASE1_PASSWORD")
    
    	DATABASE2_NAME = props.getProperty("DATABASE2_NAME")
    	DATABASE2_ADDRESS = props.getProperty("DATABASE2_ADDRESS")
    	DATABASE2_PORT = props.getProperty("DATABASE2_PORT")
    	DATABASE2_LOGIN = props.getProperty("DATABASE2_LOGIN")
    	DATABASE2_PASSWORD = props.getProperty("DATABASE2_PASSWORD")
    
    	// TreeSet is an implementation of SortedSet
    	Set databaseUpdates1 = getDatabaseUpdates(
    		"jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS =
    			(PROTOCOL = TCP)(HOST = ${DATABASE1_ADDRESS})(PORT = ${DATABASE1_PORT})))
    			(CONNECT_DATA = (SID = ${DATABASE1_NAME})))",
    		DATABASE1_LOGIN, DATABASE1_PASSWORD)
    
    	Set databaseUpdates2 = getDatabaseUpdates(
    		"jdbc:oracle:thin:" +
                    "@${DATABASE2_ADDRESS}:${DATABASE2_PORT}:${DATABASE2_NAME}",
    		DATABASE2_LOGIN, DATABASE2_PASSWORD)
    
    	// make sure to create a new set, so that the original set is left intact
    	def databaseUpdatesOnlyIn1 = new TreeSet(databaseUpdates1)
    	def databaseUpdatesOnlyIn2 = new TreeSet(databaseUpdates2)
    
    	databaseUpdatesOnlyIn1.removeAll(databaseUpdates2)
    	databaseUpdatesOnlyIn2.removeAll(databaseUpdates1)
    
    	writeDatabaseUpdates(
                    "databaseUpdatesIn_${DATABASE1_NAME}_AndNotIn_${DATABASE2_NAME}.txt",
    		databaseUpdatesOnlyIn1)
    	writeDatabaseUpdates(
                     "databaseUpdatesIn_${DATABASE2_NAME}_AndNotIn_${DATABASE1_NAME}.txt",
    		databaseUpdatesOnlyIn2)
    
    	println("Done!")
    }
    
    doYourThing() 
    

    (Note that some lines have additional line breaks for readability.)

    The script makes use of a class called DatabaseUpdate that implements Comparable. This makes the class usable in the standard Java Set collections. Sets have the property that thay contain only one item of an element that is considered equal.

    The script starts by reading the database connection information from a properties file. This is for two databases that have the same schema (or at least the same table).

    As you can see it is then very easy to retrieve all data from a table. With a closure of sql.eachRow we create DatabaseUpdate elements that are added to a TreeSet.

    With simple removeAll() methods of the Sets, we can determine the outer areas of two overlapping circles (ven-diagrams they are called in Holland).

    The tricky bit is to make sure which DatabaseUpdate elements are equal to eachother. This is also the flexible part, because you can decide to ignore certain variables of the elements or to put some other logic in the compare method.

    In this case we only look at the script number and the result. The result can be success or failure for instance. A succes should not equal a failure! Also, the rundate is left out, because scripts can be run on different days on different databases.

    Note that you need to implement three methods for this to work: compareTo(), equals() and hash(). See also the javadoc of Set, TreeSet and Comparable.

    This simple example can easily be extended in more complex compare or diffs of database tables!