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

10 comments:

Peter Thomas said...

Thanks for linking to my HSQLDB + BeanShell tip. I was not sure what you meant by the "java.sql.SQLException: Connection is broken" problem so I tried myself to use the Ant SQL Task and I hit the problem straight away. With a little bit of guesswork I found that if you add autocommit="true" the problem is solved.

So try this:

<target name="hsqldb-stop">
  <sql
    classpathref="master-classpath"
    driver="org.hsqldb.jdbcDriver"
    url="${db.url}"
    userid="${db.username}"
    password="${db.password}"
    autocommit="true">SHUTDOWN</sql>
</target>

Let me know if it works and I will update my post as well.

Tim Myer said...

Hi Peter,
Thanks very much for the comment and for the solution -- it works for me.
-----Tim-----

justin_sands2000 said...

Here, you have hardcoded the database password into your spring beans xml. This seems like a common, but poor practice, as changing the DB password requires that the war file must be rebuilt and redeployed.

I understand that your article is intended to be 'quick setup', but could you explain how to things the right way (perhaps in another article).

The best practice seems to be -- have the spring application lookup its datasource in JNDI. However, this only works with spring if there is no security on the jndi tree or datasource (not realistic).

Tim Myer said...

Hi Justin,
Thank you for the feedback.
You make a good point that including database credentials directly in the Spring application context is not a best practice. In production deployments, I have generally used Spring with JNDI to configure datasources without second-guessing by network security admins. Who has access to JNDI can be configured at the network level. If JNDI connections are only available to localhost or to a limited set of addresses, then password protection of the JNDI tree could be overkill.
The datasource can be configured in the spring application context similarly to this:

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiName" value="my/jndi/name" />  
  <property name="jndiEnvironment">
    <props>
      <prop key="java.naming.provider.url">t3://localhost:7001</prop>
    </props>
  </property>
</bean>

I hope that answers your question. If I have misunderstood, please let me know.
Thanks.
---Tim---

justin_sands2000 said...

Interesting, personally I don't have much experience with what network admins find acceptable -- so I'm just guessing here.

The policy you describe is this: if the JNDI request comes from the local machine, the caller is entitled to the resource. It's not the same as saying: web application X can only access datasource A (it implies that any web application (web or otherwise) hosted on the local machine can access datasource A. It seems (to me) that should be the obvious way to deploy web applications -- but I can't seem to find much evidence of this.

The solution I have been trying to put together (but can't quite get my head around) is this:
- define a security-role in my web.xml for my application "MyAppRole"
- have the spring dispatch servlet run as that role using a servlet "run-as" tag:
- In weblogic.xml, Map that DD role to a security principle that the container understands using a "run-as-role-assignment" mapping MySystemRole to a user in the WL security realm MyAppUser.
- put a WL policy constraint on the JNDI tree or datasource that says "require role = MyAppUser"

In theory, when my spring controllers need to acquire container resources (like datasources), they will be acting as that principal, and have permission to the datasource.

At this point I can't tell if it works, as I have a filter which needs the database. Since I can't put the run-as on the filter, i'm out of luck.

zam0th said...

hi!

tried to make your example work and got stuck with JWSC giving these kinds of errors:

[jwsc] JWS: processing module jws
[jwsc] Parsing source files
[jwsc] Parsing source files
[jwsc] 1 JWS files being processed for module jws
[jwsc] JWS: GetPeople.java Validated.
[jwsc] Processing 1 JAX-WS web services...
[jwsc] warning: Annotation types without processors: [javax.annotation.Resource]
[jwsc] 1 warning
[jwsc] warning: Annotation types without processors: [javax.xml.bind.annotation.XmlRootElement, javax.xml.bind.annotation.XmlAccessorType, javax.xml.bind.annotation.XmlType, javax.xml.bind.annotation.XmlElement]
[jwsc] 1 warning
[jwsc] JWS Compile failed: Error processing JAX-WS web services

how come that you didn't actually have it?

Cycling Beaver said...

Thanks so much! I was using Hibernate to generate a data access layer that I wanted to expose as web services. I am using Weblogic 10.3, and Workshop. This really helped a lot. I had to create more than one service, so I wrote this little abstract generic class that helped me create multiple web service classes. I thought I'd put it out here in case anyone else might find it useful. Thanks!

package com.comcastmediacenter.boss.web.service;

import java.util.concurrent.atomic.AtomicReference;

import javax.annotation.Resource;
import javax.servlet.ServletContext;
import javax.xml.ws.WebServiceContext;

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

/**
* Abstract base class for all Spring beans that are
* being exposed as JAXWS web services
*
* @author ldobbi5949k
*
*/
public abstract class AbstractSpringService<I> {

@Resource
private WebServiceContext context = null;
private final AtomicReference<I> autoWiredDelegate;
private final String myBeanName;

public AbstractSpringService(String bean) {
autoWiredDelegate = new AtomicReference<I>();
myBeanName = bean;
}

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

@SuppressWarnings("unchecked")
private I createAutoWiredDelegate() {
final ServletContext servletContext = (ServletContext) context.getMessageContext().get("javax.xml.ws.servlet.context");
final WebApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
return (I) webApplicationContext.getAutowireCapableBeanFactory().getBean(myBeanName);
}
}

Here's an instance example:

/**
*
*/
package com.comcastmediacenter.boss.web.service;

import java.util.List;

import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

import com.comcastmediacenter.boss.dto.Account;
import com.comcastmediacenter.boss.service.IAccountService;

/**
* @author ldobbi5949k
*
*/
@WebService(name="AccountService",
targetNamespace="http://www.comcastmediacenter.com/Boss",
serviceName="AccountService")
@SOAPBinding(style=SOAPBinding.Style.DOCUMENT, use=SOAPBinding.Use.LITERAL,
parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)
public class AccountService extends AbstractSpringService<IAccountService> implements
IAccountService {

public AccountService () {
super("AccountService");
}

/* (non-Javadoc)
* @see com.comcastmediacenter.boss.service.IAccountService#findAccountById(java.lang.Long)
*/
@Override
@WebResult(name = "Account")
public Account findAccountById(@WebParam(name="id") Long id) throws Exception {
return getAutoWiredDelegate().findAccountById(id);
}
}

Tim Myer said...

Thanks for the feedback and for the utility, Cycling Beaver!
---Tim---

Web dynamic max level - 8x said...

Hi Tim
I'm coding the same your example. But i get error :
Caused By: weblogic.wsee.ws.WsException: Two port in the UserServiceService(WebserviceDescriptionBean) has the same name {http://service.core.donriver.com/}UserServicePort.
at weblogic.wsee.deploy.WSEEAnnotationProcessor.checkWsdlPortNames(WSEEAnnotationProcessor.java:143)
at weblogic.wsee.deploy.WSEEAnnotationProcessor.process(WSEEAnnotationProcessor.java:133)
at weblogic.wsee.deploy.WSEEAnnotationProcessor.process(WSEEAnnotationProcessor.java:52)
at weblogic.wsee.deploy.WSEEModule.prepare(WSEEModule.java:111)
at weblogic.wsee.deploy.AppDeploymentExtensionFactory.prepare(AppDeploymentExtensionFactory.java:79)
Truncated. see log file for complete stacktrace

Please help me !

Sam said...

Hi Tim,

This is Sam. If I have to run the "Spring, Hibernate and JAX-WS WebServices on Weblogic" using Spring 3.0.7 framework, what needs to be done? I tired and getting error during the deployment. PLease advise.

Sam