Monday 15 June 2009

Hello Metro


Developing Hello World with Eclipse

In my previous article I wrote about installing Metro and Glassfish. That article is the prelude for this one. Developing webservices using a toolstack like Metro (as alternative for Apache Axis for example) opposed to developing them in your Application Servers native technology gives you more flexibility in choosing your deployment target. You may rightfully respond with stating that Metro is the native technology of Sun's Glassfish Application Server. It is in fact part of it. But Metro is also available as a separate library for other Application Servers or even standalone clients.

The basics of developing a webservice using Metro in Eclipse is written at: https://metro.dev.java.net/guide/Developing_with_Eclipse.html.

When having your workspace on a network share, define a drive-letter for it or a mount point (for example: /home/makker/workspace) and don’t use the windows-network-path reference (\\networkhost\Share$\makker). Otherwise deployment may fail because of ‘file not found errors’.

A HelloWorld webservice using Metro in Eclipse is really simple.
1. Create a new dynamic Web Project:
2.Name it HelloWorldWS and choose Glassfish as target runtime:and choose Next:

3. Specify the context Root (For example HelloWorldWS):

4. And click Finish.
5. Then create a HelloWorld java Class, from the new-gallery:

6.Implement the following code:
package com.hello.sample;
import javax.jws.WebService;
@WebService(name="HelloWorldWS", serviceName="HelloWorldWS")
public class HelloWorld {
public String hello(String name){
return "Hello "+name;
}
}
It’s all about the WebService annotation really.
7. Go to your GlassFish server in the Servers tab and right click -> ‘Add and Remove Projects’. Shuttle your HelloWorld project to the right.

8. Click Finish. The project will be synchronized with the server automatically. If you do a change then with Right Click>Publish you can synchronize again.
9. The wsdl will be on: http://localhost:8080/HelloWorld/HelloWorldWS?wsdl
10. This can be tested with SoapUI.

Create a HelloWorld Webservice using Ant

The same project can be created using ANT. This is especially usefull when the source may change during development. And when deploying to a another AS then Glassfish.

To generate the necessary webservice code from java-source (bottom-up), APT (Annotation Processor Tool) is used. For Apt an Ant task exist.
For starting your project from WSDL (top-down) also API's and corresponding Ant taks are available. With Metro samples are delivered to show how to do that.

Again create a Dynamic Web Project, like above.
Then create a build.xml script like:

 <?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="help" name="HelloWorld">
<!-- ********** Imports ********** -->
<!-- import base settings -->
<import file="etc/config.xml"/>
<!-- Set jaxws-Class Path -->
<path id="jaxws.classpath">
<pathelement location="${java.home}/../lib/tools.jar"/>
<fileset dir="${metro.lib.home}">
<include name="*.jar"/>
<exclude name="j2ee.jar"/>
</fileset>
</path>
<!-- ********** Task Defs ********** -->
<!-- Import Apt Task Def based on jaxws-classpath -->
<taskdef name="apt" classname="com.sun.tools.ws.ant.Apt">
<classpath refid="jaxws.classpath"/>
</taskdef>
<!-- clean -->
<target name="clean">
<delete dir="${build.home}" includeEmptyDirs="true"/>
<delete dir="${wsdl.home}"/>
<delete file="${webcontent.webinf.home}/sun-jaxws.xml"/>
<delete file="${webcontent.webinf.home}/web.xml"/>
</target>
<!-- Targets -->
<target name="setup">
<mkdir dir="${build.home}"/>
<mkdir dir="${build.classes.home}"/>
<mkdir dir="${webcontent.home}"/>
<mkdir dir="${webcontent.webinf.home}"/>
<!--   <mkdir dir="${wsdl.home}"/>-->
</target>
<!-- Build webservice based on Java-Source -->
<target name="build-server-java" depends="setup">
<echo message="Apt: Generate WebService"/>
<apt
   fork="true"
   debug="true"
   verbose="${verbose}"
   destdir="${build.classes.home}"
   sourcedestdir="${basedir}/src"
   sourcepath="${basedir}/src">
<classpath>
   <path refid="jaxws.classpath"/>
   <pathelement location="${basedir}/src"/>
</classpath>
<option key="r" value="${build.home}"/>
<source dir="${basedir}/src">
   <include name="**/com/rabo/samples/*.java"/>
</source>
</apt>
<echo message="Copy: Jaxws Deployment Descriptor"/>
<!--  copy jaxws deployment descriptors -->
<copy todir="${webcontent.webinf.home}">
<fileset dir="${basedir}/etc/deployment">
   <include name="sun-jaxws.xml"/>
</fileset>
</copy>
</target>
<!-- Help! (default)  -->
<target name="help">
<!--        <echo message="AS_HOME:            ${as.home}"/>-->
<echo message="Settings:"/>
<echo message="METRO_HOME:        ${metro.home}"/>
<echo message=""/>
<echo message="Targets:"/>
<echo message="server:            Generate & build webservice"/>
</target>
<!-- Generate and Build the webservice -->
<target name="server" depends="setup">
<antcall target="clean"/>
<antcall target="build-server-java"/>
</target>

</project>


It expects a config.xml file in the *project*/etc folder:

<project>
<!-- Environment Variables -->
<property environment="env"/>
<!-- Application Server Home (env var AS_HOME must be set) -->
<property name="as.home" value="${env.AS_HOME}"/>
<!-- Metro Home (env var METRO_HOME must be set) -->
<property name="metro.home" value="${env.METRO_HOME}"/>
<property name="metro.lib.home" value="${metro.home}/lib"/>
<property name="source.home" value="${basedir}/src"/>
<property name="build.home" value="${basedir}/build"/>
<property name="build.classes.home" value="${build.home}/classes"/>
<property name="webcontent.home" value="${basedir}/WebContent"/>
<property name="webcontent.webinf.home" value="${webcontent.home}/WEB-INF"/>
<property name="wsdl.home" value="${webcontent.webinf.home}/wsdl"/>
</project>


The scripts expect JAVA_HOME and METRO_HOME to be set.
Create the same java class as in the former example:
package com.hello.sample;
import javax.jws.WebService;
@WebService(name="HelloWorldWSAnt", serviceName="HelloWorldWSAnt")
public class HelloWorld {
public String hello(String name){
return "Hello "+name+"! Used Ant to generate this.";
}
}

This time I changed the name and the service name to distinguish from the former example.
To generate the webservice code perform:
ant server

The ant script also places a web.xml, and a sun-jaxws.xml into the WebContent/WEB_INF folder. It expects these files in the /etc/deployment folder.
The sun-jaxws.xml file should look like:
<?xml version="1.0" encoding="UTF-8"?>

<!--
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.

Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.

The contents of this file are subject to the terms of either the GNU
General Public License Version 2 only ("GPL") or the Common Development
and Distribution License("CDDL") (collectively, the "License").  You
may not use this file except in compliance with the License. You can obtain
a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
language governing permissions and limitations under the License.

When distributing the software, include this License Header Notice in each
file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
Sun designates this particular file as subject to the "Classpath" exception
as provided by Sun in the GPL Version 2 section of the License file that
accompanied this code.  If applicable, add the following below the License
Header, with the fields enclosed by brackets [] replaced by your own
identifying information: "Portions Copyrighted [year]
[name of copyright owner]"

Contributor(s):

If you wish your version of this file to be governed by only the CDDL or
only the GPL Version 2, indicate your decision by adding "[Contributor]
elects to include this software in this distribution under the [CDDL or GPL
Version 2] license."  If you don't indicate a single choice of license, a
recipient has the option to distribute your version of this file under
either the CDDL, the GPL Version 2 or to extend the choice of license to
its licensees as provided above.  However, if you add GPL Version 2 code
and therefore, elected the GPL Version 2 license, then the option applies
only if the new code is made subject to such option by the copyright
holder.
-->

<endpoints xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime' version='2.0'>
<endpoint
name='HelloWorldWSAnt'
implementation='com.rabo.samples.HelloWorld'
url-pattern='/HelloWorldWSAnt'/>
</endpoints>

It’s in fact the deployment file for Metro. For deployment on Glassfish this is enough. Apparently deploying to Glassfish from Eclipse would not need this file.
For deployment to a non-glassfish application server (like Weblogic), you would also need a valid Web.xml. This file should look like:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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">
<display-name>HelloWorldWSAnt</display-name>
<listener>
<listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
</listener>
<servlet>
<servlet-name>HelloWorldWSAnt</servlet-name>
<servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>HelloWorldWSAnt</servlet-name>
<url-pattern>/HelloWorldWSAnt</url-pattern>
</servlet-mapping>
<session-config>
 <session-timeout>60</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>


This Web-deployment file is generated when deploying to Glassfish from Eclipse. So don’t use this file in the WEB_INF folder in the Glassfish case. The web.xml will then be generated. The generated web.xml file will point to a JAXWSServlet class in the Glassfish application server. Other application servers will also have an own version of this class. In the above web.xml a specific Metro Servlet listener is initiated. Also the webservice will point to a Metro servlet implementation. Than on run-time Metro will generate the webservice and service it.

Deploy to Weblogic 10.3

For deployment to Weblogic 10.3 you need to install one of course, and create a domain.
Creating a domain can be done using the configuration wizard. On windows: All Programs> Oracle Weblogic>Weblogic Server 10gR3>tools>Configuration Wizard
(references: C:\utils\weblogicserver10.3\wlserver_10.3\common\bin\config.exe)

Then you’ll need to install Metro in Weblogic. This is done by copying the following files to the *weblogic-domain-home*/lib (for example C:\utils\weblogicserver10.3\domains\base_domain\lib\):
  • webservices-rt.jar
  • webservices-tools.jar
  • webservices-api.jar
On some Application servers using the Sun 1.6.0 JDK, the webservices-api.jar should be set in the *jdk_home*/lib/endorsed. On my local Weblogic 10.3 server I did not need to.

After this you have to restart your domain.

You can add Weblogic to Eclipse the same way as Glassfish. You’ll have to install the Weblogic pluging though. Choose the ones from the Oracle main entry (not the BEA), because Weblogic 10.3 is from the Oracle BEA era.

You cannot deploy your project by publishing it from Eclipse. Eclipse will complain that Weblogic will not support the sun-jaxws.xml deployment descriptor.

In your HelloWorld project, make sure you have your java source, web.xml and sun-jaxws.xml compiled and in the right folders. Then export your project to a WAR file.

This War file can be deployed to Weblogic using the console (localhost:7001/console). In the console click on Deployments and then install or update. Upload the war to *weblogic-domain-home*\servers\AdminServer\upload\ . If you point directly to your war-file, it is locked by Weblogic.

Weblogic will see that it is a Web-application, but it will not see the Webservice. This is probably because the wsdl and xsd are generated by Metro on deployment time/startup of the Webapplication So you cannot test it from the console. But the wsdl will be on:
http://localhost:7001/HelloWorldWSAnt/HelloWorldWSAnt?wsdl.
I did not try it yet, but it might be possible to register the generated wsdl as a post-deploy step in WebLogic. Maybe that this enables the console to better manage the webservice application.

Usefull Annotations

Earlier I mentioned that it's all in the Annotation really. In the above example I used the bottom-up approach (in the Metro-samples called the "from-java" approach). The HelloWorld example just receives a string and responses with one. But in reqular projects you want to return more sophisticated structures. And then you might want to do the top-down approach, starting with WSDL. The side affect there is that if you change the WSDL you generate the java-implementations, but you have to manually recouple those with your functional code.
I did the from-java approach so I create a compilable set of my functional and webservice code. And then I generate the webservice (XSD and WSDL). This is very usefull if you're in control of the interface. But then you need an enhanced set of annotations to refine your interface.
With this set I managed to manipulate the outcome of my WSDL and XSD generation to match the design. The full description of the annotation you can find at file:///*metro-home*/docs/annotations.html or in the Latest Online Doc: https://jax-ws.dev.java.net/nonav/2.1.7/docs/annotations.html.

Webservice
This is, I think, the basic annotation. This makes the webservice. Actually you need only this one to turn a class into a webservice.
Example: @WebService(name="HelloWorldWS", serviceName="HelloWorldWS")
Other attributes:
  • targetNamespace
  • endpointInterface
  • portName
  • wsdlLocation (Not currently used by Metro 1.5 FCS)
WebMethod
This one just denotes which methods are to be exposed as Webservice Operations.
Example: @WebMethod
Attributes:
  • operationName (defaults to the java name)
  • action (namespace of the wsdl)
  • exclude (to exclude the method from the webservice, defaults to false)

WebParam
To specify the name of the parameter as it appears in the Schema of the request message in the generated XSD. If not used, the parameters are named positional, like 'arg0', 'arg1', ...
Example: @WebParam(name="userName") String userName
Other Attributes:
  • targetNamespace
  • mode
  • header
WebResult
Name of the result variable, the root element in the response message in the XSD.
Example: @WebResult(name="Greeting")

RequestWrapper and ResponseWrapper
These I found are very usefull. With these you can influence the name of the request and response messages, theire namespace and the name of the generated request and response classes.
Example:
@RequestWrapper(localName = "HelloWorldReq", targetNamespace = "http://webservice.darwin-it.nl/", className = "nl.darwin-it.webservice.jaxws.HelloWorld")
@ResponseWrapper(localName = "HelloWorldRsp", targetNamespace = "http://webservice.darwin-it.nl/", className = "nl.darwin-it.webservice.jaxws.HelloWorldResponse")

Mark that for these two the webservices-api.jar has to be added to your project. You might have the GlassFish 2.1 Java EE 5 system library added but this does not have the webservices-api.jar added to it. The corresponding classes for these two annotations are there.
You have to do these imports:
  • import javax.xml.ws.RequestWrapper;
  • import javax.xml.ws.ResponseWrapper;
For the other annotations you have to do the corresponding imports from javax.jws.*.
Overall example:
package com.hello.sample;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;
@WebService(name="HelloWorldWS", serviceName="HelloWorldWS")
public class HelloWorld {
  @WebMethod
  @WebResult(name="Greeting")
  @RequestWrapper(localName = "HelloWorldReq", targetNamespace = "http://webservice.darwin-it.nl/", className = "nl.darwin-it.webservice.jaxws.HelloWorld")
  @ResponseWrapper(localName = "HelloWorldRsp", targetNamespace = "http://webservice.darwin-it.nl/", className = "nl.darwin-it.webservice.jaxws.HelloWorldResponse")
  public String hello( @WebParam(name="userName") String name){
      return "Hello "+name;
  }
}

Conclusions and More information

With Metro it is really easy to generate an Application Server independend Webservice. With the annotations it is also possible to influence the outcome of the XSD and WSDL generation.
For more information:

4 comments :

Anonymous said...

I've tried it with Tomcat, but i keep getting the following error:

com.sun.xml.ws.transport.tcp.server.servlet.WSStartupServlet contextInitialized
SEVERE: WSTCP0007:Transport module was not initialized!

How can the WSTCPModule be initialized?

Martien van den Akker said...

Unfortunately I don't know. I did struggle some with the different config files (web.xml, sun-web.xml and sun-jaxws.xml) to get it working under weblogic. Review those files on consistency. And make sure that the metro-jars are in the class-path of the domain.
For the rest I'd suggest to post your question on a metro forum.

andres hurtado said...

I've tried with wlas 10.3.3 (11g)
but i've got the followint exception:


java.lang.ExceptionInInitializerError
...
Caused by: java.lang.IllegalArgumentException: com.sun.xml.messaging.saaj.soap.LocalStrings != com.sun.xml.internal.messaging.saaj.soap.LocalStrings
at java.util.logging.Logger.getLogger(Logger.java:314)
at com.sun.xml.internal.messaging.saaj.soap.SAAJMetaFactoryImpl.(SAAJMetaFactoryImpl.java:41)
... 58 more



after this error I've packaged the solution as an ear file, with the weblogid descriptors (ear/META-INF/weblogic-application.xml and war/WEB-INF/weblogic.xml ) according this article
and now i've got the exception:

"javax.xml.ws.WebServiceException: Provider com.sun.xml.ws.spi.ProviderImpl not found


Do you have tried to deploy your application over this container version?

(http://forums.oracle.com/forums/thread.jspa?messageID=7008285&#7008285)

Martien van den Akker said...

No, I did not try it with that particular version. Actually it was already some time ago that I worked with that. The goal, though, was to have a webservice that was deployable on any j2ee server. In my case on production it was IBM WAS.
The Weblogic was to make sure it wasn't Glassfish, since metro runs natively on Glassfish: it is delivered with it.