The Google Chrome web browser has continued to gain in popularity since most of us first heard about it. Daniel Cawrey's blog post w3schools: Chrome Has Surpassed Ten Percent Share points out that W3Schools's Browser Statistics Month by Month shows the Chrome browser being used for 10.8% of visits to that web site in January 2010. In that same month, Microsoft Internet Explorer accounted for 36.2% of visits (14.3% MSIE 8, 11.7% MSIE 7, and 10.2% MSIE 6) and Mozilla Firefox accounted for 46.3% of visits.
The numbers reported by W3Schools (especially the totals of 46.3% for Firefox, 36.2% for MSIE, and 10.8% for Chrome) are not exactly what one would expect to see in the general web browsing population and this difference reflects the specialized web development focus of the W3Schools audience. Knowing that the audience of people who would read my blog or come to it from a search engine would be similarly slanted in terms of which browsers were used, I was curious to see exactly which browsers are most popular for visitors to my blog. I used Google Analytics to determine the following numbers for viewers of my blog in January 2010: Firefox 57%, Chrome 20%, Internet Explorer 10%, Safari 5.6%, and several others picking up the remaining percentage. These numbers show that Firefox is by far the most dominant browser for people who access my blog and that Chrome makes a very impressive second-place appearance with 20% of the visits.
Neither the W3Schools nor my blog usage are indicative of the general web browsing population. NetMarketShare provides an estimate of more general browser usage and its January 2010 results show Internet Explorer still being the dominant browser by far with Firefox firmly in control of second place followed by Chrome and then Safari. According to NetMarketShare's numbers more than 4 out of every 5 browsers being used is Internet Explorer or Firefox.
The long-term implications of all this remain to be seen, but there are some short-term observations that can be made. Not surprisingly, Chrome and Firefox appear to be far more popular in the software development world (including Java development and web development) than in the general population. For web developers in particular, it has long been frustrating to deal with MSIE's being stubbornly non-standard in many different areas. However, because MSIE has dominated the general web user community, it has been important to write significant non-standard code to support that large set of users. Furthermore, Chrome continues to gain users in both the general public and in the software development communities.
Security concerns related to Microsoft Internet Explorer are not really new, but have recently received renewed focus, including the now frequently cited French and German governments' recommendations to abstain (temporarily) from using MSIE. In a blog post earlier this week called IE Flaw Makes Local Files Public, taranfx states, "The end of Internet Explorer is finally here" and supports this conclusion with a brief account of problems related to use of MSIE that are "forcing users to move to alternatives." This post also provides a link to the BlackHat DC 2010 white paper Abusing Insecure Features of Internet Explorer.
Chrome is not only slowing chipping away at MSIE's usage, but is also taking market share from Firefox. It still remains to be seen how popular Chrome can become. My preference would be for Firefox and Chrome to remain strong alternatives in the browser market. I don't see a lot of benefit from Chrome simply replacing Firefox as an alternative. The best scenario for web developers and web users would be for the browser wars to drive greater standardization at a faster pace.
Dustin's Software Development Cogitations and Speculations My observations and thoughts concerning software development (general development, Java, JavaFX, Groovy, Flex, ...). Select posts from this blog are syndicated on DZone and Java Code Geeks and were formerly syndicated on JavaWorld.
Sunday, February 7, 2010
Saturday, February 6, 2010
Oracle Enterprise Pack for Eclipse
The Oracle Enterprise Pack for Eclipse (OEPE) is described as a "certified set of Eclipse plug-ins is designed to help develop, deploy and debug applications for Oracle WebLogic Server." Although this is true, it offers much more than that with support for development with the Spring Framework, the Java Persistence API (JPA) and/or other object-relational mapping technologies, and other enterprise development in conjunction with its support for WebLogic development and deployment. My article on using OEPE to improve development with Spring, JPA, and WebLogic was published this week on Oracle Technology Network. The article, called Build a Java Application with Eclipse, Spring, and Oracle WebLogic Server, covers the convenient features of OEPE for Spring and JPA developers, especially (but not necessarily) those who deploy to WebLogic.
There are several links on DZone related to the recent release of a new version of Oracle Enterprise Pack for Eclipse. These include Oracle Enterprise Pack for Eclipse 11gR1 (11.1.1.4) Release Webinar (linked article) and Oracle Upgrades Eclipse Free Plug-in Package (linked article). The EclipseZone portion of DZone has several additional articles devoted to OEPE such as the article Oracle Enterprise Pack for Eclipse (an interview with Ashish Mohindroo)
There are several links on DZone related to the recent release of a new version of Oracle Enterprise Pack for Eclipse. These include Oracle Enterprise Pack for Eclipse 11gR1 (11.1.1.4) Release Webinar (linked article) and Oracle Upgrades Eclipse Free Plug-in Package (linked article). The EclipseZone portion of DZone has several additional articles devoted to OEPE such as the article Oracle Enterprise Pack for Eclipse (an interview with Ashish Mohindroo)
Monday, February 1, 2010
Basic Java hashCode and equals Demonstrations
I often like to use this blog to revisit hard-earned lessons in the basics of Java. This blog post is one such example and focuses on illustration of the dangerous power behind the equals(Object) and hashCode() methods. I won't cover every nuance of these two highly significant methods that all Java objects have whether explicitly declared or implicitly inherited from a parent (possibly directly from Object itself), but I will cover some of the common issues that do arise when these are not implemented or are not implemented correctly. I also attempt to show by these demonstrations why it is important for careful code reviews, thorough unit testing, and/or tool-based analysis to verify the correctness of these methods' implementations.
Because all Java objects ultimately inherit implementations for
For the examples in this post, I'll be using the HashAndEquals class whose code listing is shown next to process object instantiations of various Person classes with differing levels of support for
HashAndEquals.java
The class above will be used as-is repeatedly with only one minor change later in the post. However, the
No Explicit
The first version of the
Person.java (no explicit hashCode or equals method)
This first version of

Several observations can be made from the output shown above. First, without explicit implementation of an
A second observation from this first example is that the hash code is different for each instance of the
Explicit
The second version of the
Person.java (explicit equals method provided)
When instances of this

The first observation is that now the
Explicit
It is now time to add an explicit
Here is
Person.java (explicit equals and hashCode implementations)
The output from running with the new

It is not surprising that the hash codes returned for objects with the same attributes' values are now the same, but the more interesting observation is that we can only add two of the four instances to the
The Trouble with Mutable hashCode Attributes
For the fourth and final example in this post, I look at what happens when the
Output generated from running this example is shown next.

The most interesting observation in this example is that although two instances get added to the set, only one gets removed. This is because one of the attributes upon which the hash code is based, first name, changes in between adding the object to the
Detecting Problems Related to hashCode and equals Implementations
As the examples in this post demonstrate, different behaviors occur depending on how these methods are implemented, but none of them involve an obvious error or warning. This can make it difficult to track down seemingly inconsistent or strange behavior. The best way to address this is with careful implementation of these methods, careful reviews of these important methods, and thorough testing. Another useful tactic is to avoid mindless creation of unnecessary "set" methods for all of a class's data attributes. If a "set" method is truly appropriate, that attribute should not be used in the implementation of
Conclusion
It is almost never appropriate to rely on an object's default implementation of
Because all Java objects ultimately inherit implementations for
equals(Object) and hashCode(), the Java compiler and indeed the Java runtime launcher will report no problem when invoking these "default implementations" of these methods. Unfortunately, when these methods are needed, the default implementations of these methods (like their cousin the toString method) are rarely what are desired. The Javadoc-based API documentation for the Object class discusses the "contract" expected of any implementation of the equals(Object) and hashCode() methods and also discusses the likely default implementation of each if not overridden by child classes.For the examples in this post, I'll be using the HashAndEquals class whose code listing is shown next to process object instantiations of various Person classes with differing levels of support for
hashCode and equals methods.HashAndEquals.java
package dustin.examples;
import java.util.HashSet;
import java.util.Set;
import static java.lang.System.out;
public class HashAndEquals
{
private static final String HEADER_SEPARATOR =
"======================================================================";
private static final int HEADER_SEPARATOR_LENGTH = HEADER_SEPARATOR.length();
private static final String NEW_LINE = System.getProperty("line.separator");
private final Person person1 = new Person("Flintstone", "Fred");
private final Person person2 = new Person("Rubble", "Barney");
private final Person person3 = new Person("Flintstone", "Fred");
private final Person person4 = new Person("Rubble", "Barney");
public void displayContents()
{
printHeader("THE CONTENTS OF THE OBJECTS");
out.println("Person 1: " + person1);
out.println("Person 2: " + person2);
out.println("Person 3: " + person3);
out.println("Person 4: " + person4);
}
public void compareEquality()
{
printHeader("EQUALITY COMPARISONS");
out.println("Person1.equals(Person2): " + person1.equals(person2));
out.println("Person1.equals(Person3): " + person1.equals(person3));
out.println("Person2.equals(Person4): " + person2.equals(person4));
}
public void compareHashCodes()
{
printHeader("COMPARE HASH CODES");
out.println("Person1.hashCode(): " + person1.hashCode());
out.println("Person2.hashCode(): " + person2.hashCode());
out.println("Person3.hashCode(): " + person3.hashCode());
out.println("Person4.hashCode(): " + person4.hashCode());
}
public Set addToHashSet()
{
printHeader("ADD ELEMENTS TO SET - ARE THEY ADDED OR THE SAME?");
final Set set = new HashSet();
out.println("Set.add(Person1): " + set.add(person1));
out.println("Set.add(Person2): " + set.add(person2));
out.println("Set.add(Person3): " + set.add(person3));
out.println("Set.add(Person4): " + set.add(person4));
return set;
}
public void removeFromHashSet(final Set sourceSet)
{
printHeader("REMOVE ELEMENTS FROM SET - CAN THEY BE FOUND TO BE REMOVED?");
out.println("Set.remove(Person1): " + sourceSet.remove(person1));
out.println("Set.remove(Person2): " + sourceSet.remove(person2));
out.println("Set.remove(Person3): " + sourceSet.remove(person3));
out.println("Set.remove(Person4): " + sourceSet.remove(person4));
}
public static void printHeader(final String headerText)
{
out.println(NEW_LINE);
out.println(HEADER_SEPARATOR);
out.println("= " + headerText);
out.println(HEADER_SEPARATOR);
}
public static void main(final String[] arguments)
{
final HashAndEquals instance = new HashAndEquals();
instance.displayContents();
instance.compareEquality();
instance.compareHashCodes();
final Set set = instance.addToHashSet();
out.println("Set Before Removals: " + set);
//instance.person1.setFirstName("Bam Bam");
instance.removeFromHashSet(set);
out.println("Set After Removals: " + set);
}
}
The class above will be used as-is repeatedly with only one minor change later in the post. However, the
Person class will be changed to reflect the importance of equals and hashCode and to demonstrate how easily it can be to mess these up while at the same time being difficult to track down the problem when there is a mistake.No Explicit
equals or hashCode MethodsThe first version of the
Person class does not provide an explicit overridden version of either the equals method or the hashCode method. This will demonstrate the "default implementation" of each of these methods inherited from Object. Here is the source code for Person without hashCode or equals explicitly overridden.Person.java (no explicit hashCode or equals method)
package dustin.examples;
public class Person
{
private final String lastName;
private final String firstName;
public Person(final String newLastName, final String newFirstName)
{
this.lastName = newLastName;
this.firstName = newFirstName;
}
@Override
public String toString()
{
return this.firstName + " " + this.lastName;
}
}
This first version of
Person does not provide get/set methods and does not provide equals or hashCode implementations. When the main demonstration class HashAndEquals is executed with instances of this equals-less and hashCode-less Person class, the results appear as shown in the next screen snapshot.
Several observations can be made from the output shown above. First, without explicit implementation of an
equals(Object) method, none of the instances of Person are considered equal, even when all attributes of the instances (the two Strings) are identical. This is because, as is explained in the documentation for Object.equals(Object), the default equals implementation is based on an exact reference match:The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).
A second observation from this first example is that the hash code is different for each instance of the
Person object even when two instances share the same values for all of their attributes. The HashSet returns true when a "unique" object is added (HashSet.add) to the set or false if the added object is not considered unique and so is not added. Similarly, the HashSet's remove method returns true if the provided object is considered found and removed or false if the specified object is considered to not be part of the HashSet and so cannot be removed. Because the equals and hashCode inherited default methods treat these instances as completely different, it is no surprise that all are added to the set and all are successfully removed from the set.Explicit
equals Method OnlyThe second version of the
Person class includes an explicitly overridden equals method as shown in the next code listing.Person.java (explicit equals method provided)
package dustin.examples;
public class Person
{
private final String lastName;
private final String firstName;
public Person(final String newLastName, final String newFirstName)
{
this.lastName = newLastName;
this.firstName = newFirstName;
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (this == obj)
{
return true;
}
if (this.getClass() != obj.getClass())
{
return false;
}
final Person other = (Person) obj;
if (this.lastName == null ? other.lastName != null : !this.lastName.equals(other.lastName))
{
return false;
}
if (this.firstName == null ? other.firstName != null : !this.firstName.equals(other.firstName))
{
return false;
}
return true;
}
@Override
public String toString()
{
return this.firstName + " " + this.lastName;
}
}
When instances of this
Person with equals(Object) explicitly defined are used, the output is as shown in the next screen snapshot.
The first observation is that now the
equals calls on the Person instances do indeed return true when the object is equal in terms of all attributes being the same rather than checking for a strict reference equality. This demonstrates that the custom equals implementation on Person has done its job. The second observation is that implementation of the equals method has had no effect on the ability to add and remove the seemingly same object to the HashSet.Explicit
equals and hashCode MethodsIt is now time to add an explicit
hashCode() method to the Person class. Indeed, this really should have been done when the equals method was implemented. The reason for this is stated in the documentation for the Object.equals(Object) method:Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.
Here is
Person with an explicitly implemented hashCode method based on the same attributes of Person as the equals method.Person.java (explicit equals and hashCode implementations)
package dustin.examples;
public class Person
{
private final String lastName;
private final String firstName;
public Person(final String newLastName, final String newFirstName)
{
this.lastName = newLastName;
this.firstName = newFirstName;
}
@Override
public int hashCode()
{
return lastName.hashCode() + firstName.hashCode();
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (this == obj)
{
return true;
}
if (this.getClass() != obj.getClass())
{
return false;
}
final Person other = (Person) obj;
if (this.lastName == null ? other.lastName != null : !this.lastName.equals(other.lastName))
{
return false;
}
if (this.firstName == null ? other.firstName != null : !this.firstName.equals(other.firstName))
{
return false;
}
return true;
}
@Override
public String toString()
{
return this.firstName + " " + this.lastName;
}
}
The output from running with the new
Person class with hashCode and equals methods is shown next.
It is not surprising that the hash codes returned for objects with the same attributes' values are now the same, but the more interesting observation is that we can only add two of the four instances to the
HashSet now. This is because the third and fourth add attempts are considered to be attempting to add an object that was already added to the set. Because there were only two added, only two can be found and removed.The Trouble with Mutable hashCode Attributes
For the fourth and final example in this post, I look at what happens when the
hashCode implementation is based on an attribute that changes. For this example, a setFirstName method is added to Person and the final modifier is removed from its firstName attribute. In addition, the main HashAndEquals class needs to have the comment removed from the line that invokes this new set method. The new version of Person is shown next.package dustin.examples;
public class Person
{
private final String lastName;
private String firstName;
public Person(final String newLastName, final String newFirstName)
{
this.lastName = newLastName;
this.firstName = newFirstName;
}
@Override
public int hashCode()
{
return lastName.hashCode() + firstName.hashCode();
}
public void setFirstName(final String newFirstName)
{
this.firstName = newFirstName;
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (this == obj)
{
return true;
}
if (this.getClass() != obj.getClass())
{
return false;
}
final Person other = (Person) obj;
if (this.lastName == null ? other.lastName != null : !this.lastName.equals(other.lastName))
{
return false;
}
if (this.firstName == null ? other.firstName != null : !this.firstName.equals(other.firstName))
{
return false;
}
return true;
}
@Override
public String toString()
{
return this.firstName + " " + this.lastName;
}
}
Output generated from running this example is shown next.

The most interesting observation in this example is that although two instances get added to the set, only one gets removed. This is because one of the attributes upon which the hash code is based, first name, changes in between adding the object to the
HashSet and attempting to remove the same object (albeit with a changed first name attribute) from the same HashSet. This illustrates the importance of implementing hashcode (and by extension, equals) on immutable values. More details regarding this can be found in blog posts HashSet.contains(): does your busket contain something? (original location) and Back to hashCode Mutability (original location).Detecting Problems Related to hashCode and equals Implementations
As the examples in this post demonstrate, different behaviors occur depending on how these methods are implemented, but none of them involve an obvious error or warning. This can make it difficult to track down seemingly inconsistent or strange behavior. The best way to address this is with careful implementation of these methods, careful reviews of these important methods, and thorough testing. Another useful tactic is to avoid mindless creation of unnecessary "set" methods for all of a class's data attributes. If a "set" method is truly appropriate, that attribute should not be used in the implementation of
equals or hashCode.Conclusion
It is almost never appropriate to rely on an object's default implementation of
hashCode and equals as inherited from Object. Furthermore, the equals and hashCode methods should be implemented to the contract advertised in the Javadoc documentation (only a small part of which was covered here) and should not be based on any attributes that will be changed (mutable) during the lifecycle of the instance.
Saturday, January 30, 2010
Groovy and SQL*Plus
Oracle's SQL*Plus is not as user-friendly as SQL Developer or JDeveloper for manipulating data in an Oracle database, but it is still commonly used. In fact, SQL*Plus is often preferred to the tools with fancier user interfaces for quick and dirty manipulation, for running SQL scripts, for being run as part of shell scripts, and for other non-interactive uses. Furthermore, Oracle's SQL*Plus page calls SQL*Plus "the primary interface to the Oracle Database server." In this blog post, I look at using SQL*Plus in conjunction with Groovy scripts.
Groovy's Process Management plays the most significant role in the process of using Groovy with SQL*Plus. This functionality is easily applied thanks to the GDK's String extension that includes an execute() method which returns a Process running the command contained within that GDK-extended String. This mechanism will be used repeatedly in the examples in this blog post.
Notes Related Specifically to Windows/Vista
Many books, blogs, and articles on using Groovy with Windows rightly point out that some of the commonly used commands in Windows are not actually executables, but are instead built-in commands. These commands must be prepended with cmd /C to be executed appropriately. However, this step is unnecessary when invoking SQL*Plus from Groovy on Windows because SQL*Plus is a separate executable.
When using Vista, it is important to run SQL*Plus with the appropriate privileges. Because the Groovy scripts in this post run SQL*Plus, the scripts should be executed in a console window that is being run under administrative privileges. An error message including the phrase "The requested operation requires elevation" is seen when the Groovy scripts using SQL*Plus are executed without the appropriate privileges. This is demonstrated in the next screen snapshot.

Example 1: Basic Query Statement
One of the easiest ways of using SQL*Plus is to write a file containing commands to be run within SQL*Plus. These files often end with the
In this first example, that SQL*Plus script file is called
01-employeeIds.sql
The above script is expected to be executed against the HR sample schema that is supplied with most modern versions of the Oracle database. The Groovy to run this SQL*Plus script file is contained in the next code listing for
sql01.groovy
The above Groovy script specifies an explicit call to SQL*Plus with the 'hr' username and associated 'hr' password. It then runs the script showed above by prefacing its name with the "@" symbol. All of this is included in a GDK-extended String upon which the
The initial results of running the above script are shown in the next screen snapshot:

In the above output, the first of many rows returned from the database are shown after the SQL*Plus banner is displayed and the line showing that the GDK
When run as shown above, this script never ends because the SQL*Plus session is never exited. This is easily addressed by adding the word "exit" to the end of the SQL script, which will be done in the next example. The next screen snapshot demonstrates how the script appears to "hang" when the SQL*Plus session has not been explicitly exited.

Example 2: Cleaning Up SQL*Plus's Output
We often don't want the SQL*Plus banner to be displayed when running SQL*Plus via script. SQL*Plus provides the
sql02.groovy
The only changes to this script from the Groovy script in the first example is the addition of
When running SQL*Plus with scripts, it is often convenient to suppress other portions of feedback in addition to suppressing the SQL*Plus banner. Many of these types of output are turned off or suppressed within SQL*Plus by using the
02-employeeIds.sql
The next two screen snapshots demonstrate the leaner/cleaner output with the SQL*Plus banner suppressed along with no headers, no page separation, and no feedback. The second of the images proves that


Example 3: Passing in Parameter
It is often the case that we want to run SQL*Plus scripts depending on parameters set dynamically when the script is executed. SQL*Plus accepts command-line parameters and this feature is leveraged in the next SQL script listing.
03-employeeFind.sql
In the above script, an employee's full name is returned based on a provided parameter (
sql03.grooy
This Groovy script exits promptly if no parameter is provided and informs the user that an ID is required. If the ID is provided, it is passed to the SQL*Plus script by appending it to the end of the SQL*Plus invocation. The output from this is shown next.

The screen snapshot just shown demonstrates that the parameter passing from command-line to Groovy script to SQL*Plus worked fine. The only downside is that the SQL*Plus variable substitution is explicitly shown and this may not always be desirable. I can turn this off by specifying

Example 4: Handling Return SQL*Plus Return Code
Because Groovy's process management capability uses a GDK-extended Process, the Process.exitValue() method can be used to access the codes returned from invoked commands. This allows the Groovy code to analyze the codes returned from the invoked script and act appropriately.
To illustrate this, the SQL*Plus script used above is modified to return a -4. The modified version is shown next.
04-employeeFind.sql
The modified Groovy script that takes advantage of
sql04.groovy
When this script is executed, its output looks like that shown in the next screen snapshot.

Example 5: Processing Significant SQL*Plus Output
There are times when the Groovy script needs to process more than a return code. One way of doing this is to have the SQL*Plus script write out data to an external file that the Groovy script can process. It helps, of course, that Groovy provides some nice GDK File extensions.
The SQL*Plus SPOOL command can be used to spool output to an operating system file.
An example of a SQL*Plus script that writes employee last names and first names, separated by a comma, on individual lines of a file named "name.txt" is shown next.
05-employeeList.sql
When a Groovy script runs this file, the expected file named
This output file can be processed in the same Groovy script that caused it to be generated. Groovy provides rich features for file handling and String manipulation, making it highly effective in this situation.
sql05.groovy
Much more sophisticated logic could be performed on the contents of the

Conclusion and Final Remarks
Groovy and SQL*Plus can be used effectively together to script data-related functionality. Groovy offers many of the advantages of traditional scripting languages when used in conjunction with SQL*Plus, but offers the added advantage of enjoying full access to the JVM and the plethora of Java libraries. Groovy can be used with existing SQL*Plus scripts or can access the database directly with its powerful and convenient JDBC abstraction GroovySql.
Groovy's Process Management plays the most significant role in the process of using Groovy with SQL*Plus. This functionality is easily applied thanks to the GDK's String extension that includes an execute() method which returns a Process running the command contained within that GDK-extended String. This mechanism will be used repeatedly in the examples in this blog post.
Notes Related Specifically to Windows/Vista
Many books, blogs, and articles on using Groovy with Windows rightly point out that some of the commonly used commands in Windows are not actually executables, but are instead built-in commands. These commands must be prepended with cmd /C to be executed appropriately. However, this step is unnecessary when invoking SQL*Plus from Groovy on Windows because SQL*Plus is a separate executable.
When using Vista, it is important to run SQL*Plus with the appropriate privileges. Because the Groovy scripts in this post run SQL*Plus, the scripts should be executed in a console window that is being run under administrative privileges. An error message including the phrase "The requested operation requires elevation" is seen when the Groovy scripts using SQL*Plus are executed without the appropriate privileges. This is demonstrated in the next screen snapshot.

Example 1: Basic Query Statement
One of the easiest ways of using SQL*Plus is to write a file containing commands to be run within SQL*Plus. These files often end with the
.sql suffix and are typically executed in SQL*Plus by prefacing the name of script file (with path if the file is not in the current working directory) with the @ symbol.In this first example, that SQL*Plus script file is called
01-employeeIds.sql and is a single-line script:01-employeeIds.sql
select employee_id from employees;
The above script is expected to be executed against the HR sample schema that is supplied with most modern versions of the Oracle database. The Groovy to run this SQL*Plus script file is contained in the next code listing for
sql01.groovy:sql01.groovy
#!/usr/bin/env groovy
def sqlplusCmd = "sqlplus hr/hr @01-employeeIds.sql".execute()
println "${sqlplusCmd.class}"
sqlplusCmd.in.eachLine { line -> println line }
The above Groovy script specifies an explicit call to SQL*Plus with the 'hr' username and associated 'hr' password. It then runs the script showed above by prefacing its name with the "@" symbol. All of this is included in a GDK-extended String upon which the
execute() method is invoked. The object returned from that call is a GDK-extended Process instance (as illustrated by the println call below its instantiation) from which each line is printed.The initial results of running the above script are shown in the next screen snapshot:

In the above output, the first of many rows returned from the database are shown after the SQL*Plus banner is displayed and the line showing that the GDK
String.execute() method returns a java.lang.ProcessImpl is displayed.When run as shown above, this script never ends because the SQL*Plus session is never exited. This is easily addressed by adding the word "exit" to the end of the SQL script, which will be done in the next example. The next screen snapshot demonstrates how the script appears to "hang" when the SQL*Plus session has not been explicitly exited.

Example 2: Cleaning Up SQL*Plus's Output
We often don't want the SQL*Plus banner to be displayed when running SQL*Plus via script. SQL*Plus provides the
-S option to suppress this banner. The next version of the Groovy script will take advantage of that as shown in sql02.groovy:sql02.groovy
#!/usr/bin/env groovy
def sqlplusCmd = "sqlplus -S hr/hr @02-employeeIds.sql".execute()
sqlplusCmd.in.eachLine { line -> println line }
The only changes to this script from the Groovy script in the first example is the addition of
-S to suppress SQL*Plus's banner and the calling of a different SQL script file (02-employeeIds.sql), which will be shown next.When running SQL*Plus with scripts, it is often convenient to suppress other portions of feedback in addition to suppressing the SQL*Plus banner. Many of these types of output are turned off or suppressed within SQL*Plus by using the
SET command to set the relevant property to a desired value. The code listing for 02-employeeIds.sql demonstrates turning off query results feedback, header information, and page separation in the SQL*Plus results and also explicitly exits from SQL*Plus.02-employeeIds.sql
-- HEADING off turns off column headings in output of query results
set HEADING off
-- FEEDBACK turns off message saying number of rows returned
set FEEDBACK off
-- PAGESIZE to 0 removes spaces between "pages" of results
set PAGESIZE 0
select employee_id from employees;
-- Return SQL*Plus settings to defaults
set HEADING on
set FEEDBACK 6
set PAGESIZE 14
exit
The next two screen snapshots demonstrate the leaner/cleaner output with the SQL*Plus banner suppressed along with no headers, no page separation, and no feedback. The second of the images proves that
exit successfully exits SQL*Plus (and the invoking Groovy script).

Example 3: Passing in Parameter
It is often the case that we want to run SQL*Plus scripts depending on parameters set dynamically when the script is executed. SQL*Plus accepts command-line parameters and this feature is leveraged in the next SQL script listing.
03-employeeFind.sql
-- HEADING off turns off column headings in output of query results
set HEADING off
-- FEEDBACK turns off message saying number of rows returned
set FEEDBACK off
-- PAGESIZE to 0 removes spaces between "pages" of results
set PAGESIZE 0
select first_name || ' ' || last_name "NAME" from employees where employee_id = &1;
-- Return SQL*Plus settings to defaults
set HEADING on
set FEEDBACK 6
set PAGESIZE 14
exit
In the above script, an employee's full name is returned based on a provided parameter (
&1) representing the employee's ID. To support this expected parameter, the Groovy script must supply that ID to the SQL*Plus script. This is shown in the Groovy script sql03.sql:sql03.grooy
#!/usr/bin/env groovy
if (!args)
{
println "You must supply the ID of the employee of interest."
System.exit(-1)
}
def sqlplusCmd = "sqlplus -S hr/hr @03-employeeFind.sql ${args[0]}".execute()
sqlplusCmd.in.eachLine { line -> println line }
This Groovy script exits promptly if no parameter is provided and informs the user that an ID is required. If the ID is provided, it is passed to the SQL*Plus script by appending it to the end of the SQL*Plus invocation. The output from this is shown next.

The screen snapshot just shown demonstrates that the parameter passing from command-line to Groovy script to SQL*Plus worked fine. The only downside is that the SQL*Plus variable substitution is explicitly shown and this may not always be desirable. I can turn this off by specifying
set VERIFY off in the SQL*Plus script. When I do that, the output is as shown next.
Example 4: Handling Return SQL*Plus Return Code
Because Groovy's process management capability uses a GDK-extended Process, the Process.exitValue() method can be used to access the codes returned from invoked commands. This allows the Groovy code to analyze the codes returned from the invoked script and act appropriately.
To illustrate this, the SQL*Plus script used above is modified to return a -4. The modified version is shown next.
04-employeeFind.sql
-- HEADING off turns off column headings in output of query results
set HEADING off
-- FEEDBACK turns off message saying number of rows returned
set FEEDBACK off
-- PAGESIZE to 0 removes spaces between "pages" of results
set PAGESIZE 0
-- VERIFY off turns off the prompts showing variable substitution
set VERIFY off
select first_name || ' ' || last_name "NAME" from employees where employee_id = &1;
-- Return SQL*Plus settings to defaults
set HEADING on
set FEEDBACK 6
set PAGESIZE 14
set VERIFY on
-- Pretend there is some type of error or other condition in which the returning
-- of -4 is appropriate
exit -4
The modified Groovy script that takes advantage of
Process.exitValue() to process this returned code is shown next. This Groovy script simply prints the value, but it could implement alternative logic based on the code returned.sql04.groovy
#!/usr/bin/env groovy
if (!args)
{
println "You must supply the ID of the employee of interest."
System.exit(-1)
}
def sqlplusCmd = "sqlplus -S hr/hr @04-employeeFind.sql ${args[0]}".execute()
sqlplusCmd.in.eachLine { line -> println line }
println "Return value: ${sqlplusCmd.exitValue()}"
When this script is executed, its output looks like that shown in the next screen snapshot.

Example 5: Processing Significant SQL*Plus Output
There are times when the Groovy script needs to process more than a return code. One way of doing this is to have the SQL*Plus script write out data to an external file that the Groovy script can process. It helps, of course, that Groovy provides some nice GDK File extensions.
The SQL*Plus SPOOL command can be used to spool output to an operating system file.
An example of a SQL*Plus script that writes employee last names and first names, separated by a comma, on individual lines of a file named "name.txt" is shown next.
05-employeeList.sql
-- HEADING off turns off column headings in output of query results
set HEADING off
-- FEEDBACK turns off message saying number of rows returned
set FEEDBACK off
-- PAGESIZE to 0 removes spaces between "pages" of results
set PAGESIZE 0
-- VERIFY off turns off the prompts showing variable substitution
set VERIFY off
-- COLSEP sets character to be placed between returned columns
set COLSEP ,
-- TRIMSPOOL affects spooling output only; not SQL*Plus
set TRIMSPOOL on
spool name.txt
select last_name, first_name from employees;
spool off
-- Return SQL*Plus settings to defaults
set HEADING on
set FEEDBACK 6
set PAGESIZE 14
set VERIFY on
set COLSEP " "
set TRIMSPOOL off
exit
When a Groovy script runs this file, the expected file named
name.txt is generated. A portion of it (top and bottom) is shown next.
Abel ,Ellen
Ande ,Sundar
Atkinson ,Mozhe
Austin ,David
Baer ,Hermann
Baida ,Shelli
Banda ,Amit
Bates ,Elizabeth
Bell ,Sarah
Bernstein ,David
Bissot ,Laura
Bloom ,Harrison
Bull ,Alexis
. . .
Vollman ,Shanta
Walsh ,Alana
Weiss ,Matthew
Whalen ,Jennifer
Zlotkey ,Eleni
This output file can be processed in the same Groovy script that caused it to be generated. Groovy provides rich features for file handling and String manipulation, making it highly effective in this situation.
sql05.groovy
#!/usr/bin/env groovy
def sqlplusCmd = "sqlplus -S hr/hr @05-employeeList.sql".execute()
def file = new File("name.txt")
file.eachLine { println "Name (Last, First): ${it}" }
Much more sophisticated logic could be performed on the contents of the
name.txt file, but this demonstrates how easy it is to fix the spooled output with the Groovy script. The bottom portion of the script's execution is shown in the next screen snapshot.
Conclusion and Final Remarks
Groovy and SQL*Plus can be used effectively together to script data-related functionality. Groovy offers many of the advantages of traditional scripting languages when used in conjunction with SQL*Plus, but offers the added advantage of enjoying full access to the JVM and the plethora of Java libraries. Groovy can be used with existing SQL*Plus scripts or can access the database directly with its powerful and convenient JDBC abstraction GroovySql.
My Personal Blog Policies
The recent posting of the The Oracle Social Media Participation Policy on Justin Kestelyn's (Oracle Technology Network Editor in Chief) blog has caused me to reevaluate my own personal and previously unwritten policies that affect how and what I write in this blog. In this post, I briefly look at the informal and unwritten policies that I have attempted to follow in writing posts for this blog and why these internally-motivated policies are important. Other developers who blog (and I think more should) may wish to similarly take an inventory of their own blogging practices.
Mixing of Personal and Technical
One of the main differences Justin calls out between Oracle's policy and Sun's policy is the mixing of personal with work-related content. In an update to the post, Justin points out that not all personal is discouraged. In many ways, my own post (though hosted on Google's Blogger rather than on any employer's infrastructure) follows the same advice. I do weave small personal details into my posts, but the focus of each post is always technically related.
The reason for this is that I have found that I prefer that approach as a reader of other peoples' blogs. I present in a way I most enjoy as an attendee at a presentation (fast-talking, content-rich presentations) and I write my blog in the way I prefer others' blogs (strongly focused on the technical subjects with just enough personal details to make the topic less dry, somewhat personable, and to provide informed opinion).
I tend to prefer it when other bloggers separate purely personal non-technical content from their technical content. The reason for this is that I often don't know these people personally and while their technical expertise and insight is valuable to me, I really don't care about their purely personal interests (especially in terms of politics). I don't want to wade through meaningless and uninteresting non-technical details to find the technical gems. I do "maintain" a personal blog with non-technical subjects, but it doesn't get the same attention from me as my technical blog.
Long-Term Blog Availability
One of the respondents to Justin's post asked about the future of his Sun blog. This is a reminder that one's blog can get removed at anytime. Hosting one's own blog obviously reduces the chances of anyone else removing it, but insufficient backups can mean problems even then. Hackers, hardware problems, and even user error could wipe out a blog whether it is hosted externally or on one's own. Very few things in life are guaranteed and online resource availability is certainly not (see the demise of GeoCities as an example). It is important to back up one's blogs. It helps if a blog has been reproduced on other hosts (which is sometimes the case), but even this can be difficult to piece back together.
I was very happy when Blogger announced the ability to export a blog and import a blog. That certainly contributes to the ability to move one's blog to another host if necessary or desired. The ability to do that is one of the reasons that I've stayed with Blogger.
In addition, I have my blog posts forwarded to multiple e-mail accounts as additional backup. The export/import is certainly easier if I ever need to actually do this in bulk, but the e-mail messages are automatic and are easily stored. In other words, the export/import can be more difficult to remember to do in a timely manner, but are easier to do if the time comes to export/import the blog in bulk.
In the worst case, there are other options for resurrecting a "lost blog" such as using Google Cache or the Internet Archive WayBack Machine. Reconstructing a blog from these two sources would be tedious and depends on the blog being archived/cached in these resources.
In summary on the topic of long-term blog availability, I continue to use Blogger and back it up occasionally using the export feature. I also have my posts forwarded to multiple e-mail accounts as soon as they are submitted. If Google ever stops supporting Blogger, I will hopefully have the ability to easily move my blog posts to another provider or, in the less optimal case, reconstruct them from my e-mail messages.
Watching What I Say
In my blog, I do endeavor to keep my criticisms as constructive as possible and I try not to allow emotion to override technical merit. Although I feel I do pretty well at this generally, I do better at it sometimes more than others. One of the blog posts that seemed to lead to Justin's post, Assimilation Begins...Oracle Censors Blogs.Sun.com, the blog author does seem to drop into an emotionally-charged state with significant use of hyperbole (though I love the use of the image of one of my favorite fictional villains [The Borg]).
The author uses words like "muck-mucks", "subjugate Sun culture" (cultural change seems to inevitably happen in mergers and acquisitions between very large companies), "Snoracle," "draconian rules," and "cultural imperialism." Normally, use of exaggeration and hyperbole undermines the credibility of a blog topic in my mind, but in this case, I attribute it more to the blog author's referenced personal connection to the creation of the blogs.sun.com site. It is easier to understand this emotion in that light, even if it seems a little overly dramatic from an outsider's perspective.
I acknowledge that I have said things and written things that are emotionally-driven and often overly critical, especially in e-mail and newsgroups. I try a little harder with blogs to not go that far and be more controlled in what I write.
I prefer blogs with opinions and facts as long as the opinions are informed opinions and are presented as opinion rather than fact. I also understand the importance of not revealing trade secrets regarding employers, clients, etc. and in not committing libel, slander, and the like. When one writes a blog post, the onus is on that person to think carefully about what he or she writes.
In general, I sometimes feel the "online frontier" is far too wild in terms of lack of civility and respect. I do try to keep my blog more positively focused and to talk about things I like or appreciate more often than my occasional posts about things I don't like or wish would be better. There is a place for constructive criticism and constant improvement, but it doesn't need to be rude and condescending.
Conclusion
I think it is important to have personal guidelines that shape what we say, do, and write. Having internal guidelines that I attempt to adhere to in writing this blog is an extension of that. It is my opinion that these guidelines benefit me and those who read one or more of my posts. It's time to back up my blog while I'm thinking about it.
Mixing of Personal and Technical
One of the main differences Justin calls out between Oracle's policy and Sun's policy is the mixing of personal with work-related content. In an update to the post, Justin points out that not all personal is discouraged. In many ways, my own post (though hosted on Google's Blogger rather than on any employer's infrastructure) follows the same advice. I do weave small personal details into my posts, but the focus of each post is always technically related.
The reason for this is that I have found that I prefer that approach as a reader of other peoples' blogs. I present in a way I most enjoy as an attendee at a presentation (fast-talking, content-rich presentations) and I write my blog in the way I prefer others' blogs (strongly focused on the technical subjects with just enough personal details to make the topic less dry, somewhat personable, and to provide informed opinion).
I tend to prefer it when other bloggers separate purely personal non-technical content from their technical content. The reason for this is that I often don't know these people personally and while their technical expertise and insight is valuable to me, I really don't care about their purely personal interests (especially in terms of politics). I don't want to wade through meaningless and uninteresting non-technical details to find the technical gems. I do "maintain" a personal blog with non-technical subjects, but it doesn't get the same attention from me as my technical blog.
Long-Term Blog Availability
One of the respondents to Justin's post asked about the future of his Sun blog. This is a reminder that one's blog can get removed at anytime. Hosting one's own blog obviously reduces the chances of anyone else removing it, but insufficient backups can mean problems even then. Hackers, hardware problems, and even user error could wipe out a blog whether it is hosted externally or on one's own. Very few things in life are guaranteed and online resource availability is certainly not (see the demise of GeoCities as an example). It is important to back up one's blogs. It helps if a blog has been reproduced on other hosts (which is sometimes the case), but even this can be difficult to piece back together.
I was very happy when Blogger announced the ability to export a blog and import a blog. That certainly contributes to the ability to move one's blog to another host if necessary or desired. The ability to do that is one of the reasons that I've stayed with Blogger.
In addition, I have my blog posts forwarded to multiple e-mail accounts as additional backup. The export/import is certainly easier if I ever need to actually do this in bulk, but the e-mail messages are automatic and are easily stored. In other words, the export/import can be more difficult to remember to do in a timely manner, but are easier to do if the time comes to export/import the blog in bulk.
In the worst case, there are other options for resurrecting a "lost blog" such as using Google Cache or the Internet Archive WayBack Machine. Reconstructing a blog from these two sources would be tedious and depends on the blog being archived/cached in these resources.
In summary on the topic of long-term blog availability, I continue to use Blogger and back it up occasionally using the export feature. I also have my posts forwarded to multiple e-mail accounts as soon as they are submitted. If Google ever stops supporting Blogger, I will hopefully have the ability to easily move my blog posts to another provider or, in the less optimal case, reconstruct them from my e-mail messages.
Watching What I Say
In my blog, I do endeavor to keep my criticisms as constructive as possible and I try not to allow emotion to override technical merit. Although I feel I do pretty well at this generally, I do better at it sometimes more than others. One of the blog posts that seemed to lead to Justin's post, Assimilation Begins...Oracle Censors Blogs.Sun.com, the blog author does seem to drop into an emotionally-charged state with significant use of hyperbole (though I love the use of the image of one of my favorite fictional villains [The Borg]).
The author uses words like "muck-mucks", "subjugate Sun culture" (cultural change seems to inevitably happen in mergers and acquisitions between very large companies), "Snoracle," "draconian rules," and "cultural imperialism." Normally, use of exaggeration and hyperbole undermines the credibility of a blog topic in my mind, but in this case, I attribute it more to the blog author's referenced personal connection to the creation of the blogs.sun.com site. It is easier to understand this emotion in that light, even if it seems a little overly dramatic from an outsider's perspective.
I acknowledge that I have said things and written things that are emotionally-driven and often overly critical, especially in e-mail and newsgroups. I try a little harder with blogs to not go that far and be more controlled in what I write.
I prefer blogs with opinions and facts as long as the opinions are informed opinions and are presented as opinion rather than fact. I also understand the importance of not revealing trade secrets regarding employers, clients, etc. and in not committing libel, slander, and the like. When one writes a blog post, the onus is on that person to think carefully about what he or she writes.
In general, I sometimes feel the "online frontier" is far too wild in terms of lack of civility and respect. I do try to keep my blog more positively focused and to talk about things I like or appreciate more often than my occasional posts about things I don't like or wish would be better. There is a place for constructive criticism and constant improvement, but it doesn't need to be rude and condescending.
Conclusion
I think it is important to have personal guidelines that shape what we say, do, and write. Having internal guidelines that I attempt to adhere to in writing this blog is an extension of that. It is my opinion that these guidelines benefit me and those who read one or more of my posts. It's time to back up my blog while I'm thinking about it.
Wednesday, January 27, 2010
Oracle/Sun: The Deal Has Closed
"Oracle has finalized the Sun transaction and the deal has closed."
The above statement can be found on the Sun and Oracle: Overview and Frequently Asked Questions for the Developer Community page. Besides this simple but definitive statement, the page includes some answers to some of the early questions Java developers might have about Java under Oracle. Questions asked cover subjects such as Sun's Java development network pages, Java.net, future of JavaOne, and more.
Today's Oracle+Sun Strategy presentation has been well-covered in the blogosphere (though the dominant topic on the blogsphere today was the announcement regarding the Apple iPad). There is also a web page devoted to Oracle + Sun Product Strategy Webcast Series.
If you are a Java developer and don't already have an Oracle Technology Network account, it might be a good time to consider getting one.
The official press release on Oracle's finalizing of the Sun acquisition is available here and includes the simple statement: "Oracle Corporation announced today that it had completed its acquisition of Sun Microsystems, Inc."
The above statement can be found on the Sun and Oracle: Overview and Frequently Asked Questions for the Developer Community page. Besides this simple but definitive statement, the page includes some answers to some of the early questions Java developers might have about Java under Oracle. Questions asked cover subjects such as Sun's Java development network pages, Java.net, future of JavaOne, and more.
Today's Oracle+Sun Strategy presentation has been well-covered in the blogosphere (though the dominant topic on the blogsphere today was the announcement regarding the Apple iPad). There is also a web page devoted to Oracle + Sun Product Strategy Webcast Series.
If you are a Java developer and don't already have an Oracle Technology Network account, it might be a good time to consider getting one.
The official press release on Oracle's finalizing of the Sun acquisition is available here and includes the simple statement: "Oracle Corporation announced today that it had completed its acquisition of Sun Microsystems, Inc."
Monday, January 25, 2010
GroovySql and MarkupBuilder: SQL-to-XML
I blogged previously on the ease of use of GroovySql, which is Groovy's easy-to-use JDBC-based data access approach. In another blog post, I covered the utility of OracleXMLQuery, a class which easily generates XML from an Oracle database query. This class makes it very easy, almost trivial, to convert SQL query results into XML, but a major disadvantage of OracleXMLQuery is that it is a proprietary solution. In this blog post, I demonstrate combining GroovySql with Groovy's MarkupBuilder to easily transform database data to XML.
The following script demonstrates the tremendous benefit achieved from the combination of GroovySql (and specifically the groovy.sql.Sql class) and MarkupBuilder. In this script's case, the MarkupBuilder is instantiated with its no-arguments constructor, implying that its markup output is to be written out via System.out.
When the above script is executed, XML is generated with elements featuring names provided above. A portion of the output is displayed next.
The names of these tags (
It is not always preferable to have the generated XML written to standard output. The next Groovy script is a slightly modified version of the above that writes the generated XML to a file whose filename is specified on the command line.
The script immediately above writes the generated XML out to a file with the name passed in as a command-argument. If there is no command-line argument provided, the generated XML is written to
The
Conclusion
GroovySql and MarkupBuilder are both simple to use while at the same time being highly useful. They are especially powerful together and provide a general approach to presenting database data as XML without proprietary classes specific to any one database vendor.
The following script demonstrates the tremendous benefit achieved from the combination of GroovySql (and specifically the groovy.sql.Sql class) and MarkupBuilder. In this script's case, the MarkupBuilder is instantiated with its no-arguments constructor, implying that its markup output is to be written out via System.out.
#!/usr/bin/env groovy
// Be sure to include appropriate JAR with OracleDataSource class on classpath
// (or use the appropriate datasource class if using different driver)
import groovy.sql.Sql
import java.sql.SQLRecoverableException
Sql sql = null;
try
{
sql = Sql.newInstance(
"jdbc:oracle:thin:@localhost:1521:orcl", "hr", "hr",
"oracle.jdbc.pool.OracleDataSource")
}
catch (SQLRecoverableException recoverableEx)
{
println "SQLRecoverableException: ${recoverableEx}"
println "\tError Code: ${recoverableEx.getErrorCode()}"
println "\tSQL Status Code: ${recoverableEx.getSQLState()}"
System.exit(-1)
}
import groovy.xml.MarkupBuilder
def xml = new MarkupBuilder()
xml.Employees
{
sql.eachRow("select employee_id, last_name, first_name, email, phone_number from employees") { row ->
Employee
{
Id(row.employee_id)
LastName(row.last_name)
FirstName(row.first_name)
Email(row.email)
Telephone(row.phone_number)
}
}
}
When the above script is executed, XML is generated with elements featuring names provided above. A portion of the output is displayed next.
<Employees>
<Employee>
<Id>198</Id>
<LastName>OConnell</LastName>
<FirstName>Donald</FirstName>
<Email>DOCONNEL</Email>
<Telephone>650.507.9833</Telephone>
</Employee>
<Employee>
<Id>199</Id>
<LastName>Grant</LastName>
<FirstName>Douglas</FirstName>
<Email>DGRANT</Email>
<Telephone>650.507.9844</Telephone>
</Employee>
. . .
<Employee>
<Id>196</Id>
<LastName>Walsh</LastName>
<FirstName>Alana</FirstName>
<Email>AWALSH</Email>
<Telephone>650.507.9811</Telephone>
</Employee>
<Employee>
<Id>197</Id>
<LastName>Feeney</LastName>
<FirstName>Kevin</FirstName>
<Email>KFEENEY</Email>
<Telephone>650.507.9822</Telephone>
</Employee>
</Employees>
The names of these tags (
Employees root tag, Employee row tag, and the individual employee elements) are exactly as specified in the Groovy code.It is not always preferable to have the generated XML written to standard output. The next Groovy script is a slightly modified version of the above that writes the generated XML to a file whose filename is specified on the command line.
#!/usr/bin/env groovy
// Be sure to include appropriate JAR with OracleDataSource class on classpath
// (or use the appropriate datasource class if using different driver)
import groovy.sql.Sql
import java.sql.SQLRecoverableException
Sql sql = null;
try
{
sql = Sql.newInstance(
"jdbc:oracle:thin:@localhost:1521:orcl", "hr", "hr",
"oracle.jdbc.pool.OracleDataSource")
}
catch (SQLRecoverableException recoverableEx)
{
println "SQLRecoverableException: ${recoverableEx}"
println "\tError Code: ${recoverableEx.getErrorCode()}"
println "\tSQL Status Code: ${recoverableEx.getSQLState()}"
System.exit(-1)
}
import groovy.xml.MarkupBuilder
MarkupBuilder xml
if (args.length < 1)
{
xml = new MarkupBuilder() // no-arguments constructor: System.out
}
else
{
xml = new MarkupBuilder(new PrintWriter(new File(args[0])))
}
xml.Employees
{
sql.eachRow("select employee_id, last_name, first_name, email, phone_number from employees") { row ->
Employee
{
Id(row.employee_id)
LastName(row.last_name)
FirstName(row.first_name)
Email(row.email)
Telephone(row.phone_number)
}
}
}
The script immediately above writes the generated XML out to a file with the name passed in as a command-argument. If there is no command-line argument provided, the generated XML is written to
System.out as was done in the first script.The
MarkupBuilder class allows the developer to specify customization beyond where the generated XML is written. Two such methods are MarkupBuilder.setOmitEmptyAttributes(boolean) and MarkupBuilder.setOmitNullAttributes(boolean). These specify whether null and empty values in the database should be written to the generated XML.Conclusion
GroovySql and MarkupBuilder are both simple to use while at the same time being highly useful. They are especially powerful together and provide a general approach to presenting database data as XML without proprietary classes specific to any one database vendor.
Subscribe to:
Posts (Atom)