Raible's Wiki

Raible Designs
Wiki Home
News
Recent Changes

AppFuse

Homepage
  - Korean
  - Chinese
  - Italian
  - Japanese

QuickStart Guide
  - Chinese
  - French
  - German
  - Italian
  - Korean
  - Portuguese
  - Spanish
  - Japanese

User Guide
  - Korean
  - Chinese

Tutorials
  - Chinese
  - German
  - Italian
  - Korean
  - Portuguese
  - Spanish

FAQ
  - Korean

Latest Downloads

Other Applications

Struts Resume
Security Example
Struts Menu

Set your name in
UserPreferences


Referenced by
AppFuseAxis
AppFuseXFireOld
AppFuseXFireOpenLasz...
Articles
XFireAegisMapping




JSPWiki v2.2.33

[RSS]


Hide Menu

AppFuseXFire


Difference between version 12 and version 5:

At line 1 changed 1 line.
__Part I:__ [Integrating XFire Webservices into AppFuse|AppFuseXFire] - A HowTo for providing Webservices from within AppFuse.
Webservices out of AppFuse - A HowTo for providing Webservices from within AppFuse.
At line 4 changed 1 line.
This tutorial will show you how to create a Webservice Endpoint and what you can do to automate the creation of artifacts necessary to provide Webservices to others.
This tutorial will show you how to create a Webservice and what you can do to automate the creation of artifacts necessary to provide Webservices to others.
At line 8 changed 6 lines.
* [1] Download the prerequisite packages
* [2] Add the packages to AppFuse's lib structure
* [3] Modify the build script to include the packages
* [4] Add the XFireServlet definition
* [5] Create an xdoclet-task in build.xml to generate aegis.xml mapping files
* [6] Expose a Manager as a webservice
* [1] Install the necessary artifacts from the extras package
* [2] The UserWebservice demo inspected
* [3] Testing without container
At line 30 changed 5 lines.
There's work in progress to automate the process of plugging in XFire into AppFuse (via an extras package), you'll find the
first shot version attached to this page as [zip|http://raibledesigns.com/wiki/attach/AppFuseAxis/extras-xfire.zip].
All that's described on this page is performed by an ant-task in this package. Expand it in the extras folder of your project,
go into the folder xfire-webservice with a shell and do an
!!Install the necessary artifacts from the extras package [#1]
Since AppFuse 1.9.1 there is an extras package named xfire which enables Webservice with xfire in AppFuse.
Go into extras/xfire and issue
At line 38 changed 1 line.
there. It will install the necessary files and do the changes described below.
there. This install everything you need to create your own webservices, but doesn't touch your code.
In detail it does the following:
At line 40 changed 4 lines.
!!Download the prerequistie packages [#1]
Currently I recommend the nightly snapshot of XFire. This may change with the release of 1.0, but the latest milestone 1.0M6a has got some problems that have been fixed in CVS.
It is available from the [Codehaus XFire|http://xfire.codehaus.org/] site. You'll find source and binary distribution in one file here [xfire-all-1.0-SNAPSHOT.zip|http://dist.codehaus.org/xfire/distributions/xfire-all-1.0-SNAPSHOT.zip].
When you open the zip, you'll find a lot of xfire-xxx.jars. This is because XFire has various options for XML binding, configuration and container-integration. In this howto we'll focus on Aegis for binding and Spring integration, so all you need of the xfire-jars are:
* Copy the xfire-lib-jars from extras/xfire/lib to the AppFuse libs dir and add xfire.version, xfire.dir and xfire.test.dir to lib.properties
* Add the xfire-jars to the libs wich are included in the war (by adding the xfire.dir to the war target in build.xml)
* Add applicationContext-webservice and *.aegis.xml files to the selection of files which go into service.jar (by amending build.xml package-service target)
* Add xfire to build path (by amending properties.xml)
* Add servlet and servlet-mapping to web/WEB-INF/web.xml
* Copy xdoclet-template (aegis-mapping.xdt) to ../../metadata/templates
* Add xfire gen-aegis-mapping target to build.xml
* Copy EchoWebServiceTest to test/service/org/appfuse/service
* Adjust .classpath for eclipse
At line 45 changed 9 lines.
* xfire-core-1.0-SNAPSHOT.jar
* xfire-aegis-1.0-SNAPSHOT.jar
* xfire-spring-1.0-SNAPSHOT.jar
plus some of the third-party libs you'll find in the libs/runtime dir of the zip. Later, we'll use some of the libs from libs/test as well, because I'll show you how you can setup testcases in the webservice field really easy.
!!Add the packages to AppFuse's lib structure [#2]
#Create the following folder structure in your project directory:
To test, if everything worked as expected you can issue
At line 55 changed 4 lines.
lib/
xfire-1.0-SNAPSHOT/
lib/runtime
lib/test
ant test
At line 50 added 2 lines.
in the extras directory. It should execute the EchoWebserviceTest, which creates a webservice using spring, get's the wsdl and sends a message to the webservice.
This test is really simple in it's assemblage but does a powerful test of the whole webservice stack:
At line 61 changed 1 line.
Copy the following files into the xfire-1.0-SNAPSHOT/lib/runtime folder:
{{{
package org.appfuse.service;
At line 63 changed 1 line.
from the root of the zip
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.xfire.spring.AbstractXFireSpringTest;
import org.jdom.Document;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
At line 65 changed 3 lines.
* xfire-core-1.0-SNAPSHOT.jar
* xfire-aegis-1.0-SNAPSHOT.jar
* xfire-spring-1.0-SNAPSHOT.jar
public class EchoWebServiceTest extends AbstractXFireSpringTest {
At line 69 changed 1 line.
from the lib/runtime folder
protected final Log log = LogFactory.getLog(getClass());
At line 71 changed 5 lines.
* jaxen-1.1-beta-8.jar
* jdom-1.0.jar
* stax-1.1.2-dev.jar
* stax-api-1.0.jar
* stax-util-snapshot-20040917.jar
public void setUp() throws Exception {
super.setUp();
}
public void testGetWsdl() throws Exception {
Document doc = getWSDLDocument("Echo");
printNode(doc);
assertValid("//xsd:element[@name=\"echo\"]", doc);
assertValid("//xsd:element[@name=\"echoResponse\"]", doc);
}
public void testCallEcho() throws Exception {
Document response =
invokeService("Echo", "/org/appfuse/service/echo.xml");
printNode(response);
addNamespace("service","http://test.xfire.codehaus.org");
assertValid("//service:echoResponse/service:out[text()=\"Hello world!\"]",response);
At line 77 removed 1 line.
additionally, you need wsdl4j-1.5.2.jar which is available [here|http://www.ibiblio.org/pub/packages/maven2/wsdl4j/wsdl4j/1.5.2/wsdl4j-1.5.2.jar]
At line 79 changed 1 line.
* wsdl4j-1.5.2.jar
}
At line 81 changed 1 line.
some other required libs under libs/runtime you already have in other lib directories of appfuse. Currently, there are no version conflicts with the 1.8 version of appfuse. If there are some with the new 1.9 version, I'll see to sort them out and amend this howto.
protected ApplicationContext createContext() {
return new ClassPathXmlApplicationContext(new String[]{
"org/appfuse/service/applicationContext-test.xml",
"org/appfuse/service/applicationContext-webservice.xml"});
}
At line 83 changed 1 line.
Copy the following files from the libs/test dir to xfire-1.0-SNAPSHOT/lib/test
}
}}}
At line 85 changed 3 lines.
* easymock-1.1.jar
* httpunit-1.5.1.jar
* xmlunit-1.0.jar
invokeService() for example uses the LocalBinding of XFire to send the echo.xml file into the stack and assertValid() uses XPath expressions to examine the (xml)response of the service.
At line 89 changed 1 line.
Now add the following to lib.properties
echo.xml
At line 91 changed 6 lines.
#
# XFire - http://xfire.codehaus.org
#
xfire.version=1.0-SNAPHOST
xfire.dir=${lib.dir}/xfire-${xfire.version}/lib/runtime
xfire.test.dir=${lib.dir}/xfire-${xfire.version}/lib/test
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header/>
<env:Body>
<echo xmlns="http://service.appfuse.org">
<in0>Hello world!</in0>
</echo>
</env:Body>
</env:Envelope>
At line 99 changed 2 lines.
!!Modify the build script to include these packages [#3]
Now that AppFuse is aware of the new libraries, we need to include them in the build process.
You'll find more about testing in section [3].
At line 102 changed 6 lines.
We must modify the package-web target, to add the following to the lib directives for the war task:
{{{
...
<lib dir="${xfire.dir}" includes="*.jar"/>
</war>
}}}
!!The UserWebservice demo inspected [#2]
At line 109 changed 1 line.
Additinally we need to put the xfire libs into the compile-service classpath in properties.xml(just below &lt;path id="service.compile.classpath"&gt;):
The extras package contains a demo showing how to create webservices out of AppFuse. You can install the demo by issuing
At line 111 changed 1 line.
<fileset dir="${xfire.dir}" includes="*.jar"/>
ant install-demo
At line 113 changed 1 line.
and the corresponding test classpath
in the extras/appfuse directory.
Installing the demo actually means adding xdoclet tags to User.java, Role.java in the model and adding an additional interface to UserManagerImpl.java in service/impl.
The additional interface is:
At line 115 changed 2 lines.
<fileset dir="${xfire.test.dir}" includes="*.jar"/>
}}}
package org.appfuse.service;
At line 118 changed 5 lines.
!!Add the XFireServlet definition [#4]
Actually there are two servlets, you could choose. One is the Spring integration of XFire servlet, the other the XFire integration of Spring servlet. You see, the both projects love each other :-)
We'll go with the Spring way, known as XFireConfigurableServlet.
One of the major differences is, that the XFire way creates the Spring appContext in the servlet, something that we don't need, as we already have a Spring enabled web application.
If you want to create a webservice-only application, you can use the org.codehaus.xfire.transport.http.XFireConfigurableServlet instead, so that you don't need to use Springs startup-servlet or listener to load the appContext (if you don't know, what I'm talking about, you probably don't need it :-)
import java.util.List;
At line 124 changed 10 lines.
Now it depends on the appfuse version you have. In versions below 1.9, the servlet-configuration is done under metadata/web in the servlets.xml and servlet-mapping.xml files. From 1.9 on, there is a web.xml file under web/WEB_INF. However, I assume, that you as a web developer know your web.xml.
Open web/WEB-INF/web.xml and add the following:
{{{
<!-- XFire servlet definition and mapping -->
<servlet>
<servlet-name>XFireServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>
import org.appfuse.dao.UserDAO;
import org.appfuse.model.User;
At line 135 removed 4 lines.
<servlet-mapping>
<servlet-name>XFireServlet</servlet-name>
<url-pattern>/servlet/XFireServlet/*</url-pattern>
</servlet-mapping>
At line 140 changed 5 lines.
<servlet-mapping>
<servlet-name>XFireServlet</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
}}}
/**
* WebService Interface
*
* <p><a href="UserWebService.java.html"><i>View Source</i></a></p>
*
* @author <a href="mailto:[email protected]">Mika Goeckel</a>
* @aegis.mapping
*/
public interface UserWebService {
//~ Methods ================================================================
At line 146 changed 14 lines.
Additionally it makes sense to add mime-mappings in the web.xml file:
{{{
<!-- currently the W3C havent settled on a media type for WSDL;
http://www.w3.org/TR/2003/WD-wsdl12-20030303/#ietf-draft
for now we go with the basic 'it's XML' response -->
<mime-mapping>
<extension>wsdl</extension>
<mime-type>text/xml</mime-type>
</mime-mapping>
<mime-mapping>
<extension>xsd</extension>
<mime-type>text/xml</mime-type>
</mime-mapping>
}}}
/**
* Retrieves a user by username. An exception is thrown if now user
* is found.
*
* @param username
* @return User
*/
public User getUser(String username);
At line 161 changed 4 lines.
The DispatcherServlet expects your Spring service definition in a file named XFire-servlet.xml under WEB-INF.
The first definition (SimpleUrlHandlerMapping) tells the DispatcherServlet, that it should pass a request to "services/UserService" to the bean named "userWebservice".
The second is the declaration of this web service. We delegate the requests directly to the bean "userManager" using the interface UserManager. userManager itself is declared in your applicationContext-service.xml.
You see how smooth xfire integrates with Spring again. No boilerplate code between the servlet and the POJO Manager bean. Only a bit of xml wiring them together.
/**
* Retrieves a list of users, filtering with parameters on a user object
* @param user parameters to filter on
* @return List
*
* Tell xfire, which objects it will find in the List
* @aegis.method
* @aegis.method-return-type componentType="org.appfuse.model.User"
*/
public List getUsers(User user);
At line 166 changed 2 lines.
You may have noticed the import. That file contains definitions for beans integrated with Spring from xfire. There you can find the spring bean declarations of "xfire", "xfire.serviceFactory" and some more.
The doc on the xfire site gives the advice to put that in the global application-context declaration in your web.xml, but I thought it is more clear just to expose this to the context that deals with the xfire webservices rather than the root application context.
/**
* Saves a user's information
*
* @param user the user's information
* @throws UserExistsException
*/
public void saveUser(User user) throws UserExistsException;
At line 169 changed 35 lines.
Create a new file und web/WEB-INF named "XFire-servlet.xml" and paste the following into it:
{{{
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<import resource="classpath:org/codehaus/xfire/spring/xfire.xml"/>
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<map>
<entry key="/UserService">
<ref bean="userWebservice" />
</entry>
</map>
</property>
</bean>
<!-- Declare a parent bean with all properties common to both services -->
<bean id="userWebservice" class="org.codehaus.xfire.spring.remoting.XFireExporter">
<property name="serviceFactory">
<ref bean="xfire.serviceFactory" />
</property>
<property name="xfire">
<ref bean="xfire" />
</property>
<property name="serviceBean">
<ref bean="userManager" />
</property>
<property name="serviceInterface">
<value>org.appfuse.service.UserManager</value>
</property>
</bean>
</beans>
/**
* Removes a user from the database by their username
*
* @param username the user's username
*/
public void removeUser(String username);
}
At line 206 changed 1 line.
In principle you'd be done here. You could deploy the application and call the webservice. But because the UserManager has got a method "List getUsers()", we need to tell xfire, what components to expect to get back when calling this method.
The rationale behind the additional interface is to clearly separate the methods that you want to expose as webservice from these that you use inside your application.
Actually there is no way to ignore a method of an exposed interface in xfire as of version 1.0 like the @xfire.property ignore="true" that exists on the model objects (we'll com to that later), so having a second interface is probably the best way to distinct at the moment.
At line 208 changed 13 lines.
!!Create an xdoclet-task in build.xml to generate the aegis.xml mapping files [#5]
XFire doesn't need a .wsdd file like axis, because all services are configured via Spring and optional additional files.
In this howto, we use aegis-mapping, which works on xml descriptors to provide information XFire can't obtain through introspection from the class files.
Another method is to annotate the classes with java 5 annotations (covered by xfire-java5) or with commons-annotations (xfire-annotations).
It's beyond the scope of this howto to explain all possibilities, so we stick with aegis.
Normally you'd create the xxx.aegis.xml mapping files by hand, but as we are used to xdoclet tags in AppFuse, I wrote a template doing that for us.
The xxx.aegis.xml files go into the package structure like property files, so if you want to provide a mapping for org.appfuse.model.User, you just put it there under org.appfuse.model.User.aegis.xml.
What you can do with the mappings is described [here|http://xfire.codehaus.org/Aegis+Binding?nocache]. There's a schema for the xml [here|http://xfire.codehaus.org/schemas/1.0/mapping.xsd].
Create a file named aegis-mapping.xdt in metadata/templates:
Looking on the source code of the interface above, you noticed the uncommon xdoclet tags @aegis.mapping at class level and @aegis.method and @aegis.method-return-type on the getUsers() method.
These are tags which are processed using the aegis-mapping.xdt xdoclet template which was added earlier into metadata/templates by the ant install task.
XFire has several ways of describing the metainformation you need to create a webservice. One of them is [aegis|http://xfire.codehaus.org/Aegis+Binding], another is [JSR181-Annotations|http://xfire.codehaus.org/JSR+181+Annotations].
Aegis works with xml files which contain the metadata. These files look like
At line 223 changed 13 lines.
<mappings<XDtClass:ifHasClassTag tagName="aegis.mapping" paramName="xmlns"> xmlns:<XDtClass:className/>="<XDtClass:classTagValue tagName="aegis.mapping" paramName="xmlns"/>"</XDtClass:ifHasClassTag>>
<mapping<XDtClass:ifHasClassTag tagName="aegis.mapping" paramName="name"> name="<XDtClass:ifHasClassTag tagName="aegis.mapping" paramName="xmlns"><XDtClass:className/>:</XDtClass:ifHasClassTag><XDtClass:classTagValue tagName="aegis.mapping" paramName="name"/>"</XDtClass:ifHasClassTag><XDtClass:ifHasClassTag tagName="aegis.mapping" paramName="uri"> uri="<XDtClass:classTagValue tagName="aegis.mapping" paramName="uri"/>"</XDtClass:ifHasClassTag>>
<XDtMethod:forAllMethods>
<XDtMethod:ifHasMethodTag tagName="aegis.property"><property<XDtMethod:ifHasMethodTag tagName="aegis.property" paramName="name"> name="<XDtMethod:methodTagValue tagName="aegis.property" paramName="name"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifDoesntHaveMethodTag tagName="aegis.property" paramName="name"> name="<XDtMethod:propertyName/>"</XDtMethod:ifDoesntHaveMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.property" paramName="type"> type="<XDtMethod:methodTagValue tagName="aegis.property" paramName="type"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.property" paramName="mappedName"> mappedName="<XDtMethod:methodTagValue tagName="aegis.property" paramName="mappedName"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.property" paramName="nillable"> nillable="<XDtMethod:methodTagValue tagName="aegis.property" paramName="nillable"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.property" paramName="ignore"> ignore="<XDtMethod:methodTagValue tagName="aegis.property" paramName="ignore"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.property" paramName="componentType"> componentType="<XDtMethod:methodTagValue tagName="aegis.property" paramName="componentType"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.property" paramName="style"> style="<XDtMethod:methodTagValue tagName="aegis.property" paramName="style"/>"</XDtMethod:ifHasMethodTag>/></XDtMethod:ifHasMethodTag>
<XDtMethod:ifHasMethodTag tagName="aegis.method"><method<XDtMethod:ifHasMethodTag tagName="aegis.method" paramName="name"> name="<XDtMethod:methodTagValue tagName="aegis.method" paramName="name"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifDoesntHaveMethodTag tagName="aegis.method" paramName="name"> name="<XDtMethod:methodName/>"</XDtMethod:ifDoesntHaveMethodTag>>
<XDtMethod:ifHasMethodTag tagName="aegis.method-return-type">
<return-type<XDtMethod:ifHasMethodTag tagName="aegis.method-return-type" paramName="keyType"> keyType="<XDtMethod:methodTagValue tagName="aegis.method-return-type" paramName="keyType"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.method-return-type" paramName="componentType"> componentType="<XDtMethod:methodTagValue tagName="aegis.method-return-type" paramName="componentType"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.method-return-type" paramName="mappedName"> mappedName="<XDtMethod:methodTagValue tagName="aegis.method-return-type" paramName="mappedName"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.method-return-type" paramName="typeName"> typeName="<XDtMethod:methodTagValue tagName="aegis.method-return-type" paramName="typeName"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.method-return-type" paramName="type"> typeName="<XDtMethod:methodTagValue tagName="aegis.method-return-type" paramName="type"/>"</XDtMethod:ifHasMethodTag>/>
</XDtMethod:ifHasMethodTag>
<XDtMethod:ifHasMethodTag tagName="aegis.method-parameter">
<XDtMethod:forAllMethodTags tagName="aegis.method-parameter">
<parameter<XDtMethod:ifHasMethodTag tagName="aegis.method-parameter" paramName="index"> index="<XDtMethod:methodTagValue tagName="aegis.method-parameter" paramName="index"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.method-parameter" paramName="keyType"> keyType="<XDtMethod:methodTagValue tagName="aegis.method-parameter" paramName="keyType"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.method-parameter" paramName="componentType"> componentType="<XDtMethod:methodTagValue tagName="aegis.method-parameter" paramName="componentType"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.method-parameter" paramName="mappedName"> mappedName="<XDtMethod:methodTagValue tagName="aegis.method-parameter" paramName="mappedName"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.method-parameter" paramName="typeName"> typeName="<XDtMethod:methodTagValue tagName="aegis.method-parameter" paramName="typeName"/>"</XDtMethod:ifHasMethodTag><XDtMethod:ifHasMethodTag tagName="aegis.method-parameter" paramName="type"> typeName="<XDtMethod:methodTagValue tagName="aegis.method-parameter" paramName="type"/>"</XDtMethod:ifHasMethodTag>/>
</XDtMethod:forAllMethodTags>
</XDtMethod:ifHasMethodTag>
<mappings>
<mapping>
<method name="getUsers">
<return-type componentType="org.appfuse.model.User"/>
At line 237 removed 2 lines.
</XDtMethod:ifHasMethodTag>
</XDtMethod:forAllMethods>
At line 241 changed 1 line.
}}}
}}}
in fact the file above was created by the described xdoclet task out of the annotations that were mentioned before.
At line 243 changed 1 line.
Tags recognized are:
In this case the tag describes to xfire what objects it should expect in the List, the getUsers() method returns. This allows xfire to assign the right return type to the method in the wsdl (webservice description language)
which is
At line 245 changed 33 lines.
Class-Level:
@aegis.mapping
xmlns=""
name=""
uri=""
Method-Level:
@aegis.property
name=""
type=""
mappedName=""
nillable=""
ignore=""
componentType=""
style=""
@aegis.method
name=""
@aegis.method-return-type
keyType=""
componentType=""
mappedName=""
typeName=""
type=""
@aegis.method-parameter
index=""
keyType=""
componentType=""
mappedName=""
typeName=""
type=""
<xsd:complexType name="ArrayOfUser">
<xsd:sequence>
<xsd:element name="User" type="ns1:User" nillable="true" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
At line 210 added 1 line.
instead of ArrayOfAnyType which would be generated if we wouldn't give that hint to xfire.
At line 280 changed 1 line.
First create the bit that resolves the getUsers() List component type:
Exactly that problem makes it necessary to add some xdoclet tags to the model we use in AppFuse. User references a List of Roles and Role backreferences User (later more on that).
So the getRoles() method on the User object needs a tag as well:
At line 282 removed 24 lines.
[...}
* @aegis.mapping
*/
public interface UserManager {
[...]
* @aegis.method-return-type componentType="org.appfuse.model.User"
*/
public List getUsers(User user);
[...]
}}}
The same issue comes up in the User type itself and in the Role type.
For User.java this looks like
{{{
[...]
*
* @struts.form include-all="true" extends="BaseForm"
* @hibernate.class table="app_user"
* @aegis.mapping
*/
public class User extends BaseObject implements Serializable, UserDetails {
private static final long serialVersionUID = 3832626162173359411L;
[...]
At line 307 changed 16 lines.
* @struts.validator type="required"
* @hibernate.id length="20" generator-class="assigned" unsaved-value="version"
* @aegis.property name="Username"
*/
public String getUsername() {
return username;
}
[...]
/**
* Returns the full name.
* @aegis.property ignore="true"
*/
public String getFullName() {
return firstName + ' ' + lastName;
}
[...]
* @hibernate.set table="user_role" cascade="save-update" lazy="false"
* @hibernate.collection-key column="username"
* @hibernate.collection-many-to-many class="org.appfuse.model.Role" column="role_name"
At line 225 added 1 line.
Here it is the aegis.property instead of aegis.method-return-type. Don't forget the class level @aegis.mapping tag which serves as a marker for the xdoclet ant task, omitting it is a common mistake I made during the creation of this howto :-)
At line 330 changed 3 lines.
and for Role we have the same Collection resolving plus we need to get rid of the circular reference to User. This is because xfire is about document/literal style webservices. What that is compared to jax-rpc is best explained [here|http://www-128.ibm.com/developerworks/webservices/library/ws-whichwsdl/?ca=dgr-devx-WebServicesMVP03].
document/literal doesn't have references in the xml that it understands. So if we would keep that circular reference, we would get a StackOverflowException.
So now more on the backreference in Role.java:
At line 334 changed 7 lines.
[...]
* @hibernate.class table="role"
* @aegis.mapping
*/
public class Role extends BaseObject implements Serializable, GrantedAuthority {
[...]
* @aegis.property ignore="true"
/**
* @return Returns the users.
* This inverse relation causes exceptions :-( drk
* hibernate.set table="user_role" cascade="save-update"
* lazy="false" inverse="true"
* hibernate.collection-key column="role_name"
* hibernate.collection-many-to-many class="org.appfuse.model.User"
* column="username"
* @aegis.property ignore="true"
At line 345 removed 1 line.
[...]
At line 243 added 3 lines.
why do we ignore this property instead of telling XFire to create an ArrayOfRole like we did above? It's because we need to avoid circular references. If we had a User referencing a Role referencing the same User referencig the same Role, we get a StackOverflowException once we call the service.
This is because xfire is all about document/literal style of webservices which just has no notion of object references because of interoperability issues. You can read more about different styles of webservices [here|http://www-128.ibm.com/developerworks/webservices/library/ws-whichwsdl/].
So our only option is to ignore the backreference. This requires return values to be trees of objects.
At line 348 changed 1 line.
Depending on your specific usage of the webservice you design, you can ignore further properties, but I leave that to you in this example.
What else do you need to do to expose something as a webservice. We already had the aegis xdoclet annotations, which are only necessary in case you have Collection types, but how does xfire know what service you want to expose?
XFire integrates with several IoC frameworks, [PicoContainer|http://picocontainer.codehaus.org/], [Plexus|http://plexus.codehaus.org/], [Loom|http://loom.codehaus.org/] and last but definitiely not least Spring. Besides that XFire can be configured through XFireConfigurableServlet and XFireServlet.
You even don't need a servlet container, because XFire comes with a built in [Jetty|http://jetty.mortbay.org/jetty/index.html] so that you could run completely stand alone.
At line 350 changed 1 line.
Open build.xml and add the following target definition (I personally put it directly before the compile-web target):
In AppFuse we've got Spring and we run inside Tomcat or another ServletContainer (XFire has proven to run in Tomcat, JBoss and Weblogic in my tests, other probably work as well), so we use Spring to tell XFire what to do.
You might have notices the applicationContext-webservice.xml which we copied into the src/service/org/appfuse/service directory:
At line 352 changed 4 lines.
<target name="gen-aegis-mapping" depends="prepare"
description="Generates Aegis Mapping XML descriptors from POJOs">
<taskdef name="xdoclet" classname="xdoclet.DocletTask"
classpathref="xdoclet.classpath"/>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
At line 357 changed 6 lines.
<xdoclet excludedtags="@version,@author"
addedtags="@xdoclet-generated at ${TODAY}"
force="${xdoclet.force}"
destdir="${build.dir}/service/classes"
mergedir="metadata/webservice">
<fileset dir="src/service" includes="**/*.java"/>
<beans>
At line 364 changed 13 lines.
<template templateFile="metadata/templates/aegis-mapping.xdt"
acceptAbstractClasses="false"
prefixWithPackageStructure="true"
destinationFile="{0}.aegis.xml"
havingClassTag="aegis.mapping"/>
</xdoclet>
<xdoclet excludedtags="@version,@author"
addedtags="@xdoclet-generated at ${TODAY}"
force="${xdoclet.force}"
destdir="${build.dir}/dao/classes"
mergedir="metadata/webservice">
<fileset dir="src/dao" includes="**/*.java"/>
<import resource="classpath:org/codehaus/xfire/spring/xfire.xml"/>
<bean name="echoService" class="org.codehaus.xfire.spring.ServiceBean">
<property name="serviceBean" ref="echo"/>
<property name="serviceClass" value="org.codehaus.xfire.test.Echo"/>
<property name="inHandlers">
<list>
<ref bean="addressingHandler"/>
</list>
</property>
</bean>
At line 378 changed 7 lines.
<template templateFile="metadata/templates/aegis-mapping.xdt"
acceptAbstractClasses="false"
prefixWithPackageStructure="true"
destinationFile="{0}.aegis.xml"
havingClassTag="aegis.mapping"/>
</xdoclet>
</target>
<bean id="echo" class="org.codehaus.xfire.test.EchoImpl"/>
<bean name="userService" class="org.codehaus.xfire.spring.ServiceBean">
<property name="serviceBean" ref="userManager"/>
<property name="serviceClass" value="org.appfuse.service.UserWebService"/>
<property name="inHandlers">
<list>
<ref bean="addressingHandler"/>
</list>
</property>
</bean>
<bean id="addressingHandler" class="org.codehaus.xfire.addressing.AddressingInHandler"/>
</beans>
At line 387 changed 1 line.
!!Expose a Manager as a webservice [#6]
I chose this way, because I find it easy to understand and to use, given the structure of AppFuse. So such xml Spring beans files are not uncommon to you.
The import tag at the beginning is our way to include the standard xfire bean definitions (have a look into that file, it's in xfire-spring.jar)
At line 389 changed 2 lines.
Now you've got everything together that's necessary to see it running.
Do an ant gen-aegis-mapping deploy and open the url "services/UserService?wsdl" of your application in your browser. You should see a xfire generated wsdl webservice definition:
We use the ServiceBean from xfire-spring.jar, which is easy to configure. If you ignore the AddressingInHandler (which is to support [ws-addressing|http://www.w3.org/Submission/ws-addressing/] header information), it's really straight forward.
You define an interface (property serviceClass) which will be exposed and an implementation (in this case a reference to userManager, defined in applicationContext-service.xml).
The first definition (echoService) is a simple test service which just returns the string you put in as argument (the one we called in EchoWebServiceTest above).
So to create additional services, you don't need more than add another ServiceBean with your interface and implementation class.
ServiceBean is more complex than that snippet of xml suggests. You can control large portions of the behavior of xfire using your impementations of other properties ServiceBean takes.
The complete processing stack (In-, Out- and Fault-Handlers), ServiceFactory, known XML-Schemas, Bindings, Scope (request, session, application) and much more.
Through controlling the ServiceFactory you even got more options. But the default settings is just what we need here.
Unfortunately the best way to understand the possibilities is reading the code, as there is not much documentation there right now. But be assured, the code is really understandable.
If you want to see your service live, do an
At line 305 added 13 lines.
ant deploy
}}}
and go to http://localhost:8080/appfuse/services with your browser. You should see
{{{
Services:
* Echo
* UserWebService
}}}
Next, you can get the wsdl of these services by calling http://localhost:8080/appfuse/services/UserWebService?wsdl which should produce some lines of xml, something like
{{{
At line 393 changed 12 lines.
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:ns1="http://model.appfuse.org"
xmlns:ns3="http://dao.appfuse.org"
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"
xmlns:soapenc11="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:soapenc12="http://www.w3.org/2003/05/soap-encoding"
xmlns:tns="http://service.appfuse.org"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://service.appfuse.org">
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:ns1="http://model.appfuse.org"
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"
xmlns:soapenc11="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soapenc12="http://www.w3.org/2003/05/soap-encoding"
xmlns:tns="http://service.appfuse.org" xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://service.appfuse.org">
At line 332 added 2 lines.
...
}}}
At line 414 changed 9 lines.
<xsd:element name="address" type="ns1:Address" minOccurs="0" nillable="true" />
<xsd:element name="authorities" type="ns2:ArrayOfGrantedAuthority" minOccurs="0" nillable="true" />
<xsd:element name="confirmPassword" type="xsd:string" minOccurs="0" nillable="true" />
<xsd:element name="credentialsExpired" type="xsd:boolean" minOccurs="0" />
<xsd:element name="credentialsNonExpired" type="xsd:boolean" minOccurs="0" />
<xsd:element name="email" type="xsd:string" minOccurs="0" nillable="true" />
<xsd:element name="enabled" type="xsd:boolean" minOccurs="0" />
<xsd:element name="firstName" type="xsd:string" minOccurs="0" nillable="true" />
<xsd:element name="lastName" type="xsd:string" minOccurs="0" nillable="true" />
!!Testing without container [#3]
Unfortunately you can't call the xfire webservice like you can with axis by just appending parameters like form parameters yet. Thatis a feature which is currently under development.
But you can test your service with [eclipse-wtp|http://www.eclipse.org/webtools/], there you find under Run->Launch the Webservices Explorer a mighty tool to test your services.
It's a little bit tricky to get to the service using the explorer, because you have to know that you need to click on the secondmost icon in the right upper corner of the window (WSDL page hoover).
If you click there you get a "WSDL Main" entry in the Navigator box on the left hand, click there and you get a input box for your wsdl url on the right side. There you enter http://localhost:8080/appfuse/services/UserWebService?wsdl and the explorer loads your wsdl and shows the methods that your interface exposes.
Click on "getUsers" and you see select boxes for all properties of the input User object you can use to select the users you'd like to be displayed. For the time just klick on the "Go" button at the very end of that list and you'll get a list of the users currently registered in your database.
At line 424 changed 9 lines.
<xsd:element name="password" type="xsd:string" minOccurs="0" nillable="true" />
<xsd:element name="passwordHint" type="xsd:string" minOccurs="0" nillable="true" />
<xsd:element name="phoneNumber" type="xsd:string" minOccurs="0" nillable="true" />
<xsd:element name="roles" type="ns1:ArrayOfRole" minOccurs="0" nillable="true" />
<xsd:element name="username" type="xsd:string" minOccurs="0" nillable="true" />
<xsd:element name="version" type="xsd:int" minOccurs="0" nillable="true" />
<xsd:element name="website" type="xsd:string" minOccurs="0" nillable="true" />
</xsd:sequence>
</xsd:complexType>
This is an easy, but unfortunately very manual way of testing your service. As a test infected AppFuse developer you probably want to use unit tests for your services.
We've seen a simple testcase above (EchoWebServiceTest) now we want to dive deeper into testing of webservices with the UserWebServiceTest:
{{{
package org.appfuse.service;
At line 434 changed 9 lines.
<xsd:complexType name="ArrayOfRole">
<xsd:sequence>
<xsd:element name="Role" type="ns1:Role" nillable="true" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="Role">
<xsd:sequence>
<xsd:element name="authority" type="xsd:string" minOccurs="0" nillable="true" />
<xsd:element name="description" type="xsd:string" minOccurs="0" nillable="true" />
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.appfuse.dao.UserDAO;
import org.appfuse.model.User;
import org.codehaus.xfire.spring.AbstractXFireSpringTest;
import org.jdom.Document;
import org.jmock.Mock;
import org.jmock.core.constraint.IsEqual;
import org.jmock.core.matcher.InvokeOnceMatcher;
import org.jmock.core.stub.ReturnStub;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
At line 444 changed 9 lines.
<xsd:element name="name" type="xsd:string" minOccurs="0" nillable="true" />
<xsd:element name="version" type="xsd:int" minOccurs="0" nillable="true" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="Address">
<xsd:sequence>
<xsd:element name="address" type="xsd:string" minOccurs="0" nillable="true" />
<xsd:element name="city" type="xsd:string" minOccurs="0" nillable="true" />
<xsd:element name="country" type="xsd:string" minOccurs="0" nillable="true" />
public class UserWebServiceTest extends AbstractXFireSpringTest {
At line 454 changed 9 lines.
<xsd:element name="postalCode" type="xsd:string" minOccurs="0" nillable="true" />
<xsd:element name="province" type="xsd:string" minOccurs="0" nillable="true" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ArrayOfUser">
<xsd:sequence>
<xsd:element name="User" type="ns1:User" nillable="true" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
protected final Log log = LogFactory.getLog(getClass());
At line 464 changed 9 lines.
</xsd:schema>
<xsd:schema targetNamespace="http://acegisecurity.org" elementFormDefault="qualified" attributeFormDefault="qualified">
<xsd:complexType name="ArrayOfGrantedAuthority">
<xsd:sequence>
<xsd:element name="GrantedAuthority" type="ns2:GrantedAuthority" nillable="true" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="GrantedAuthority">
<xsd:sequence>
public void setUp() throws Exception {
super.setUp();
}
public void testGetWsdl() throws Exception {
Document doc = getWSDLDocument("UserWebService");
printNode(doc);
assertValid("//xsd:complexType[@name=\"User\"]", doc);
assertValid("//xsd:complexType[@name=\"Role\"]", doc);
}
public void testGetUser() throws Exception {
// Setup testharness
User testData = new User("tomcat");
testData.setEnabled(true);
Mock userDAO = new Mock(UserDAO.class);
// because we can't extend MockObjectTestCase we create new instances for once(), eq() and returnValue()
InvokeOnceMatcher once = new InvokeOnceMatcher();
IsEqual eq = new IsEqual("tomcat");
ReturnStub returnValue = new ReturnStub(testData);
userDAO.expects(once).method("getUser").with(eq).will(returnValue);
UserManager service = (UserManager) getContext().getBean("userManager");
service.setUserDAO((UserDAO)userDAO.proxy());
// invoke webservice
Document response =
invokeService("UserWebService", "/org/appfuse/service/getUser.xml");
At line 474 changed 9 lines.
<xsd:element name="authority" type="xsd:string" minOccurs="0" nillable="true" />
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
<xsd:schema targetNamespace="http://service.appfuse.org" elementFormDefault="qualified" attributeFormDefault="qualified">
<xsd:element name="getUsers">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="in0" type="ns1:User" nillable="true" minOccurs="1" maxOccurs="1" />
//printNode(response);
// verify result
userDAO.verify();
addNamespace("service","http://service.appfuse.org");
addNamespace("model","http://model.appfuse.org");
assertValid("//service:getUserResponse/service:out[model:username=\"tomcat\"]",response);
assertValid("//service:getUserResponse/service:out[model:enabled=\"true\"]",response);
}
At line 484 changed 9 lines.
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="getUsersResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="out" type="ns1:ArrayOfUser" nillable="true" minOccurs="1" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
protected ApplicationContext createContext() {
return new ClassPathXmlApplicationContext(new String[]{
"org/appfuse/service/applicationContext-test.xml",
"org/appfuse/service/applicationContext-webservice.xml"});
}
At line 494 changed 9 lines.
</xsd:element>
<xsd:element name="setUserDAO">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="in0" type="ns3:UserDAO" nillable="true" minOccurs="1" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="setUserDAOResponse">
}
}}}
At line 504 changed 9 lines.
<xsd:complexType />
</xsd:element>
<xsd:element name="getUser">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="in0" type="xsd:string" nillable="true" minOccurs="1" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
The testGetUser shows that the test really calls through to your Manager class.
It shows as well how you can use JMock even if you don't extend JMock's MockObjectTestCase which you would do if you use the BaseManagerTestCase of AppFuse.
Because we need the functionality of the Spring/XFire TestCase classes, we can't use the BaseManagerTestCase but fortunately there is a way around.
At line 514 changed 9 lines.
<xsd:element name="getUserResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="out" type="ns1:User" nillable="true" minOccurs="1" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="saveUser">
<xsd:complexType>
I hope I could explain how to use XFire together with AppFuse a little bit. Once you started playing arounf with XFire you probably find out more and more details that aren't even mentioned
in this HowTo, please feel free to extend it.
At line 524 changed 159 lines.
<xsd:sequence>
<xsd:element name="in0" type="ns1:User" nillable="true" minOccurs="1" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="saveUserResponse">
<xsd:complexType />
</xsd:element>
<xsd:complexType name="UserExistsException" />
<xsd:element name="UserExistsException" type="tns:UserExistsException" />
<xsd:element name="removeUser">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="in0" type="xsd:string" nillable="true" minOccurs="1" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="removeUserResponse">
<xsd:complexType />
</xsd:element>
</xsd:schema>
<xsd:schema targetNamespace="http://dao.appfuse.org" elementFormDefault="qualified" attributeFormDefault="qualified">
<xsd:complexType name="UserDAO" />
</xsd:schema>
</wsdl:types>
<wsdl:message name="getUserResponse">
<wsdl:part element="tns:getUserResponse" name="parameters" />
</wsdl:message>
<wsdl:message name="getUsersRequest">
<wsdl:part element="tns:getUsers" name="parameters" />
</wsdl:message>
<wsdl:message name="removeUserResponse">
<wsdl:part element="tns:removeUserResponse" name="parameters" />
</wsdl:message>
<wsdl:message name="setUserDAOResponse">
<wsdl:part element="tns:setUserDAOResponse" name="parameters" />
</wsdl:message>
<wsdl:message name="removeUserRequest">
<wsdl:part element="tns:removeUser" name="parameters" />
</wsdl:message>
<wsdl:message name="saveUserRequest">
<wsdl:part element="tns:saveUser" name="parameters" />
</wsdl:message>
<wsdl:message name="setUserDAORequest">
<wsdl:part element="tns:setUserDAO" name="parameters" />
</wsdl:message>
<wsdl:message name="UserExistsException">
<wsdl:part element="tns:UserExistsException" name="UserExistsException" />
</wsdl:message>
<wsdl:message name="getUsersResponse">
<wsdl:part element="tns:getUsersResponse" name="parameters" />
</wsdl:message>
<wsdl:message name="saveUserResponse">
<wsdl:part element="tns:saveUserResponse" name="parameters" />
</wsdl:message>
<wsdl:message name="getUserRequest">
<wsdl:part element="tns:getUser" name="parameters" />
</wsdl:message>
<wsdl:portType name="UserManagerPortType">
<wsdl:operation name="getUsers">
<wsdl:input message="tns:getUsersRequest" name="getUsersRequest" />
<wsdl:output message="tns:getUsersResponse" name="getUsersResponse" />
</wsdl:operation>
<wsdl:operation name="setUserDAO">
<wsdl:input message="tns:setUserDAORequest" name="setUserDAORequest" />
<wsdl:output message="tns:setUserDAOResponse" name="setUserDAOResponse" />
</wsdl:operation>
<wsdl:operation name="getUser">
<wsdl:input message="tns:getUserRequest" name="getUserRequest" />
<wsdl:output message="tns:getUserResponse" name="getUserResponse" />
</wsdl:operation>
<wsdl:operation name="saveUser">
<wsdl:input message="tns:saveUserRequest" name="saveUserRequest" />
<wsdl:output message="tns:saveUserResponse" name="saveUserResponse" />
<wsdl:fault message="tns:UserExistsException" name="UserExistsException" />
</wsdl:operation>
<wsdl:operation name="removeUser">
<wsdl:input message="tns:removeUserRequest" name="removeUserRequest" />
<wsdl:output message="tns:removeUserResponse" name="removeUserResponse" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="UserManagerHttpBinding" type="tns:UserManagerPortType">
<wsdlsoap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="getUsers">
<wsdlsoap:operation soapAction="" />
<wsdl:input name="getUsersRequest">
<wsdlsoap:body use="literal" />
</wsdl:input>
<wsdl:output name="getUsersResponse">
<wsdlsoap:body use="literal" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="setUserDAO">
<wsdlsoap:operation soapAction="" />
<wsdl:input name="setUserDAORequest">
<wsdlsoap:body use="literal" />
</wsdl:input>
<wsdl:output name="setUserDAOResponse">
<wsdlsoap:body use="literal" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="getUser">
<wsdlsoap:operation soapAction="" />
<wsdl:input name="getUserRequest">
<wsdlsoap:body use="literal" />
</wsdl:input>
<wsdl:output name="getUserResponse">
<wsdlsoap:body use="literal" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="saveUser">
<wsdlsoap:operation soapAction="" />
<wsdl:input name="saveUserRequest">
<wsdlsoap:body use="literal" />
</wsdl:input>
<wsdl:output name="saveUserResponse">
<wsdlsoap:body use="literal" />
</wsdl:output>
<wsdl:fault name="UserExistsException">
<wsdlsoap:fault name="UserExistsException" use="literal" />
</wsdl:fault>
</wsdl:operation>
<wsdl:operation name="removeUser">
<wsdlsoap:operation soapAction="" />
<wsdl:input name="removeUserRequest">
<wsdlsoap:body use="literal" />
</wsdl:input>
<wsdl:output name="removeUserResponse">
<wsdlsoap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="UserManager">
<wsdl:port binding="tns:UserManagerHttpBinding" name="UserManagerHttpPort">
<wsdlsoap:address location="http://localhost:8080/appfuse-xfire/services/UserService" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
}}}
The next part of this howto deals with unit-testing of your webservices. You can do that on various levels of the xfire-stack.
The third part is about client-generation and will follow (hopefully) soon.
So stay tuned!
The former tutorial is available [here|AppFuseXFireOld]

Back to AppFuseXFire, or to the Page History.