Hi all, I finally have some time to sum up my experiences with using XDoclet to get a web service deployed to IBM Websphere 6.0. It's been an interesting adventure, and hopefully this will help others? This was my first time working with XDoclet and IBM Websphere, so I'm probably doing things that are not required. If anyone has suggestions on how I can clean up some of the steps I had to go through to get things working, I'm all ears!! Note: Any opinions expressed in this email are strictly my own and do not reflect the opinions of the company I work for and any of it's subsidiaries, affiliates, etc.. ( figured I should that in here...just in case ). Also note, this is going to be a long email!! :) I want to be detailed enough, so that someone starting out ( like myself ) has a complete understanding of what I was doing. Like others, I turned to XDoclet to assist in the generation of the appropriate J2EE interfaces and deployment descriptors. The project I am working on requires us to develop some Web Services and deploy them onto IBM Websphere 6.0. So I started with the typical Stateless Session bean and followed the documentation provided by XDoclet to put in the appropriate tags within my comments: Bean Class Comments ------------------- /** * @ejb.bean * name="MyAdapter" * view-type="all" * @ejb.home * generate="local,remote" * @ejb.interface * generate="local,remote,service-endpoint" * service-endpoint-class="com.services.ws.MyAdapterService * @wsee.port-component * name="MyAdapterService" * display-name="MyAdapterService" */ Someone had posted that they had problems with the generation of the home and remote interfaces. If they specified just generating the remote versions of those interfaces, XDoclet still put entries in the ejb-jar.xml for the local verisons of those interfaces. I ran into the same problem, so like others, I was forced to generate those classes so that my bean could be deployed properly. Build.xml Snip --------------- <ejbdoclet ejbSpec="2.1" excludedTags="@author,@version,@todo,@see" verbose="true" destdir="${src.dir}" force="${force.generation}"> <packagesubstitution packages="session" substituteWith="interfaces"/> <fileset dir="${src.dir}"> <include name="${ejb.dir}/**/*Bean.java"/> </fileset> <homeinterface/> <remoteinterface/> <localhomeinterface/> <localinterface/> <service-endpoint/> <deploymentdescriptor destdir="${src.dir}/${ejb.dir}/META-INF/ejb" useIds="true" validateXML="true"/> <webSphere destdir="${src.dir}/${ejb.dir}/META-INF/ejb" useIds="true" validateXML="true"/> </ejbdoclet> In my build process I actually put all of the generated files into my source tree....I realize people frown on this, but it's the approach I took. Basically, each service has it's own build.xml that will be responsible for cleaning up all of the generated files during an "ant clean". This creates all of the EJB interfaces and deployment descriptors. A Note on the src directory structure: I defined the following structure for each of the services I wanted to develop. com/services/ws/service1 interfaces - this is the destination directory for all generated EJB interfaces ( this directory is created a build time ) session - this is the directory where I store my SessionBean implementation. ws - this is the destination directory for all generated Service Endpoint interfaces. ( this directory is created a build time ) entity - this is the directory that contains all Entity Bean implementations. META-INF - this is the destination directory for all generated deployment descriptors. app - this directory contains the application.xml, and the vendor's extension application.xml files. In IBM's case, ibm-application-bnd.xmi and ibm-applicaiton-ext.xmi. Notice, the files in this directory have to be hand-crafted. XDoclet does not provide a mechanism to auto-generate these files. ejb - this is the destination directory for all EJB related deployment descriptors ( this directory is created a build time ) ws - this is the destination directory for all Web Services related deployment descriptors ( this directory is created a build time ). Okay, from there I generate the Web Services deployment descriptors ( webservices.xml, .wsdl file, and the IBM web services counter-parts ). I ran into a problem here. When I tried to use the <deploymentdescriptor> subtask within the wseedoclet task it didn't generate the webservices.xml correctly. More specifically, within the wseedoclet task you define <packageNamespaceMapping> that ends up being the targetNamespace within your .wsdl file. Well, when the webservices.xml file was generated using the <deploymentdescriptor> subtask, IBM complained that the <wsdl-port> element was not properly defined. I discovered that there appears to be a relationship between the targetNamespace in the .wsdl file and the <wsdl-port> element in the webservices.xml. So, the <wsdl-port> looked like this when using the <deploymentdescriptor> subtask within XDoclet: <wsdl-port>MyAdapterServer</wsdl-port> But examples given by IBM, showed the wsdl-port defined differently, more specifically: ( Assume targetNamespace within the wsdl file is ws.services.com ) <wsdl-port xmls:pfx="http://ws.services.com">pfx:MyAdapterServers</wsdl-port> So I jumped over to the WSDL2Java ant task provided by IBM in order to generate the webservices.xml, mapping.xml, and the IBM specific ibm-webservices-bnd.xmi and ibm-webservices-ext.xmi. Build.xml Snip: ----------------- <wseedoclet wsdlFile="wsdl/${wsdl.name}.wsdl" wseeSpec="1.1" excludedTags="@version,@author,@todo" verbose="true" destdir="${src.dir}/${ws.dir}/META-INF/ws" force="yes"> <packageNamespaceMapping packages="${ws.packages}" namespace="${ws.namespace}"/> <fileset dir="${src.dir}"> <include name="${ws.dir}/session/*Bean.java"/> </fileset> <wsdl/> </wseedoclet> <WSDL2JavaTask url="" output="${src.dir}/${ws.dir}/META-INF/ws" role="develop-server" container="ejb" genjava="no"> <!-- I dont' generate java classes, because I already generated my interfaces using XDoclet --> </WSDL2JavaTask> IBM doesn't comment their ant tasks very well....properly because they'd much prefer you to use their IDE to do all of this stuff. So the role and container attributes I just set to some arbitruary values. I am not sure what the valid values are? With this in place, all of the descriptors are generated correctly. There are a few issues with using WSDL2Java task from IBM: 1. In the webservices.xml, you'll notice: <ejb-link>??SET THIS TO ejb-name ELEMENT OF ejb-jar.xml??</ejb-link>. So you have to figure a way of replacing the "SET THIS TO..." string with the value of the <ejb-name> element within ejb-jar.xml. How I got around this was I used the <xmlproperty> task to read in the actual ejb-jar.xml file. Then I do a replace with the value in the <ejb-name> element: Build.xml Snip: ----------------- <xmlproperty file="${src.dir}/${ws.dir}/META-INF/ejb/ejb-jar.xml" semanticAttributes="true"/> <replace file="${src.dir}/${ws.dir}/META-INF/ws/webservices.xml" token="??SET THIS TO ejb-name ELEMENT OF ejb-jar.xml??" value="${ejb-jar.enterprise-beans.session.ejb-name}"/> I realize there is an issue with this, if you have more than one SessionBean defined in your ejb-jar.xml. Which <ejb-name> do you use if there are multiple <ejb-name> elements defined??? I haven't worked out the details on this yet. Right now, for me, I will only have one SessionBean defined in my ejb-jar.xml - so it's good enough for now. Definately open to suggestions here! 2. IBM makes assumptions!! Whenever you use the WSDL2Java task, it tacks on _PortType to the name of your service. So, I had to put in a simple replace command to strip off the _PortType. If I left the _PortType on my service name, it didn't match up with binding within my .wsdl file: Build.xml Snip: ----------------- <replace file="${src.dir}/${ws.dir}/META-INF/ws/webservices.xml" token="_PortType" value=""/> 3. In the .wsdl file you'll notice: <soap:address location="REPLACE_WITH_ACTUAL_URL"/>. I had to jump through some hoops to get this URL set correctly!! I'll leave this for now....we'll come back to it. Okay, so now we have all of the interfaces and deployment descriptors generated. Next step, compiling and packaging. I don't do anything special here. Compile all of the source, copy all appropriate deployment descriptors and package up a JAR file that contains the session bean, interfaces and deployment descriptors. Then I create an EAR file that will contain that JAR file. So at the end of the compile and packaging, I end up with a resultant EAR file. The reason: When I go to enable my web service, IBM creates a .war file and inserts it into the EAR file. In order to enable your Web Service for IBM, you have to run it through their <EndpointEnablerTask> task. Again, this task is not documented very well. Build.xml Snip: ----------------- <EndpointEnablerTask earfile="${dist}/${ear.name}.ear"> <property key="verbose" value="true"/> <property key="http.routerModuleNameSuffix" value="HTTPRouter"/> <property key="${jar.name}.http.contextRoot" value="...."/> </EndpointEnablerTask> The "verbose" property key has no affect - you can try to set it to "false", but you will still see verbose output during the process. Must be hardcoded within the task. I'm not sure what the routerModuleNameSuffix does....I went with the same value outlined in the example within the JavaDocs. The .http.contextRoot will be the <context-root> set within your application.xml: Application.xml Snip: ----------------------- <web> <web-uri>adapter.war</web-uri> <context-root>...</context-root> </web> It appears that you have to prefix the .http.contextRoot with the name of the jar file. So if your jar file name is adapter.jar, then you'd define the following: <property key="adapter.http.contextRoot value="..."/> If that prefix is not defined correctly, then it will default the contextroot the name of the jar file. The name of the war file will also default to the same name as the jar file. But you can change it by using: <property key="${jar.name}.http.routerModuleName" value="..."/>. Some stuff you should know about EndpointEnablerTask: ---------------------------------------------------------- 1. Once the EndpointEnablerTask has completed, it will have injected a war file into your EAR and modified your application.xml to add the web module mapping. 2. Again, IBM makes assumptions. In the web.xml file contained within the WAR file, you'll notice that the <url-pattern> will ALWAYS be prefixed with "services". So in my case <url-pattern>services/MyAdapterServicePort</url-pattern> 3. So in order to hit your web service from a client, you're gonna have to make the following request: http://host:port/context-root/services/..... where context-root is equal to the <context-root> element defined within your application.xml. So, this brings us back to <soap:address location="REPLACE_WITH_ACTUAL_URL"/> within the .wsdl file. Given the assumptions made by IBM, we have to ensure that the soap:address location matches the actual URL. I got this working because each service has it's own build.xml file. So each service defines a ws.context property that will be used in the EndpointEnablerTask when setting the http.contextRoot. Like so: Build.xml Snip: ----------------- <EndpointEnablerTask earfile="${dist}/${ear.name}.ear"> <property key="verbose" value="true"/> <property key="http.routerModuleNameSuffix" value="HTTPRouter"/> <property key="${jar.name}.http.contextRoot" value="${ws.context}"/> </EndpointEnablerTask> Now we know after the contextRoot of our URL, IBM forces us to use services - so we can hard code that part of the URL. The final part is the actual name of the service you are requesting. You can solve this a bunch of different ways, you could define a property, you could pull the value out of the webservices.xml, or you could just hard code the entire URL within your build.xml, etc., - totally up to you. So once you run your EAR file through the EndpointEnablerTask, you can take that EAR file and deploy it to the IBM Websphere application server. Please note, whenever I deploy I always use their Admin screens. Which takes me through an 8 step process before I can actually deploy the thing. I'm not sure if the EAR file created using this process can be just dropped into the appropriate directory and be hot-deployed by the application server?? Well, there it is. Hopefully some will find this helpful? All feedback and comments are welcome! :) Regards, Jeff |