Saturday, February 13, 2010

JAX-RS 1.1: What's New?

Srini Penchikala's InfoQ article Java EE 6 Web Services: JAX-RS 1.1 Provides Annotation Based REST Support provides a concise (fits on one printed page!) high-level overview of JAX-RS 1.1, some of the commonly used JAX-RS annotations, and some common implementations of JAX-RS 1.1. It's definitely worth the short read for anyone just starting to learn about JAX-RS. JAX-RS 1.x has been developed under the Java Community Process via JSR 311 ("JSR 311: JAX-RS: The Java API for RESTful Web Services").

The JAX-RS 1.1 changelog documents changes from JAX-RS 1.0 to 1.1. One of the most significant characteristics of JAX-RS 1.1 is that it is part of Java EE 6 and so multiple items related to inclusion with Java EE 6 are included. Several minor, but appreciated, enhancements surround adding examples to, clarifying, and correcting Javadoc comments related to use of JAX-RS.

One of the interesting JAX-RS 1.1 enhancements is changing the enum Response.Status to implement an interface called Response.StatusType. This is interesting from an intellectual or conceptual perspective that it illustrates the usefulness of having a Java enum implement an interface. In this case, this allows JAX-RS 1.1 to get around limitations associated with not being able to extend an enum. In particular (and this is where this is interesting from a practical perspective), this allows developers to express their own custom response status types by custom enum.

The new @ApplicationPath annotation can be used on a custom extension of Application to specify the base URI for all paths specified with the @Path annotation. This is similar to specifying this in the web.xml file and, in fact, the any setting in the web.xml file still takes precedence over the @ApplicationPath value. I generally prefer this strategy (used also in Java Persistence API) of having external XML/configuration be able to override/supersede in-code annotations.

It is not unusual to have a version 1.1 of a specification focus on improvements to the 1.0 version found during "real life" application of that specification. JAX-RS 1.1 is no exception and has several changes related to improving JAX-RS by making standard supported behavior less ambiguous. For example, JAX-RS 1.1 requires implementations to use application/octet-stream as the default Content-Type. JAX-RS 1.1 also requires compliant implementations to support a static fromString method in a type describing a resource model parameter.

There are several other goodies added to JAX-RS 1.1 and these can be seen in the previously mentioned changelog. Markus Karg's blog post "WebDAV Support for JAX-RS" Release 1.1 discusses how changes in JAX-RS 1.1 enabled him to simplify WebDAV support for JAX-RS.

The article that I referenced at the beginning of this post (Java EE 6 Web Services: JAX-RS 1.1 Provides Annotation Based REST Support) outlines the versions of several JAX-RS implementations that support JAX-RS 1.1. These implementations of JAX-RS 1.1 are Jersey 1.1.5, Apache CXF 2.3, and RESTEasy. Other JAX-RS implementations include Restlet and Apache Wink (incubator).

JAX-RS 1.1 introduces some nice changes to JAX-RS that one normally expects when going from a 1.0 to a 1.1. Not the least of these advantages is its inclusion in Java EE 6.

Wednesday, February 10, 2010

Java: "duplicate class" and Mismatched File Name Error

Developers who are new to Java can sometimes have trouble with class and package naming. In fact, the introductory Java forums are filled with threads starting with questions about these areas of Java. In this blog post, I look at some of these errors and some of the causes of these errors.

One of the more obvious errors occurs when a public Java class is named differently than the file that contains the class definition.

This is demonstrated in the next screen snapshot. In this example, a class was declared as public with the name Person, but was saved in a file called Person2.java. The error message is pretty explicit: "class Person is public, should be declared in a file named Person.java"



The "duplicate class" error can sometimes be a little more tricky to resolve. One situation in which this occurs is when two source code directories include the same class with the same package structure. This error looks like that shown in the next screen snapshot.



As the above screen snapshot indicates, the class dustin.examples.Person exists in both the src2 directory and (not shown here) in the src directory ("duplicate class: dustin.examples.Person"). Indeed, these are duplicate classes, at least in terms of package and class name.

The "duplicate class" error can also occur when the class is named the same with the same package naming hierarchy, even if one of the classes exists in a directory structure with directory names different than the package names. This is shown in the next screen snapshot.



This screen snapshot demonstrates that the "duplicate error" occurs when the class names and declared package names match even if the source file exists in directories with different names (different even than the declared package structure). What this implies is that if a particular class was copied to another directory without changing the package statement and the new directory was in the source path for javac, the "duplicate class" error will occur.

Another interesting observation from the last example is that javac uses the package statement to decide where to build the .class file rather than using the source file's location in a directory structure.

In the blog post Java duplicate class error, Morgy describes a situation in which he ran into the "duplicate class" error message (he had neglected to place the class's package declaration at the top of the class).

Conclusion

In this post, I have attempted to show some common causes of errors that can be troublesome for those new to Java. Specifically, I have demonstrated some of the common causes of the "duplicate class" error and of the public class not matching its file name.

Tuesday, February 9, 2010

JAX-RS with Jersey: An Introduction

The JAX-RS (JSR 311: The Java API for RESTful Web Services) specification provides a standardized Java-based approach to implementing REST-style web services. Jersey is the reference implementation of JAX-RS and I provide a brief introduction to JAX-RS via Jersey in this blog post.

Although Jersey doesn't require the use of GlassFish, I use Jersey in conjunction with GlassFish v3 in this post. GlassFish v3 provides the reference implementation for Java EE 6. I download the GlassFish v3 Windows Installer and ran it for installation. After its installation, I set the environment variable GLASSFISH_HOME to point to the installation's root directory and added %GLASSFISH_HOME% to my PATH. GlassFish can then be run with the command asadmin start-domain (starts the default domain) as demonstrated in the next screen snapshot.



Because I used the default settings during my installation of GlassFish, the web-based administrative console is available on my machine at the URI http://localhost:4848/ (default port is 4848). With GlassFish running, this URI leads to the login page for the Administrative Console. The administrative username and password were specified in the installation. This screen is shown in the next screen snapshot.




With GlassFish set up, I now move onto developing a very simple REST application using Jersey. I start with a properly JAX-RS-annotated class called MovieOfTheDay:

MovieOfTheDay.java

package rmoug.td2010.rest;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.ws.rs.GET;

import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;

/**
* Simple class that provides a movie for the provided month and day of that
* month.
*/
@Path("/movies")
public class MovieOfTheDay
{
private static final Logger LOGGER = Logger.getLogger("rmoug.td2010.rest.MovieOfTheDay");

private static final Map<Integer, Map<Integer, String>> MOVIE_OF_THE_DAY;

static
{
MOVIE_OF_THE_DAY = new HashMap<Integer, Map<Integer, String>>();

final Map<Integer, String> janMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JANUARY), janMovies);

final Map<Integer, String> febMovies = new HashMap<Integer, String>();
febMovies.put(2, "Groundhog Day");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.FEBRUARY), febMovies);

final Map<Integer, String> marMovies = new HashMap<Integer, String>();
marMovies.put(16, "The Fugitive");
marMovies.put(17, "Darby O'Gill and the Little People");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.MARCH), marMovies);

final Map<Integer, String> aprMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.APRIL), aprMovies);

final Map<Integer, String> mayMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.MAY), mayMovies);

final Map<Integer, String> junMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JUNE), junMovies);

final Map<Integer, String> julMovies = new HashMap<Integer, String>();
julMovies.put(4, "Independence Day");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JULY), julMovies);

final Map<Integer, String> augMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.AUGUST), augMovies);

final Map<Integer, String> sepMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.SEPTEMBER), sepMovies);

final Map<Integer, String> octMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.OCTOBER), octMovies);

final Map<Integer, String> novMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.NOVEMBER), novMovies);

final Map<Integer, String> decMovies = new HashMap<Integer, String>();
decMovies.put(24, "It's A Wonderful Life");
decMovies.put(25, "A Christmas Carol");
decMovies.put(26, "A Christmas Story");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.DECEMBER), decMovies);
}

@GET
@Path("/")
@Produces("text/plain")
public String getMovie()
{
return
"To see the movie of the day, provide URL with month and day: "
+ "\thttp://localhost:8080/rest/resources/movies/<<month>>/<<day>>";
}

/**
* Obtain the movie of the day as indicated by the provided month and date.
*
* @param month Month for which movie of the day is desired.
* @param date Date for which movie of the day is desired.
* @return Title of the movie of the day for provided month and date.
*/
@GET
@Path("/{month}/{date}")
@Consumes("text/plain")
@Produces("text/html")
public String getMovieOfTheDay(
@PathParam("month") final Integer month,
@PathParam("date") final Integer date)
{
final Map<Integer, String> moviesOfTheMonth =
MOVIE_OF_THE_DAY.get(month-1);
final String movieOfTheDay = moviesOfTheMonth != null
? moviesOfTheMonth.get(date)
: "Fletch";
return movieOfTheDay != null
? generateHtml(movieOfTheDay, month, date)
: generateHtml("Fletch Lives!", month, date);
}

private String generateHtml(
final String movieTitle, final int movieMonth, final int movieDay)
{
final StringBuilder builder = new StringBuilder();
builder.append("<html>")
.append("<head><title>Movie of the Day</title></head>")
.append("<body><span style='font-weight: bold; font-size: 125%;")
.append("text-align: center;'>Movie of the Day</span>")
.append("<p>The movie of the day for ")
.append(movieMonth)
.append("/")
.append(movieDay)
.append(" is '")
.append(movieTitle)
.append("'.</p></body></html>");
return builder.toString();
}
}


The static initialization block is not specific to JAX-RS, but is instead used to simulate a database. In a real REST application, I'd almost certainly have a database on the back-end, but the in-memory static map simulates that here.

Although simple, the above class demonstrates key JAX-RS features. The most interesting JAX-RS pieces of the class are the JAX-RS annotations such as @Path, @GET, @Consumes, @Produces, and @PathParam. I won't delve into what these JAX-RS annotations do in this post because my emphasis is on using Jersey. See the Java EE 6 Tutorial chapter on REST with Jersey for more background on these annotations.

I will deploy the JAX-RS-annotated class to GlassFish in a WAR file with the appropriate web.xml file as shown next:

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>ServletAdaptor</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>ServletAdaptor</servlet-name>
<url-pattern>/resources/*</url-pattern>
</servlet-mapping>

<session-config>
<session-timeout>
30
</session-timeout>
</session-config>

</web-app>


In my case, NetBeans 6.8 generated this web.xml file for me automatically when I added the appropriate JAX-RS and Jersey JAR files to my project's libraries. This is a relatively simple web.xml file because of GlassFish's awareness of JAX-RS. (A strikingly similar web.xml works for deploying Jersey-based REST applications to Tomcat as demonstrated in Jason Drake's blog post Deploying Jersey in Tomcat 6.0.)

For my example, a WAR file called Rest1.war is generated. Its contents are displayed in the next screen snapshot.



As the screen snapshot indicates, there are JAX-RS and Jersey JAR files included in the generated WAR file. The class MovieRestApplication can be ignored because it is not used with Jersey on GlassFish. This means that the only custom files in the WAR are the JAX-RS-annotated class MovieOfTheDay, the web.xml file, and the index page (index.jsp). The content of the index.jsp page is shown next.

index.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>REST with JAX-RS Example</title>
</head>
<body>
<h1>REST Can be Easy!!!</h1>
</body>
</html>


The next screen snapshot demonstrates deployment of the generated WAR file via the web-based Glass Administrative Console:



The most important detail to note from the image of the deployment of the WAR file is that I have named the context root "rest." This will be part of the URIs by which my deployed REST services are accessed. The earlier web.xml file also showed that resources/ will be part of this REST service access URI as well. The remainder of the appropriate URI is based on the pieces of URI provided in the JAX-RS annotations on the Java class (/movies, /, and /{month}/{date}). The portions of URI denoted with curly braces indicate that the placeholders will be injected with values from the JAX-RS implementation that are in the calling URI. For example, if the relevant piece of URI was /7/4, this would indicate, in this case, a month of 7 (July because not using Java's zero-based month index in the URI) and a day of 4.

When deployment is successful, the administrative console appears as shown in the next screen snapshot.



With the JAX-RS application deployed, I can now access it from a myriad of different clients. JAX-RS does not spell out a standardized approach for clients, but Jersey and most other popular JAX-RS implementations provide their own approach for building clients. Other HTTP/REST clients are also available such as RESTClient. For now, I'll simply use a web browser.

Placing the URI http://localhost:8080/ in my browser displays the main page indicating that GlassFish is running:



If I add the web context (rest) to the URI, I see my index.jsp page:



To access the JAX-RS-powered REST applications, I need to add the resources portion of the URI as specified in the web.xml file. When I add this plus the /movies portion (as specified in the @Path annotation), I see the following page.



The above screen snapshot indicates that the GET access was invoked with the path "/" and the getMovie method was called. At this point, I can add a month and date to the URI to get a movie for that specific day. The next two screen snapshots demonstrate this for Groundhog Day and for Christmas Day.





As the above screen snapshots demonstrate, the months and days provided in the URIs are automatically injected by the JAX-RS provider into the parameters to the appropriate method. Now that's easy!


Conclusion

The process of deploying a JAX-RS-based web service using Jersey and GlassFish is relatively straightforward. All I really needed was access to the JAX-RS and Jersey JARs, the properly annotated Java class, and the short web.xml file that allowed Jersey to be used as a servlet. This blog post has attempted to show the basic steps involved with writing a simple JAX-RS-annotated class, deploying it to GlassFish, and taking advantage of Jersey's implementation of JAX-RS.


Other Resources

⇒ RESTful Web Services Developer's Guide

⇒ RESTful Java, Some Links

⇒ JSR 311: A Java API for RESTful Web Services?

⇒ Deploying and Testing a Jersey Application without NetBeans

⇒ Jersey 1.0: Getting Started

⇒ JSR-311 Javadoc-based API

Sunday, February 7, 2010

The Rise of Google Chrome

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.

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)

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 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 Methods

The 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 Only

The 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 Methods

It 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 .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.