Wednesday, December 05, 2007

Web Services - Spring Style

Ok, so there is the whole debate about EJB's vs.  Web Services, at least at my company - we have a team of Java developers who want to use EJB's (well sort of) and the Architecture team who is pushing portability/compatibility with web services. 

We use IBM's WebSphere here at work which is both good and bad I suppose - recently I was asked to help out with a Proof - Of - Concept application and wanted to leverage some of the services developed on WebSphere but from a non-WebSphere app server.  Guess what...  IBM won't let you access their services unless you are running under their JDK.  What the heck is up with that IBM?   We're wanting to play with some of the cool stuff that EJB 3/JPA and Annotations can give us but IBM doesn't support that in their JDK's so we can't play at all.

So, I'm playing with web services because I want a service that can be portable and compatible with other languages/frameworks - which comes at a bit of a performance hit - I understand - but it would be nice to have some services that could be accessed from other languages - don't get me wrong I love Java but I like to tinker with Python and Ruby as well....

My only complaint with Spring's Web Services is the build process...

I wrote my first Spring web service to authenticate user's against one of our LDAP servers, now I'm getting ready to write another web service from scratch and thought I'd write a tutorial on it as I go through the steps (note: I have adapted all of the code examples from a project I'm developing for another company so if you seen any misnamed xml namespaces, class files - or anything else let me know I'll fix them up for the next person).

Let's call our new web service...."ApplicationServices"

  • First of all create a Web Project however you like to do that (I like Eclipse so I'm going to be creating a new "Dynamic Web Project" with as few faces as possible - no struts, no jsf, nothing, just a plain vanilla servlet project)
  • My application will be running on GlassFish this is what the initial layout looks like:

projectLayout 

  • For my project I need to expose some functionality that was written to be included in another application.  Luckily it can be bundled up in a Jar and included into our project very easily.  Along with that let's go ahead and add all of our other supporting libraries.  Here is a list of every jar I needed to add for the Spring Web Services framework to work (versioning shouldn't be too terribly critical just don't use anything less than the version listed), along with hibernate as well.:
    • spring-ws-core-1.0.2.jar
    • spring-xml-1.02.jar
    • spring.jar
    • commons-logging.jar
    • commons-collections.jar(hibernate)
    • log4j-1.2.15.jar
    • jdom.jar - get this at JDOM
    • WSDL4j.jar
    • cglib-nodep-2.1_3.jar (hibernate)
    • ehcache-1.2.4.jar (hibernate)
    • hibernate3.jar (hibernate, duh)
    • dom4j-1.6.1.jar (hibernate)
  • Now that we have our libs all setup we need to configure our web.xml file.  Basically we need to map the spring webservice servlet class to a url and load up our config file like this:
    • <?xml version="1.0" encoding="UTF-8"?>
      <web-app id="WebApp_ID" 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">
      <display-name>ApplicationServices</display-name>
      <servlet>
      <servlet-name>services-ws</servlet-name>
      <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
      </servlet>
      <servlet-mapping>
      <servlet-name>services-ws</servlet-name>
      <url-pattern>*.wsdl</url-pattern>
      </servlet-mapping>
      <welcome-file-list>
      <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>
      </web-app>

    • So, what is going here?  the <servlet> Tag let's our container know we have a servlet (you probably figured that much out) and then gives us a name to map it with and the class that needs to be loaded for it.
    • Next is the servlet mapping which uses the mapping name we just created and maps that to any requests that come in with a URL that ends with *.wsdl
    • Now this is the important part you MUST name your spring file the name of your servlet so for this example our spring file must be named: services-ws-servlet.xml and it must be in your WEB-INF directory (see the picture up above).
    • To be convenient let's load up a welcome file to see if things are working (don't forget to create the index.jsp file in the WebContent root directory - we will be using it to test with.  You can do something simple like "Hello World" if you want.  I never seem to get tired of seeing that one....

  • Let's go ahead and stub out a spring file and see if our application will even load.  Create a new file called services-ws-servlet.xml in the WEB-INF directory you can fill it up with this:

    • <?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.0.xsd
      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
      http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd
      http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
      http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:util="http://www.springframework.org/schema/util">

      </beans>

    • That should be enough to get us started

  • Go ahead and fire up your container and see if it works.  You shouldn't see any startup exceptions in your logs.  And if you go to the URL of your container probably something like http://localhost:8080/ApplicationServices/index.jsp you should see whatever you content you put in the jsp page - now if you were to jump ahead and go to http://localhost:8080/ApplicationServices/something.wsdl the servlet we configured will load up and you should get a 405 error.
  • If you look at your logs you probably saw an error that said your log4j isn't configured correctly let's throw out a log4j.properties file in our classpath real quick this is the content I typically use:
      log4j.rootLogger=DEBUG, stdout, logfile
      log4j.rootCategory=DEBUG, stdout, logfile

      log4j.appender.stdout=org.apache.log4j.ConsoleAppender
      log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
      # %d = date
      # %p = priority
      # %c = category (class)
      # %m = message
      # %C = class
      # %M = method name
      log4j.appender.stdout.layout.ConversionPattern=%d %p [%c.%M] - %m %n

      log4j.appender.logfile=org.apache.log4j.RollingFileAppender
      log4j.appender.logfile.File=../logs/applications/applicationServices.log
      log4j.appender.logfile.MaxFileSize=1024KB
      log4j.appender.logfile.MaxBackupIndex=5
      log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
      log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

      log4j.category.org.hibernate=WARN
      log4j.category.net.sf.ehcache=WARN
      log4j.category.org.apache.axis=WARN
      log4j.category.org.apache.commons.beanutils=WARN
      log4j.category.org.apache.commons.digester=WARN
      log4j.category.org.apache.catalina.session.ManagerBase=WARN
      log4j.category.org.apache.jasper=WARN
      log4j.category.org.springframework=WARN

  • So let's do something interesting....  Spring Web services recommends using the contract first pattern - basically we configure an xml messsage (or conversation) and use that to drive our service creation instead of having our application drive our xml configuration.  This seems a little backwards but it breaks your XML (or WSDL ultimately) from being tightly coupled to the code behind it (which is a good thing).
  • So we need our service to do something... Let's create some XML to help us think about what we're going to do:

    • Let's create an auto process request object that has an instance name
    • And an auto process response that has a statusCode and a messages field my XML looks like this:
      <autoProcessing  xmlns="http://www.flyingspheres.com/tutorial/services">
      <processRequest
      <instanceName>someInstanceName</instanceName>
      </processRequest>

      <processResponse>
      <statusCode></statusCode>
      <messages></messages>
      </processResponse>
      </autoProcessing>

  • We need to turn that into an xsd document that looks like - a little caveat here there is an amusing statement in the spring webservices tutorial "By far the easiest way to create an XSD is to infer it from sample documents.  Any good XML editor or Java IDE offers this functionality" Oddly enough I couldn't figure out how to get Eclispe or Netbeans to do this either - either I don't know how to use the tool (possible) or they aren't decent editors ( you can decide that one yourself).

    • You could definitely hand tool the XSD it isn't that difficult however... since we want to reduce the hand tooling as much as possible go to HitSoftware and load up your xml file and generate your xsd
    • You should get a response that looks like this:
      <?xml version="1.0" encoding="UTF-8" ?>

      <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
      <xs:element name="autoProcessing">
      <xs:complextype>
      <xs:sequence>
      <xs:element ref="processRequest" />
      <xs:element ref="processResponse" />
      </xs:sequence>
      </xs:complextype>
      </xs:element>

      <xs:element name="instanceName">
      <xs:complextype mixed="true" />
      </xs:element>

      <xs:element name="messages" type="xs:string" />

      <xs:element name="processRequest">
      <xs:complextype>
      <xs:sequence>
      <xs:element ref="instanceName" />
      </xs:sequence>
      </xs:complextype>
      </xs:element>

      <xs:element name="processResponse">
      <xs:complextype>
      <xs:sequence>
      <xs:element ref="statusCode" />
      <xs:element ref="messages" />
      </xs:sequence>
      </xs:complextype>
      </xs:element>

      <xs:element name="statusCode" type="xs:string" />

      </xs:schema>

  • That got us pretty close but to get to where we need we need to adjust the xsd just a bit let's change it to look like this, oh and put the saved file in your WEB-INF directory: 
    <?xml version="1.0" encoding="UTF-8" ?>

    <xs:schema targetNamespace="http://service.flyingspheres.com/autoProcessing"
    elementFormDefault="qualified"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:auto="http://service.flyingspheres.com/autoProcessing">
    <xs:element name="autoProcessing">
    <xs:complexType>
    <xs:choice>
    <xs:element ref="auto:processResponse" />
    <xs:element ref="auto:processRequest" />
    </xs:choice>
    </xs:complexType>
    </xs:element>

    <xs:element name="processRequest">
    <xs:complexType>
    <xs:sequence>
    <xs:element ref="auto:instanceName" />
    </xs:sequence>
    </xs:complexType>
    </xs:element>
    <xs:element name="processResponse">
    <xs:complexType>
    <xs:sequence>
    <xs:element ref="auto:messages" />
    <xs:element ref="auto:statusCode" />
    </xs:sequence>
    </xs:complexType>
    </xs:element>
    <xs:element name="instanceName" type="xs:string" />
    <xs:element name="messages" type="xs:string" />
    <xs:element name="statusCode" type="xs:string" />
    </xs:schema>

  • Next we need to flesh out our spring file so that it will do something useful. I highly recommend going out and reading the documentation on the spring web services before you go too far down this path there are a lot of options and a ton of functionality I'm not covering here but you may need to do something very clever that I don't....
  • So this is my spring file:

<?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.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:lang="http://www.springframework.org/schema/lang" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:util="http://www.springframework.org/schema/util">
<import resource="persistence_ioc.xml" />
<!-- WSDL Generation Information -->
<bean id="autoProcessing" class="org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition">
<property name="builder">
<description>
The builder creates a WSDL from the a schema.
It detects all elements that ends with 'Request', finds corresponding
'Response' messages, and creates an operation based on that.
        </description>
<bean class="org.springframework.ws.wsdl.wsdl11.builder.XsdBasedSoap11Wsdl4jDefinitionBuilder">
<property name="schema" value="/WEB-INF/mySchemaFile.xsd"/>
<property name="portTypeName" value="processing"/>
<property name="locationUri" value="http://localhost:8080/ApplicationServices/"/>
</bean>
</property>
</bean>

<bean id="servicesEndPoint" class="com.flyingspheres.service.endpoints.ServicesEndPoint">
<constructor-arg ref="autoProcessingService"/>
</bean>

<bean id="autoProcessingService" class="com.flyingspheres.service.AutoProcessingService">

</bean>

<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="mappings">
<props>
<prop key="{http://service.flyingspheres.com/processing}Processing">servicesEndPoint</prop>
</props>
</property>
<property name="interceptors">
<list>
<ref local="loggingInterceptor"/>
<ref local="validatingInterceptor"/>
</list>
</property>
    </bean>
<bean id="loggingInterceptor" class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor">
<description>
This interceptor logs the message payload.
</description>
</bean>
<bean id="validatingInterceptor"
class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
<description>
This interceptor validates both incoming and outgoing message contents according to the 'echo.xsd' XML
Schema file.
</description>
<property name="schema" value="/WEB-INF/mySchemaFile.xsd"/>
<property name="validateRequest" value="true"/>
<property name="validateResponse" value="true"/>
</bean>
</beans>



    • Details:

      • the autoProcessing bean has defines how the container / client will interact with our service you can access the wsdl by going to the url locationUri/portTypeName.wsdl: http://localhost:8080/ApplicationServices/processing.wsdl obviously this will probably be different for you.
      • servicesEndPoint bean is the entry point for your application, our example uses a constructor arg so that we can control how it is created - this can be done a lot of different ways but this is how the Spring WS tutorial does it so we will follow suit.
      • autoProcessingServices bean is where we will do most of our coding that is what will actually happen
      • The next bean doesn't have a name but is really where the magic happens that kind of maps urls to our endpoints (which ends up calling our service bean).  This line: <prop key="{http://service.flyingspheres.com/processing}processing">servicesEndPoint</prop> is where the url is mapped to our end point
      • Next some interceptors are defined (we'll talk about interceptors and aspects some other day).  For now they're there to do what they look like validation and logging

  • Well if we did everything correctly we should be able to access a wsdl that looks something like this:
    <?xml version="1.0" encoding="UTF-8"?><wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:schema="http://service.flyingspheres.com/processing" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" targetNamespace="http://service.flyingspheres.com/processing">
    <wsdl:types>
    <xs:schema xmlns:auto="http://service.flyingspheres.com/processing" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://service.flyingspheres.com/processing">
    <xs:element name="autoProcessing">
    <xs:complexType>
    <xs:choice>
    <xs:element ref="auto:processResponse"/>
    <xs:element ref="auto:processRequest"/>
    </xs:choice>
    </xs:complexType>
    </xs:element>

    <xs:element name="processRequest">
    <xs:complexType>
    <xs:sequence>
    <xs:element ref="auto:instanceName"/>
    </xs:sequence>
    </xs:complexType>
    </xs:element>
    <xs:element name="processResponse">
    <xs:complexType>
    <xs:sequence>
    <xs:element ref="auto:messages"/>
    <xs:element ref="auto:statusCode"/>
    </xs:sequence>
    </xs:complexType>
    </xs:element>
    <xs:element name="instanceName" type="xs:string"/>
    <xs:element name="messages" type="xs:string"/>
    <xs:element name="statusCode" type="xs:string"/>
    </xs:schema>
    </wsdl:types>
    <wsdl:message name="processRequest">
    <wsdl:part element="schema:processRequest" name="processRequest">
    </wsdl:part>
    </wsdl:message>
    <wsdl:message name="processResponse">
    <wsdl:part element="schema:processResponse" name="processResponse">
    </wsdl:part>
    </wsdl:message>
    <wsdl:portType name="autoProcessing">
    <wsdl:operation name="process">
    <wsdl:input message="schema:processRequest" name="processRequest">
    </wsdl:input>
    <wsdl:output message="schema:processResponse" name="processResponse">
    </wsdl:output>
    </wsdl:operation>
    </wsdl:portType>
    <wsdl:binding name="autoProcessingBinding" type="schema:autoProcessing">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="process">
    <soap:operation soapAction=""/>
    <wsdl:input name="processRequest">
    <soap:body use="literal"/>
    </wsdl:input>
    <wsdl:output name="processResponse">
    <soap:body use="literal"/>
    </wsdl:output>
    </wsdl:operation>
    </wsdl:binding>
    <wsdl:service name="autoProcessingService">
    <wsdl:port binding="schema:autoProcessingBinding" name="autoProcessingPort">
    <soap:address location="http://localhost:8080/ApplicationServices/"/>
    </wsdl:port>
    </wsdl:service>
    </wsdl:definitions>

  • We can then take this wsdl and drop it into our client and have it generate the necessary files to call our web services.
  • Before we close this tutorial for now let's talk real quick about the ServicesEndPoint class.    So we told spring that we were going to create a class called com.flyingspheres.service.endpoints.ServicesEndPoint that would extend the AbstractJDomPayloadEndpoint class (by telling spring that it would be called from the org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping class that meant we had to extend the AbstractJDomPayloadEndpoint.  Since we also told spring that we would overload the constructor with our serviceBean we need to define that constructor as well as the methods the PayloadEndpoint forces us to implement.  The Spring WS tutorial section 3.6 does a great job explaining what needs to happen in those methods - so I won't cover that here.  For our needs lets put in some logging like this:
    package com.flyingspheres.service.endpoints;

    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.jdom.Element;
    import org.springframework.ws.server.endpoint.AbstractJDomPayloadEndpoint;

    import com.flyingspheres.service.AutoProcessingService;

    public class ServicesEndPoint extends AbstractJDomPayloadEndpoint{
    private static final Log LOG = LogFactory.getLog(ServicesEndPoint.class.getName());
    public ServicesEndPoint(AutoProcessingService service){
    LOG.debug("Entering the constructor with a: " + service.getClass().getName());
    }
    protected Element invokeInternal(Element authenticationRequest) throws Exception {
    LOG.debug("Entering the invokeInternal method with a: " + authenticationRequest.getClass().getName());
    return null;
    }
    }

Well that should be enough to get you started...  Read the tutorial and all of the other documentation as well if you have any questions let me know Aaron at flyingspheres dot com


 


Happy coding!

No comments: