Saturday, November 1, 2008

Solipse on Parallels

Solipse has recently been updated for the latest stable version of Eclipse Ganymede (3.4.1).
One quick note on this update:
Recently, I acquired a Mac and installed Parallels with a trial license. Installation of SXCE b91 went smoothly, except a few of the default settings needed tweaking, i.e., the memory must be at least 768MB (I bumped mine to 1024 MB) or else there are Out of Memory errors, and the network adapter must be Bridged Ethernet to connect over WiFi.
Solaris Express on Mac Parallels

Opera is the first piece of software I always install. After that, I decided to try the eclipse solaris-gtk-x86 build since the latest version supported by the solipse script was out-of-date, because a user had reported a bug in our provisioning of the SDK Profile, because I wanted to see if Eclipse would be affected at all by the changes in JDK 6u10 and because I wanted to see how well Parallels would manage with such a memory intensive process.
Downloading and installing the pre-requisite software (SunStudio 11, which I have on a local share, JDKs 1.4, 5 and 6 and Apache Ant 1.7.1, which I prefer to the default 1.6.5 installation in SXCE) went as easily as possible.
My first attempt at a 3.4.1 build did not go quite as well as I had hoped. Eventually I ended up reverting to 3.4.0 because the provisioning bug was more pressing than the version upgrade. The build time decreased by half compared to my other SXCE installation (on a dual processor P3 at 1GHz per chip with 2 GB RAM). The time went from 75 minutes to 44.
After successfully fixing the p2 bug in the script and building and provisioning 3.4.0 with JDK 6u10, my final task was to figure out what had changed in the Eclipse build between 3.4.0 and 3.4.1. Eventually I found the culprit -- a modified classpath in the org.eclipse.osgi plug-in build.xml. Yet another find-and-replace went into the solipse script along with a few more parameters for versioning information, and soon the build was humming to a happy completion.
Solaris Express on Mac Parallels

My trial period for Parallels has not yet expired! The savings in build time alone will easily make the cost of a license worthwhile....

Friday, October 31, 2008

Spring, Hibernate and Oracle Stored Procedures

Motivation: While there are a few resources available online for calling stored procedures from Hibernate, it took me a while to stumble across one that mostly captures what I need. The intention of this blog entry is to put a similar example into my own words, to extend it slightly and hopefully to help anyone not experienced with Hibernate and Oracle to integrate Stored Procedures and Functions into an application quickly.

Setup Oracle 10g


For this example, we will be using Oracle 10g. We can initialize our schema user with SQLPlus with the following commands:
  • sqlplus connect as sysdba
  • create user my_orcl identified by my_orcl;
  • grant create session to my_orcl;
  • grant resource to my_orcl;
  • grant create table to my_orcl;


Setup a Project For Spring and Hibernate


We will download spring-framework-2.5.5-with-dependencies.zip, hibernate-distribution-3.3.1.GA-dist.zip and hibernate-annotations-3.4.0.GA.zip. We can create a standard project layout of src, test and lib folders with the following jars on the classpath:

  • spring-framework-2.5.5/dist/spring.jar
  • spring-framework-2.5.5/dist/modules/spring-test.jar
  • spring-framework-2.5.5/lib/jakarta-commons/commons-logging.jar
  • spring-framework-2.5.5/lib/jakarta-commons/commons-dbcp.jar
  • spring-framework-2.5.5/lib/jakarta-commons/commons-pool.jar
  • spring-framework-2.5.5/lib/jakarta-commons/commons-collections.jar
  • spring-framework-2.5.5/lib/dom4j/dom4j-1.6.1.jar
  • spring-framework-2.5.5/lib/log4j/log4j-1.2.15.jar
  • spring-framework-2.5.5/lib/slf4j/slf4j-api-1.5.0.jar
  • spring-framework-2.5.5/lib/slf4j/slf4j-log4j12-1.5.0.jar
  • spring-framework-2.5.5/lib/j2ee/*.jar
  • hibernate-annotations-3.4.0.GA/hibernate-annotations.jar
  • hibernate-annotations-3.4.0.GA/lib/hibernate-commons-annotations.jar
  • hibernate-distribution-3.3.1.GA/hibernate3.jar
  • hibernate-distribution-3.3.1.GA/lib/required/javassist-3.4.GA.jar
  • hibernate-distribution-3.3.1.GA/lib/required/slf4j-api-1.5.2.jar


Because we will be using Oracle Stored Procedures, we will also need a database driver such as

  • oracle/product/10.2.0/db_1/jdbc/lib/ojdbc14.jar


Create Domain Objects


We can setup our domain using annotated Java. For these examples, we need one simple domain Object.


package spring.hibernate.oracle.stored.procedures.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "AUTHOR", schema = "MY_ORCL")
public class Author implements java.io.Serializable {

private static final long serialVersionUID = 8676058601610931698L;
private int id;
private String firstName;
private String lastName;

@Id
@Column(name = "ID", nullable = false)
public int getId() {
return this.id;
}

public void setId(final int id) {
this.id = id;
}

@Column(name = "FIRST_NAME", nullable = false, length = 50)
public String getFirstName() {
return this.firstName;
}

public void setFirstName(final String firstName) {
this.firstName = firstName;
}

@Column(name = "LAST_NAME", nullable = false, length = 50)
public String getLastName() {
return this.lastName;
}

public void setLastName(final String lastName) {
this.lastName = lastName;
}
}



Create a DAO


Now that we have a domain Object, we can create a DAO for a simple operation, such as looking up Authors by last name. Fortunately, Spring provides a convenient base class for DAO operations.


package spring.hibernate.oracle.stored.procedures.dao;

import java.util.List;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import spring.hibernate.oracle.stored.procedures.domain.Author;

public class AuthorDAO extends HibernateDaoSupport {

@SuppressWarnings("unchecked")
public List<Author> findByLastNameUsingHQL(final String lastName) {
return getHibernateTemplate().find("from Author author where author.lastName = ?", lastName);
}
}



The Spring Application Context Configuration


The Spring applicationContext.xml can reside directly at the root of our src classpath, and it will contain information for configuring Spring to manage our Hibernate sessions, transactions and datasources, as well as our DAO.


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"
default-autowire="constructor">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" />
<property name="username" value="my_orcl" />
<property name="password" value="my_orcl" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:/hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean name="authorDAO"
class="spring.hibernate.oracle.stored.procedures.dao.AuthorDAO">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>



The Hibernate Configuration


The hibernate.cfg.xml can also reside directly in our src/ folder. The primary purpose of this file is to let Hibernate know about our domain class.


<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<mapping class="spring.hibernate.oracle.stored.procedures.domain.Author" />
</session-factory>
</hibernate-configuration>



Testing the Setup


Now that our project is setup, we can write a simple test to very that all of our configuration files can be properly loaded and that all of our connections work. We will begin by setting up some simple test data in the database, and then we can call our DAO method to find Authors by their last names. Again, we can take advantage of a convenient Spring base class for our test cases.


package spring.hibernate.oracle.stored.procedures.dao;

import org.junit.Test;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;

public class AuthorDAOTest extends AbstractTransactionalDataSourceSpringContextTests {

private AuthorDAO authorDAO;

@Override
protected void onSetUp() throws Exception {
super.onSetUp();
createAuthor(1, "Jules", "Verne");
createAuthor(2, "Charles", "Dickens");
createAuthor(3, "Emily", "Dickinson");
createAuthor(4, "Henry", "James");
createAuthor(5, "William", "James");
createAuthor(6, "Henry", "Thoreau");
}

@Test
public void testFindByLastNameUsingHQL() throws Exception {
assertEquals(2, getAuthorDAO().findByLastNameUsingHQL("James").size());
assertEquals(1, getAuthorDAO().findByLastNameUsingHQL("Verne").size());
assertEquals(1, getAuthorDAO().findByLastNameUsingHQL("Dickinson").size());
assertEquals(1, getAuthorDAO().findByLastNameUsingHQL("Dickens").size());
assertEquals(0, getAuthorDAO().findByLastNameUsingHQL("Whitman").size());
}

@Override
protected String[] getConfigLocations() {
return new String[] { "applicationContext.xml" };
}

public AuthorDAO getAuthorDAO() {
return authorDAO;
}

public void setAuthorDAO(final AuthorDAO authorDAO) {
this.authorDAO = authorDAO;
}

private void createAuthor(final int id, final String firstName, final String lastName) {
jdbcTemplate.execute(String.format("insert into author (id, first_name, last_name) values (%d, '%s', '%s')", id,
firstName, lastName));
}
}



Write a Stored Procedure


Because we have specified <prop key="hibernate.hbm2ddl.auto">create</prop> in our Spring configuration for Hibernate, the AUTHOR table now exists in the database. We can write a simple stored procedure to query this table.


CREATE OR REPLACE PROCEDURE findByLastName
( res OUT SYS_REFCURSOR,
vLastName IN author.last_name%type ) AS
BEGIN
OPEN res FOR
SELECT * FROM author WHERE last_name = vLastName;
END findByLastName;



Call The Stored Procedure From Hibernate


Now that we have a PL/SQL Stored Procedure, we will need a way to reference it from Hibernate. We can annotate the domain Object with such a named query.


....
@Entity
@org.hibernate.annotations.NamedNativeQuery(name = "findByLastName", query = "call findByLastName(?, :vLastName)", callable = true, resultClass = Author.class)
@Table(name = "AUTHOR", schema = "MY_ORCL")
public class Author implements java.io.Serializable {
....



Now we can add a method to our AuthorDAO for calling this Stored Procedure.


....
@SuppressWarnings("unchecked")
public List<Author> findByLastNameUsingStoredProcedure(final String lastName) {
return (List<Author>) getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(final Session session) throws HibernateException, SQLException {
return session.getNamedQuery("findByLastName") //
.setParameter("vLastName", lastName) //
.list();
}
});
}
....



Finally, we will add a test case for calling the new DAO method.


....
@Test
public void testFindByLastNameUsingStoredProcedure() throws Exception {
assertEquals(2, getAuthorDAO().findByLastNameUsingStoredProcedure("James").size());
assertEquals(1, getAuthorDAO().findByLastNameUsingStoredProcedure("Verne").size());
assertEquals(1, getAuthorDAO().findByLastNameUsingStoredProcedure("Dickinson").size());
assertEquals(1, getAuthorDAO().findByLastNameUsingStoredProcedure("Dickens").size());
assertEquals(0, getAuthorDAO().findByLastNameUsingStoredProcedure("Whitman").size());
}
....



Write a PL/SQL Function


We can similarly call an Oracle Function. Here, we will use a function that locates Authors by their first names.


CREATE OR REPLACE FUNCTION findByFirstName
( vFirstName IN author.first_name%type )
RETURN SYS_REFCURSOR AS
res SYS_REFCURSOR;
BEGIN
OPEN res FOR
SELECT * FROM author WHERE first_name = vFirstName;
RETURN res;
END findByFirstName;



Call The Function From Hibernate


We can reference this function using an org.hibernate.annotations.NamedNativeQuery, but we can also make the scenario a little more interesting by instead using the javax.persistence.NamedNativeQuery annotation in conjunction with the javax.persistence.QueryHint annotation. By using this annotation, note how we have two named queries declared on a single domain Object. Also note the braces that are necessary for the query syntax of the function but are not necessary for the stored procedure call.


....
@Entity
@org.hibernate.annotations.NamedNativeQuery(name = "findByLastName", query = "call findByLastName(?, :vLastName)", callable = true, resultClass = Author.class)
@javax.persistence.NamedNativeQuery(name = "findByFirstName", query = "{ ? = call findByFirstName(:vFirstName) }", resultClass = Author.class, hints = { @javax.persistence.QueryHint(name = "org.hibernate.callable", value = "true") })
@Table(name = "AUTHOR", schema = "MY_ORCL")
public class Author implements java.io.Serializable {
....



Again, we will access this PL/SQL function through our DAO.


....
@SuppressWarnings("unchecked")
public List<Author> findByFirstNameUsingFunction(final String firstName) {
return (List<Author>) getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(final Session session) throws HibernateException, SQLException {
return session.getNamedQuery("findByFirstName") //
.setParameter("vFirstName", firstName) //
.list();
}
});
}
....



And finally, we can add another simple test for this function call.


....
@Test
public void testFindByFirstNameUsingFunction() throws Exception {
assertEquals(0, getAuthorDAO().findByFirstNameUsingFunction("James").size());
assertEquals(2, getAuthorDAO().findByFirstNameUsingFunction("Henry").size());
}
....



Hopefully, this step-by-step process gives a good starting point for creating more complex stored procedure and function calls using Spring, Hibernate and Oracle.

Next: Hibernate updates and stored procedures ->

Saturday, September 6, 2008

Spring, Hibernate and JAX-WS WebServices on Weblogic

Purpose


This post concentrates the information presented in the Spring MVC Tutorial and the Spring WebServices Tutorial and adds information specific to configuring an application for Weblogic. The goal is to get a working application running from end-to-end (jsp or webservice -> hibernate -> database) very quickly.

Development and Deployment Environments


The development environment assumed for the tutorial is Eclipse 3.4 with Ant 1.7.1 for building. For Continuous Integration, I recommend Hudson. There is nothing specific about those components and versions, just that they are a familiar development environment for me.
For deployment, we will be using Spring 2.5.5 with Hibernate Core 3.3 and Hibernate Annotations 3.4 on Weblogic Server 10.3. For the database, we can use the HSQLDB 1.8 that comes with the Spring 2.5 distribution with dependencies.
For this project also, we will need to setup a Weblogic domain for deployment. Here, we can use a domain called spring.hibernate.webservices and we can use all the default settings (e.g., Sun JDK, port 7001, Development configuration).

Initial Project Setup


We will setup the project as described in the Spring Tutorial.
For this, we can name the project spring.hibernate.webservices.weblogic.
We can create the following directories:
src directory for any Java source files.
war directory for the index file.
war/WEB-INF directory for the web.xml file.
war/WEB-INF/jsp directory for the jsp files.
war/WEB-INF/lib directory for library dependencies.
war/WEB-INF/classes directory for compiled classes.
We can create the initial web.xml as described in the tutorial.


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" >

<servlet>
<servlet-name>spring.hibernate.webservices.weblogic</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>spring.hibernate.webservices.weblogic</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>
index.jsp
</welcome-file>
</welcome-file-list>

</web-app>


We will also create a build.xml file in the root of the project. The Spring tutorial uses tasks specific to Tomcat. Here, we can setup tasks specific to the Weblogic domain we have created without explicitly setting up the Weblogic domain environment.


<?xml version="1.0"?>
<project name="spring.hibernate.webservices.weblogic" basedir="." default="deploy">

<property name="src.dir" value="src" />
<property name="web.dir" value="war" />
<property name="dist.dir" value="dist" />
<property name="build.dir" value="${web.dir}/WEB-INF/classes" />
<property name="name" value="spring.hibernate.webservices.weblogic" />

<path id="master-classpath">
<fileset dir="${web.dir}/WEB-INF/lib">
<include name="*.jar" />
</fileset>
<pathelement path="${build.dir}" />
</path>

<target name="init">
<mkdir dir="${build.dir}" />
<mkdir dir="${dist.dir}" />
</target>

<target name="build" depends="init" description="Compile main source tree java files">
<javac destdir="${build.dir}" source="1.5" target="1.5" debug="true" deprecation="false" optimize="false" failonerror="true">
<src path="${src.dir}" />
<classpath refid="master-classpath" />
<classpath refid="weblogic-classpath" />
</javac>
</target>

<target name="build-war" depends="build" description="Packages the war file">
<war destfile="${dist.dir}/${name}.war" webxml="${web.dir}/WEB-INF/web.xml">
<fileset dir="${web.dir}">
<include name="**/*.*" />
</fileset>
</war>
</target>

<!-- ============================================================== -->
<!-- Start Weblogic Tasks -->
<!-- ============================================================== -->

<property name="weblogic.home" location="C:/Tim/WebServers/bea" />
<property name="weblogic.domain" location="${weblogic.home}/user_projects/domains/spring_webservices" />

<path id="weblogic-classpath">
<fileset dir="${weblogic.home}/wlserver_10.3/server/lib">
<include name="*.jar" />
</fileset>
</path>

<macrodef name="run-domain-task">
<attribute name="task.name" />
<sequential>
<property name="runner" location="${weblogic.domain}/bin/run_domain_task.cmd" />
<copy file="${weblogic.domain}/bin/setDomainEnv.cmd" tofile="${runner}" overwrite="true" />
<concat append="true" destfile="${runner}">ant -f "${ant.file}" @{task.name}</concat>
<exec executable="${runner}" />
</sequential>
</macrodef>

<taskdef name="wldeploy" classname="weblogic.ant.taskdefs.management.WLDeploy">
<classpath refid="weblogic-classpath" />
</taskdef>

<target name="deploy-to-domain">
<property name="weblogic.username" value="weblogic"></property>
<property name="weblogic.password" value="weblogic"></property>
<property name="weblogic.adminurl" value="t3://localhost:7001"></property>
<property name="weblogic.targets" value="AdminServer"></property>
<wldeploy action="deploy" verbose="true" debug="true" name="${name}" source="${dist.dir}/${name}.war" user="${weblogic.username}" password="${weblogic.password}" adminurl="${weblogic.adminurl}" targets="${weblogic.targets}" />
</target>

<target name="deploy" depends="build-war" description="Deploys the project .war file">
<run-domain-task task.name="deploy-to-domain" />
</target>

<!-- End Weblogic tasks -->

</project>


If we start the Weblogic domain, deploy this script and open a browser with the URL http://localhost:7001/spring.hibernate.webservices.weblogic/, we should now see our hello world application.

Setup Spring MVC


Setting up the initial Spring libraries, controller and jsp should be exactly the same as described in the tutorial. There is nothing specific to Weblogic here.
As the tutorial indicates, we will copy spring.jar, spring-webmvc.jar, commons-logging.jar, standard.jar and jstl.jar to our WEB-INF/lib directory, and we will add these to the project build path.
Please note also that, in Eclipse, we will want to create a new user library that includes all the .jar files in <weblogic_home>/wlserver_10.3/server/lib and all the glassfish*.jar and javax*.jar files in <weblogic_home>/modules and we will want to add this to the build path.
We can also declare a servlet named spring.hibernate.webservices.weblogic and its servlet-mapping in the web.xml file, and we can add the Spring configuration file spring.hibernate.webservices.weblogic-servlet.xml to the WEB-INF directory.
To make the example slightly more interesting, instead of simply displaying a static hello world page, we will autowire a stub DAO and display the information it returns.
Suppose we have a simple domain object Person:


package spring.hibernate.webservices.weblogic.domain;

public class Person {

private final String firstName;
private final String lastName;

public Person(final String firstName, final String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

public String getFirstName() {
return firstName;
}

public String getLastName() {
return lastName;
}
}



Suppose we have a stub PersonDAO that returns a collection of Person objects.


package spring.hibernate.webservices.weblogic.dao;

import java.util.Arrays;
import java.util.Collection;
import spring.hibernate.webservices.weblogic.domain.Person;

public class PersonDAO {
public Collection findAll() {
return Arrays.asList(new Person("John", "Smith"), new Person("Jane", "Doe"), new Person("Jim", "Jones"));
}
}



Our HelloController will be autowired through the constructor with the PersonDAO and will put the stub People into the Model:


package spring.hibernate.webservices.weblogic;

import java.io.IOException;
import java.util.HashMap;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import spring.hibernate.webservices.weblogic.dao.PersonDAO;

public class HelloController extends SpringBeanAutowiringSupport implements Controller {

private final PersonDAO personDAO;

@Autowired
public HelloController(final PersonDAO personDAO) {
this.personDAO = personDAO;
}

public ModelAndView handleRequest(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException {
final HashMap<String, Object> model = new HashMap<String, Object>();
model.put("people", personDAO.findAll());
return new ModelAndView("hello", "model", model);
}
}



As described in the tutorial, we can create a header for including standard jstl tags called WEB-INF/include.jsp:


<%@ page session="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>



We can modify our index page to point to the new hello page we will create:


<%@ include file="/WEB-INF/jsp/include.jsp" %>
<c:redirect url="/hello.htm"/>



We can create a WEB-INF/hello.jsp that displays the information from our DAO:


<%@ include file="/WEB-INF/jsp/include.jsp" %>
<html>
<head><title>Hello Spring on Weblogic</title></head>
<body>
<h1>Hello Spring on Weblogic</h1>
<h3>People</h3>
<TABLE>
<TR>
<TH>First Name</TH>
<TH>Last Name</TH>
</TR>
<c:forEach items="${model.people}" var="person">
<TR>
<TD><c:out value="${person.firstName}"/></TD>
<TD><c:out value="${person.lastName}"/></TD>
</TR>
</c:forEach>
</TABLE>
</body>
</html>



Finally, we can add content to the spring.hibernate.webservices.weblogic-servlet.xml spring configuration file to declare not only that Spring will manage the HelloController but also the PersonDAO and that default auto-wiring should occur through the constructor:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"
default-autowire="constructor">
<bean name="/hello.htm" class="spring.hibernate.webservices.weblogic.HelloController" />
<bean name="personDAO" class="spring.hibernate.webservices.weblogic.dao.PersonDAO"/>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property>
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>


Note that if we do not specify constructor auto-wiring and allow Spring to auto-wire byType, we will need to setup a context param and context listener in the web.xml file in order to get a non-null DAO. We will also need this org.springframework.web.context.ContextLoaderListener to ensure that the WebApplicationContext has been initialized before our WebService is called the first time, so we can register it now, even though it is not absolutely necessary yet.


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" >

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring.hibernate.webservices.weblogic-servlet.xml</param-value>
</context-param>

<servlet>
<servlet-name>spring.hibernate.webservices.weblogic</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>spring.hibernate.webservices.weblogic</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>
index.jsp
</welcome-file>
</welcome-file-list>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

</web-app>



Setup Hibernate


For this example we can use the hsqldb that comes with the Spring distribution. We can setup some infrastructure for the project by creating the following directories:
database-resources
database-resources/scripts
database-resources/hibernate
We can put the hsqldb.jar into war/WEB-INF/lib because it will need to be deployed with our application.
We can put a simple script called createdb.hsql that creates a database with a Person table in database-resources/scripts.


DROP TABLE IF EXISTS person;
CREATE TABLE person (
id INTEGER NOT NULL IDENTITY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL
);


We can put a simple script called populatedb.hsql that populates the Person table in database-resources/scripts.


INSERT INTO person (id, first_name, last_name) values(1, 'John', 'Smith');
INSERT INTO person (id, first_name, last_name) values(2, 'Jane', 'Doe');
INSERT INTO person (id, first_name, last_name) values(3, 'Jim', 'Jones');


We can start and populate the database in our build.xml.


<!-- Start Database tasks -->

<property name="db.url" value="jdbc:hsqldb:hsql://localhost/${name}" />
<property name="db.username" value="sa" />
<property name="db.password" value="" />

<target name="hsqldb-start" description="Starts an HSQL Database.">
<java classname="org.hsqldb.Server" classpathref="master-classpath" fork="true" spawn="true">
<arg line="-database.0 file:${name}" />
<arg line="-dbname.0 ${name}" />
</java>
</target>

<target name="hsqldb-stop" description="Stops an HSQL database.">
<script language="javascript" classpathref="master-classpath">
<![CDATA[
java.sql.DriverManager.registerDriver(new org.hsqldb.jdbcDriver());
con = java.sql.DriverManager.getConnection(project.getProperty("db.url"), project.getProperty("db.username"), project.getProperty("db.password"));
sql = "SHUTDOWN";
stmt = con.createStatement();
stmt.executeUpdate(sql);
stmt.close();
]]>
</script>
</target>

<target name="create-database" description="Create the database.">
<sql driver="org.hsqldb.jdbcDriver" url="${db.url}" userid="${db.username}" password="${db.password}" onerror="continue" src="database-resources/scripts/createdb.hsql">
<classpath refid="master-classpath" />
</sql>
<sql driver="org.hsqldb.jdbcDriver" url="${db.url}" userid="${db.username}" password="${db.password}" onerror="continue" src="database-resources/scripts/populatedb.hsql">
<classpath refid="master-classpath" />
</sql>
</target>

<!-- End Database tasks -->


Note that the task hsqldb-stop, which is based on a similar beanshell task, uses javascript and so requires that either the rhino libraries be on our classpath or that we use Hotspot JDK6. We use this task instead of the standard (below) because it successfully shuts down the database without failing the build with the error java.sql.SQLException: Connection is broken.


<target name="hsqldb-stop">
<sql driver="org.hsqldb.jdbcDriver" url="${db.url}" userid="${db.username}" password="${db.password}" onerror="continue">
<classpath refid="master-classpath" />
SHUTDOWN;
</sql>
</target>



Now that we have a database and a Person table, we can fill out our domain object with annotations. For this example, we can use the Hibernate Tools for Eclipse 3.4 and reverse-engineer the Java class from the schema by creating database-resources/hibernate/hibernate.cfg.xml and hibernate.reveng.xml files.


package spring.hibernate.webservices.weblogic.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
* Person generated by hbm2java
*/
@Entity
@Table(name = "PERSON", schema = "PUBLIC")
public class Person implements java.io.Serializable {

private static final long serialVersionUID = -8972977074631290288L;
private int id;
private String firstName;
private String lastName;

public Person() {
}

public Person(final int id, final String firstName, final String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}

@Id
@Column(name = "ID", unique = true, nullable = false)
public int getId() {
return this.id;
}

public void setId(final int id) {
this.id = id;
}

@Column(name = "FIRST_NAME", nullable = false, length = 50)
public String getFirstName() {
return this.firstName;
}

public void setFirstName(final String firstName) {
this.firstName = firstName;
}

@Column(name = "LAST_NAME", nullable = false, length = 50)
public String getLastName() {
return this.lastName;
}

public void setLastName(final String lastName) {
this.lastName = lastName;
}
}



Before we setup the DAO to use the Spring HibernateTemplate and the Hibernate SessionFactory, we will need to add a few libraries to the war/WEB-INF/lib directory and to our build path:
hibernate3.jar from Hibernate Core
javassist-3.4.GA.jar from Hibernate Core
hibernate-commons-annotations.jar from Hibernate Annotations
hibernate-annotations.jar from Hibernate Annotations
dom4j.jar from Hibernate Annotations
commons-dbcp.jar from the Spring distribution
log4j-1.2.15.jar from the Spring distribution
slf4j-api-1.5.0.jar from the Spring distribution
slf4j-log4j12-1.5.0.jar from the Spring distribution



package spring.hibernate.webservices.weblogic.dao;

import java.util.Collection;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateTemplate;
import spring.hibernate.webservices.weblogic.domain.Person;

public class PersonDAO {

private final HibernateTemplate hibernateTemplate;

@Autowired
public PersonDAO(final SessionFactory sessionFactory) {
hibernateTemplate = new HibernateTemplate(sessionFactory);
}

@SuppressWarnings("unchecked")
public Collection<Person> findAll() {
return hibernateTemplate.loadAll(Person.class);
}
}



The Spring configuration file must now declare that it is managing the DataSource and Hibernate SessionFactory.


<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
<property name="url"
value="jdbc:hsqldb:hsql://localhost/spring.hibernate.webservices.weblogic" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="/WEB-INF/hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>



Finally, we will also create a hibernate.cfg.xml file to declare that Hibernate is managing our domain.


<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<mapping class="spring.hibernate.webservices.weblogic.domain.Person" />
</session-factory>
</hibernate-configuration>



Now if we look at the URL http://localhost:7001/spring.hibernate.webservices.weblogic in a browser, our page should display the names of the people in our createdb.hsql script.

If we add another line to this script, re-run the create-database task and refresh the browser, the list in the browser should reflect the new name.

Setup a WebService


Now that our view is populated by a Spring-managed DAO that uses Hibernate to access our database, we are ready to add our Service endpoint.
Suppose we want to expose all the People in our database with a JAX-WS WebService call.
Suppose we also want to use the Spring Web Context to auto-wire the WebService with our existing DAO.
The interface for the WebService can have a single method that returns an array of People.


package spring.hibernate.webservices.weblogic.ws;
import spring.hibernate.webservices.weblogic.domain.Person;
public interface IGetPeople {
Person[] getPeople();
}



We can create an auto-wired implementation of this interface. This is the class that Spring will know about. To Spring, this class is just an auto-wired bean and not necessarily a WebService.


package spring.hibernate.webservices.weblogic.ws;

import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import spring.hibernate.webservices.weblogic.dao.PersonDAO;
import spring.hibernate.webservices.weblogic.domain.Person;

public final class GetPeopleDelegate implements IGetPeople {

private final PersonDAO personDAO;

@Autowired
public GetPeopleDelegate(final PersonDAO personDAO) {
this.personDAO = personDAO;
}

public Person[] getPeople() {
final Collection<Person> people = personDAO.findAll();
return people.toArray(new Person[people.size()]);
}
}



We can annotate a Service Endpoint implementation of that interface as a WebService, as described in the WebLogic Web Services Guide. WebLogic does not know, nor does it need to know, that this class will delegate its work to a Spring-managed bean. Note that Weblogic must inject the javax.xml.ws.WebServiceContext, which is used to get the javax.servlet.ServletContext and from there to get the Spring bean. Also note the use of efficient, threadsafe lazy instantiation for retrieving the delegate from the Spring BeanFactory.


package spring.hibernate.webservices.weblogic.ws;

import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Resource;
import javax.jws.WebMethod;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.servlet.ServletContext;
import javax.xml.ws.WebServiceContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import spring.hibernate.webservices.weblogic.domain.Person;

@WebService(name = "GetPeople", targetNamespace = "http://spring.hibernate.webservices.weblogic", serviceName = "GetPeople")
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT, use = SOAPBinding.Use.LITERAL, parameterStyle = SOAPBinding.ParameterStyle.WRAPPED)
public class GetPeople implements IGetPeople {

@Resource
private WebServiceContext context;
private final AtomicReference<IGetPeople> autoWiredDelegate;

public GetPeople() {
autoWiredDelegate = new AtomicReference<IGetPeople>();
}

@WebMethod(operationName = "getPeople")
@WebResult(name = "People", targetNamespace = "http://spring.hibernate.webservices.weblogic")
public Person[] getPeople() {
return getAutoWiredDelegate().getPeople();
}

private IGetPeople getAutoWiredDelegate() {
final IGetPeople existingValue = autoWiredDelegate.get();
if (existingValue != null) {
return existingValue;
}
final IGetPeople newValue = createAutoWiredDelegate();
if (autoWiredDelegate.compareAndSet(null, newValue)) {
return newValue;
}
return autoWiredDelegate.get();
}

private IGetPeople createAutoWiredDelegate() {
final ServletContext servletContext = (ServletContext) context.getMessageContext().get(
"javax.xml.ws.servlet.context");
final WebApplicationContext webApplicationContext = WebApplicationContextUtils
.getRequiredWebApplicationContext(servletContext);
return (IGetPeople) webApplicationContext.getAutowireCapableBeanFactory().getBean("getPeople");
}
}


This specific implementation is one of many possible. On the Weblogic 10.0 server, it is also possible to auto-wire a Weblogic WebService by extending org.springframework.web.context.support.SpringBeanAutowiringSupport (and as indicated in the javadoc for this class, "a typical usage of this base class is a JAX-WS endpoint class"). Unfortunately, this is not possible on Weblogic 10.3 because JAX-WS WebServices are instantiated in a different classloader than the classloader in which Spring is running. This does not appear to be the case for the earlier version on WebLogic.

We can add the managed delegate to the Spring configuration file.


<bean id="getPeople" class="spring.hibernate.webservices.weblogic.ws.GetPeopleDelegate" />



We must also declare the WebService in the web.xml file. Because we have already added the org.springframework.web.context.ContextLoaderListener, we can be sure that the WebApplicationContext has been initialized before the WebService is called the first time.


<servlet>
<servlet-name>GetPeoplehttp</servlet-name>
<servlet-class>spring.hibernate.webservices.weblogic.ws.GetPeople</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>GetPeoplehttp</servlet-name>
<url-pattern>/GetPeople</url-pattern>
</servlet-mapping>



Finally, we can setup our build.xml to generate the necessary JAX-WS WebService classes with the Weblogic jwsc task.


<taskdef name="jwsc" classname="weblogic.wsee.tools.anttasks.JwscTask">
<classpath refid="weblogic-classpath" />
</taskdef>

<target name="build-webservices-for-domain">
<jwsc srcdir="src" destdir="tmp/ws/gen" tempdir="tmp/ws/build" keeptempfiles="true">
<classpath refid="master-classpath" />
<classpath refid="weblogic-classpath" />
<module contextpath="${name}">
<jws type="JAXWS" file="spring/hibernate/webservices/weblogic/ws/GetPeople.java" />
</module>
</jwsc>
</target>

<target name="build-webservices">
<run-domain-task task.name="build-webservices-for-domain" />
<mkdir dir="${build.dir}/spring/hibernate/webservices/weblogic/ws/jaxws"/>
<copy todir="${build.dir}/spring/hibernate/webservices/weblogic/ws/jaxws" flatten="true">
<fileset dir="tmp/ws/build">
<include name="*/spring/hibernate/webservices/weblogic/ws/jaxws/*.class" />
</fileset>
</copy>
<delete>
<fileset dir="tmp">
<include name="**/*" />
</fileset>
<dirset dir="tmp">
<include name="**/*" />
</dirset>
</delete>
</target>


Note that if we would like to publish the wsdls, then we can easily set the module task's wsdlonly attribute to true and publish the generated wsdl and xsd before deleting all the temporary files.
Also note that we will want to set build-webservices as a dependency of the build-war task.

If we deploy the application to WebLogic, we can use a tool like soapUI to send a request and receive a response.




Testing the Application


Coming soon -- testing the application with JUnit, JMeter and STIQ . . . .

Saturday, August 23, 2008

Eclipse Test Oriented Templates

Eclipse templates decrease some of the drudgery of re-typing common patterns. While there are quite a few posts that describe the process for importing, exporting and writing custom templates, I have not encountered too many custom templates geared specifically to the test-oriented community (aside from the standard Test and test Java type member templates). With the recent addition of the Templates View in Ganymede, the abilty to paste snippets from the clipboard to this view, and a new feature that allows the automatic addition of Java imports, I will keep the idea of factoring common code patterns to templates in the back of my mind during my day-to-day coding. Because I am a test-oriented developer, I have found the following particularly useful:

JUnit templates:


setup (Context Java type members) Used to create a JUnit4 setUp method

${:import(org.junit.Before)}
@Before
public void setUp() {
}


setup_before_class (Context Java type members) Used to create a static JUnit4 Class setUp method

${:import(org.junit.BeforeClass)}
@BeforeClass
public static void setUpBeforeClass() {
}


teardown (Context Java type members) Used to create a JUnit4 tearDown method

${:import(org.junit.After)}
@After
public void tearDown() {
}


teardown_after_class (Context Java type members) Used to create a static JUnit4 Class tearDowan method

${:import(org.junit.AfterClass)}
@AfterClass
public static void tearDownAfterClass() {
}


Test_expected_exception (Context Java type members) Used to create a JUnit4 test method that expects an Exception to be thrown

@${testType:newType(org.junit.Test)}(expected = ${cursor})
public void ${testname}() throws Exception {
${staticImport:importStatic('org.junit.Assert.*')}
}


Test_timeout (Context Java type members) Used to create a JUnit4 test method with a time limit

@${testType:newType(org.junit.Test)}(timeout = ${cursor})
public void ${testname}() throws Exception {
${staticImport:importStatic('org.junit.Assert.*')}
}


JMock templates:



run_with_jmock (Context Java) Used to annotate a Test Class that it should be run with the JMock runner

${:import(org.junit.runner.RunWith,org.jmock.integration.junit4.JMock)}
@RunWith(JMock.class)


mockery_for_classes (Context Java type members) Used to create a new JMock mockery for Classes

${:import(org.jmock.Mockery,org.jmock.lib.legacy.ClassImposteriser)}
private final Mockery mockery = new Mockery() {
{
setImposteriser(ClassImposteriser.INSTANCE);
}
};


mockery_for_interfaces (Context Java type members) Used to create a new JMock mockery for Interfaces

${:import(org.jmock.Mockery)}
private final Mockery mockery = new Mockery();


checking (Context Java statements) Used to setup JMock expectations

${:import(org.jmock.Expectations)}

mockery.checking(new Expectations() {
{
}
});


General templates:


sysdebug (Context Java statements) Slightly more robust than sysout, labels the value printed to standard out.

System.out.println("${word_selection}=["+${word_selection}${}+"].");${cursor}


templates.xml


You can also copy the following text to a templates.xml file and import it through Eclipse Window -> Preferences -> Java -> Editor -> Templates


<?xml version="1.0" encoding="UTF-8"?><templates><template autoinsert="true" context="java-statements" deleted="false" description="Setup JMock expectations" enabled="true" name="checking">${:import(org.jmock.Expectations)}&#13;
mockery.checking(new Expectations() {&#13;
{&#13;
}&#13;
});</template><template autoinsert="true" context="java-members" deleted="false" description="Create a new JMock Mockery" enabled="true" name="mockery_for_classes">${:import(org.jmock.Mockery,org.jmock.lib.legacy.ClassImposteriser)}&#13;
private final Mockery mockery = new Mockery() {&#13;
{&#13;
setImposteriser(ClassImposteriser.INSTANCE);&#13;
}&#13;
};</template><template autoinsert="true" context="java-members" deleted="false" description="Create a new JMock Mockery" enabled="true" name="mockery_for_interfaces">${:import(org.jmock.Mockery)}&#13;
private final Mockery mockery = new Mockery();</template><template autoinsert="true" context="java" deleted="false" description="Creates a @RunWith(JMock.class) declaration" enabled="true" name="run_with_jmock">${:import(org.junit.runner.RunWith,org.jmock.integration.junit4.JMock)}&#13;
@RunWith(JMock.class)</template><template autoinsert="true" context="java-members" deleted="false" description="setup a JUnit test case" enabled="true" name="setup">${:import(org.junit.Before)}&#13;
@Before&#13;
public void setUp() {&#13;
}</template><template autoinsert="true" context="java-members" deleted="false" description="Create a static setup method" enabled="true" name="setup_before_class">${:import(org.junit.BeforeClass)}&#13;
@BeforeClass&#13;
public static void setUpBeforeClass() {&#13;
}</template><template autoinsert="true" context="java-statements" deleted="false" description="print to standard out with a descriptive variable name" enabled="true" name="sysdebug">System.out.println("${word_selection}=["+${word_selection}${}+"].");${cursor}</template><template autoinsert="true" context="java-members" deleted="false" description="tear down a JUnit test case" enabled="true" name="teardown">${:import(org.junit.After)}&#13;
@After&#13;
public void tearDown() {&#13;
}</template><template autoinsert="true" context="java-members" deleted="false" description="Create a static teardown method" enabled="true" name="teardown_after_class">${:import(org.junit.AfterClass)}&#13;
@AfterClass&#13;
public static void tearDownAfterClass() {&#13;
}</template><template autoinsert="false" context="java-members" deleted="false" description="Test method (JUnit 4) for an expected exception" enabled="true" name="Test_expected_exception">@${testType:newType(org.junit.Test)}(expected = ${cursor})
public void ${testname}() throws Exception {
${staticImport:importStatic('org.junit.Assert.*')}
}</template><template autoinsert="false" context="java-members" deleted="false" description="Test method (JUnit 4) that sets a timeout" enabled="true" name="Test_timeout">@${testType:newType(org.junit.Test)}(timeout = ${cursor})
public void ${testname}() throws Exception {
${staticImport:importStatic('org.junit.Assert.*')}
}</template></templates>

Wednesday, July 30, 2008

Run Weblogic Ant Tasks Without setDomainEnv

Suppose we need to run one of the Weblogic ant tasks for deployment of an application, such as wldeploy, or for webservice support, such as clientgen, jwsc or wsdlc (or any task that requires the environment to be set previously by executing the setDomainEnv script). Suppose we need to execute such an ant task in one command without having set the domain environment previously with this script, say from inside Eclipse. The problem turned out to be more frustrating than I would have preferred, especially since calling the .cmd and then calling ant from another .bat file was problematic. The solution is straightforward, and we can walk through it using the wldeploy example.

The first step is to setup some properties describing your environment, i.e., the location of the weblogic installation, the weblogic domain installation and the weblogic classpath. Assuming we are using weblogic 10 with a domain called aDomain, those properties will look something like this:

<?xml version="1.0"?>
<project ....>
    ....
    <property name="my.home" location="C:/bea" />
    <property name="my.domain" location="${my.home}/user_projects/domains/aDomain" />
    <path id="weblogic.classpath">
        <fileset dir="${my.home}/wlserver_10.0/server/lib">
            <include name="*.jar" />
        </fileset>
    </path>
    ....
</project>



Next, we define the wldeploy task:

<project ....>
    ....
    <taskdef name="wldeploy" 
        classname="weblogic.ant.taskdefs.management.WLDeploy">
        <classpath refid="weblogic.classpath" />
    </taskdef>
    ....
</project>



Now we can create a macro for running any weblogic ant task that requires the environment be set through the setDomainEnv script:

<project ....>
    ....
    <macrodef name="run-domain-task">
        <attribute name="task.name" />
        <sequential>
            <property name="runner" location="${my.domain}/bin/run_domain_task.cmd" />
            <copy file="${my.domain}/bin/setDomainEnv.cmd" tofile="${runner}" overwrite="true" />
            <concat append="true" destfile="${runner}">
                  ant -f "${ant.file}" @{task.name}</concat>
            <exec executable="${runner}" />
        </sequential>
    </macrodef>
    ....
</project>



This macro takes as input a target, such as deploy-to-domain below. It copies the setDomainEnv.cmd to a runner script and appends a command to call the input target from ant. In this way, the domain environment will be set properly before the weblogic-specific task runs.

We can now create a target to be called from the runner deployed in the macro:

<project ....>
    ....
    <target name="deploy-to-domain">
      <wldeploy action="deploy" .... />
    </target>
    ....
</project>



Finally, we add a public target that we call directly or that our Continuous Integration system can run when it has finished building our enterprise application.

<project ....>
    ....
    <target name="deploy">
        <run-domain-task task.name="deploy-to-domain" />
    </target>
    ....
</project>



Ideally, I would prefer to set the environment directly through Ant instead of through the script. As a quick solution, and because setDomainEnv is generated based on a particular installation of a domain, the solution works well in the general case. If anyone is aware of a generic, fully Ant-based solution to this problem, I would certainly like to hear about it.
I would also recommend moving away from Ant and towards Maven2 when at all possible. These instructions should be a good starting point for migrating toward a Maven2 build and deployment solution for Weblogic.

Tuesday, July 29, 2008

ODA Ecore Getting Started Guide -- Part Kettő

Note: this post is a continuation of The ODA-Ecore Getting Started Guide and assumes that you are familiar with its contents and the examples presented therein.

Creating a Master-Details Report


Suppose we want to report on all the Writers and their Books in a model.  Each Writer's name should appear in the Master part of the report.  Each of the Writer's Books should appear as a Detail and should display the Title, Number of Pages and the Category for the Book.

There are at least two solutions with the current implementation of the enablement plug-in.

EReference Resolution

The ODA-Ecore enablement plug-in supports EReference resolution. For any EObject that contains a reference to another EObject, it is possible to display the value of a particular EAttribute of that EReference (or an EAttribute of an EReference to an EReference, etc.). It is also possible to display the value of the full EReference itself (which is just a toString() of the EObject that is referenced).  A user selects EAttributes of EReferences when creating a new Data Set in the Data Set Columns Wizard Page.  Collections of References are returned as a <cr>-delimited String of the selected EAttributes for an EReference. The more deeply nested the EAttribute or EReference, the more dificult interpretation of the results becomes, which is why this method is prefered only in simple cases.



In this selection, we have chosen to output the publicationDate of any books written by a particular Writer, along with the address of any borrowers of the books written by the Writer.

Using EReferences is a simple solution, but it is limited in the general case. For such a simple use case, it will satisfy our needs, however.
From the Query Wizard Page, select Writer as the Invariant.
As your Boolean Query, use "self.oclIsKindOf(Writer)".
From the Columns Wizard Page, select

  • Writer::name
  • Writer::books::title
  • Writer::books::pages
  • Writer::books::category

In the BIRT report, we will insert a new table with 1 columns and 2 details. The first details row will be the Master part, the second will be the Details part.
In column 1 of the first detail row, we will drag Writer::name from the Data Set.
In the second detail row, we will insert a new table with 2 columns and 1 detail. The first column is for spacing, the second will contain the EAttribute values (for the Details part).
In the detail row of the second column of the nested table, we will insert another table with 3 columns and 1 detail row.
Finally, into these 3 columns, we will drag Writer::books::title, Writer::books::pages and Writer::books::category from the Data Set.
We could remove the header information and re-space the columns to get the layout of the information that we want.


The Layout of the Report using EReference Resolution for Master-Details display.




We have achieved our goal using three nested tables and a single query that resolves EReferences. 

Joining ResultSets

Parameterizing the BooleanOCLQuery and and using multiple joined data sets are solutions for more complex situations.

each ? in the query is replaced with the input parameters in order (the first ? is replaced with the first parameter, the second ? is replaced with the second parameter, etc.). Named parameters are not yet supported.

For the example stated above (a Master-Details table of each Writer's name as the Master with information about all the Writer's Books as the Detail.)
1. Create a DataSource and point it to the Extlibrary file.
2. Create a DataSet called "Writers Data Set" with these features:

  • Invariant -- Writer
  • Query -- self.oclIsKindOf(Writer)
  • Selected Column -- Writer::name

3. Create a DataSet called "Books Data Set" with these features:

  • Invariant -- Book
  • Query -- self.oclIsKindOf(Book) and self.oclAsType(Book).author.name = '?'
  • Selected Columns -- Book::title, Book::pages, Book::category, Book::author::name (just to prove out what we are doing)
  • Parameters -- input param1 with default value 'James Fenimore Cooper' (make sure you use quotes or you will get a Javascript error).

4. Drag the Writers Data Set into the Report and name the Table "Writers List". This is the Master part.
5. Add a column to the right of the Writer::name column.
6. Drag the Books Data Set into the empty column. This is the Details part.
7. Select the Books Data Set Table and add a Data Set Parameter Binding from Writer::name to param1.

8. Select Preview and you should now see a Master-Details report that lists the Writers' names in the first column and, for each Writer, a list of the Books associated to that Writer in a nested table in the right column.



Sorting Results


Suppose we need to sort or filter the results of a query. As only Boolean OCL queries are supported by the enablement plug-in, the driver does not currently support a SORT BY feature in the OCL query itself. Fortunately, BIRT's sorting and filtering mechanism for groups should satisfy many of our needs.
Suppose we want to find all the Writers in the object graph and print their names. In the Wizard to form the DataSet, we would choose "Writer" from the dropdown, our query would be "self.oclIsKindOf(Writer)", and we would choose to report on the column "Writer::name".
For the report, we can drag the Data Set from the Data Explorer to the report.


The unsorted preview of this report.

In the Detail Row, instead of using "[Writer::name]", we will delete the Detail row, right click and select "Insert Group". On the Group Details wizard, we will select the item to group on (here, "Writer::name") and the type of sorting we want to perform (here, "Writer::name" in ascending order).


The configuration of filters and sorters.

The resulting report should consistently contain sorted, unique results.


Duplicates are removed and writers' names appear alphabetically.

Thursday, July 24, 2008

Drop All Tables in a Single Query

Recently, I needed to find a single, simple PL/SQL command to drop all the tables from an Oracle database without explicitly naming each table and without dropping the entire schema. Application development has just started, and I need to easily rename tables and other database objects, so the simplest solution at this early stage is often just to wipe and recreate the entire schema.
One possible solution would be to login as SYS and drop the schema itself, but I need to configure a single login for any database access, i.e., the user whose database objects will be deleted.

I did not find my specific solution online, but I owe thanks to alternative approaches in some other posts.

First, make sure that you connect to Oracle as the correct user, i.e., the one whose tables will be dropped.

This command will drop all the user's tables:
BEGIN 
  FOR i IN (SELECT table_name FROM user_tables) 
    LOOP 
      EXECUTE IMMEDIATE('DROP TABLE ' || user || '.' || i.table_name || ' CASCADE CONSTRAINTS'); 
    END LOOP
END;

Similar commands can be used to drop all triggers, sequences, etc.  For example,
BEGIN 
  FOR i IN (SELECT trigger_name FROM user_triggers) 
    LOOP 
      EXECUTE IMMEDIATE('DROP TRIGGER ' || user || '.' || i.trigger_name); 
    END LOOP
END;

               and

BEGIN 
  FOR i IN (SELECT sequence_name FROM user_sequences) 
    LOOP 
      EXECUTE IMMEDIATE('DROP SEQUENCE ' || user || '.' || i.sequence_name); 
    END LOOP
END;

NB: if you drop all tables before triggers, your triggers will be renamed with special characters and the command might not work; make sure to drop triggers before tables.