Exploring a Top-down SOAP Service: Part III
Come read Part III of Eli Corrales's series about exploring a top-down SOAP service. This time, we'll add a shape calculator, an informational response, and more!
Join the DZone community and get the full member experience.
Join For FreeLast time, we established a working baseline. We assured ourselves that we could make a change in the WSDL, generate the code, copy/merge the generated copy to our working copy, install/deploy, and test it. Let's keep going.
Latest Code
You can get the latest code here.
And for our dependency on our child (Shape Calculator project), here.
Add Shape Calculator
We want our external (API) runAllPendingRequestsNoStopOnError() to actually DO something, so we add the calculator and we call the very same operation on it:
@Generated(value = "org.apache.cxf.tools.wsdlto.WSDLToJava", date = "2016-09-21T13:21:57.958-04:00", comments = "Apache CXF 3.1.7")
public class ShapeCalculatorWebServiceImpl implements ShapeCalculatorWebService {
@Autowired
private ShapeCalculatorService calculator;
@Generated(value = "org.apache.cxf.tools.wsdlto.WSDLToJava", date = "2016-09-21T13:21:57.958-04:00")
public void runAllPendingRequestsNoStopOnError() {
LOG.debug("\n\n\nExecuting operation runAllPendingRequestsNoStopOnError\n\n\n");
try {
int numRun = calculator.runAllPendingRequestsNoStopOnError();
} catch (java.lang.Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
Build.Deploy.Run.Test
This time, when we try hitting our runAllPendingRequestsNoStopOnError() operation, we see a lot more output from the console. We see hibernate get into the act, so we are definitely doing what we want. However, there is no feedback at all. And that calculator method does return the number of calculations run, which might be useful, but we can't pass that back to our SOAP client the way we have the response set up.
One more test. Let's temporarily shut down MySQL... (again please refer to the several previous articles on this)
Now let's have SOAPUI do its thing.
Notice that the Eclipse console has a long trace of exceptions because Hibernate (in the base Shape Calculator project) cannot access the data store.
But there was absolutely no indication of any problems in the SOAPUI response. Nada.
This is not good, and it doesn't meet our previous requirements.
Add Response To Operation In WSDL
So our goal is to have the operation we've been exercising to return some sort of response, rather than void.
We go back to edit and enhance the WSDL. Let's take a look at it. You'll notice that in the <wsdl:message...>
section, and in the <wsdl:portType......>
section, and in the <wsdl:binding....>
section, everything indicates that our single operation is one-way. In. We send the runAllPendingRequestsNoStopOnError message, and that's it.
So we need to add a corresponding Out portion to every place there's an In.
Here is our first stab at the complete WSDL, with the new parts added.
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://ws.service.shape.calc.eli.com/"
targetNamespace="http://ws.service.shape.calc.eli.com/"
>
<wsdl:message name="runAllPendingRequestsNoStopOnError">
</wsdl:message>
<wsdl:message name="runAllPendingRequestsNoStopOnErrorResponse">
</wsdl:message>
<wsdl:portType name="ShapeCalculatorWebService">
<wsdl:operation name="runAllPendingRequestsNoStopOnError">
<wsdl:input name="runAllPendingRequestsNoStopOnError" message="tns:runAllPendingRequestsNoStopOnError">
</wsdl:input>
<wsdl:output name="runAllPendingRequestsNoStopOnErrorResponse" message="tns:runAllPendingRequestsNoStopOnErrorResponse">
</wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="ShapeCalculatorWebServiceSoapBinding" type="tns:ShapeCalculatorWebService">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="runAllPendingRequestsNoStopOnError">
<wsdl:input name="runAllPendingRequestsNoStopOnError">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="runAllPendingRequestsNoStopOnErrorResponse">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="ShapeCalculatorWebService">
<wsdl:port name="ShapeCalculatorWebService" binding="tns:ShapeCalculatorWebServiceSoapBinding">
<soap:address location="http://localhost:8080/web-service-soap-bottom-up/services/ws/ShapeCalculatorWebService"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Now we clean and generate-sources, adjust our code, etc., install, run it and test it.
Remember that any time you change the WSDL, you'll need to go re-do your SOAPUI project:
http://localhost:8080/ws-soap-top-down-p3/services/ShapeCalculatorWebService?wsdl
You might also want to view it in the browser to make sure it did in fact change.
So, this time, we have something more.
Let's try the error test by shutting down MySQL.
OK, now at least we know it will not fail silently.
Add Informational Response
What we would like to do, only for now as a test, is to be at least able to return the int value from the calculator.runAllPendingRequestsNoStopOnError()
as part of the SOAP response.
So we add a response type to the response message:
<wsdl:message name="runAllPendingRequestsNoStopOnErrorResponse">
<wsdl:part name="response" element="RunAllResponse" />
</wsdl:message>
However, that's not enough because the element value has to be in a namespace. Before we worry about that too much, let's try another generate-sources.
Re-generating gives us this:
[ERROR] Part <response> in Message
<{http://ws.service.shape.calc.eli.com/}
runAllPendingRequestsNoStopOnErrorResponse>
referenced Type
<{http://ws.service.shape.calc.eli.com/}somename>
cannot be found in the schemas
Add new WSDL section: TypesWhat schemas?
We need a new section that will define the elements we declared in the <wsdl:message.....>
. We declared an element of type RunAllResponse, so now we need to define that element.
<wsdl:types>
<schema>
...... something here defining element....
</schema>
</wsdl:types>
It really isn't my intention to teach too much about WSDLs but I hope you have noticed that all of the WSDL sections or blocks, exist in the wsdl: namespace. And that namespace was declared at heading of the <wsdl:definitions......>
.
Right now, the above code, has the <schema>
without an explicit namespace. If we try to generate-sources, we'll get:
[ERROR] Failed to execute goal org.apache.cxf:cxf-codegen-plugin:3.1.7:wsdl2java (generate-sources) on project ws-soap-top-down-p3: Execution generate-sources of goal org.apache.cxf:cxf-codegen-plugin:3.1.7:wsdl2java failed: org.apache.cxf.wsdl11.WSDLRuntimeException: Fail to create wsdl definition file:/C:/_dzone-article-sandbox/eclipse-workspace/ws-soap-top-down-p3/WebContent/wsdl/ShapeCalcWebService.wsdl: WSDLException (at /wsdl:definitions/wsdl:types/schema): faultCode=INVALID_WSDL: Encountered illegal extension element 'schema' in the context of a 'javax.wsdl.Types'. Extension elements must be in a namespace other than WSDL's. -> [Help 1]
Which then brings us to the question: What Namespace?
Well.. we can put the namespace we need AND define it right in the <schema...>
block, just like the namespace wsdl: was defined and used.
Here it the above <wsdl:types......>
block re-done:
<wsdl:types>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
...... some definition here.....
</xsd:schema>
</wsdl:types>
Now let's take a stab at defining a simple type; our RunAllResponse, which we want to be an integer.
<wsdl:types>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
<xsd:element name="RunAllResponse" type="xsd:int" />
</xsd:schema>
</wsdl:types>
What we see above is that a new custom element (RunAllResponse) is defined as an integer, and that integer type itself has already been defined by the XMLSchema, and due to our xsd: namespace
declaration, it exists there, thus the xsd:int
type.
Ok, we have defined our new element. And we had previously tried to declare its use in our outbound or response message. Before we go any further, let's see our complete WSDL again, with what we've just added.
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://ws.service.shape.calc.eli.com/"
targetNamespace="http://ws.service.shape.calc.eli.com/"
>
<wsdl:types>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
<xsd:element name="RunAllResponse" type="xsd:int" />
</xsd:schema>
</wsdl:types>
<wsdl:message name="runAllPendingRequestsNoStopOnError">
</wsdl:message>
<wsdl:message name="runAllPendingRequestsNoStopOnErrorResponse">
<wsdl:part name="response" element="RunAllResponse" />
</wsdl:message>
<wsdl:portType name="ShapeCalculatorWebService">
<wsdl:operation name="runAllPendingRequestsNoStopOnError">
<wsdl:input name="runAllPendingRequestsNoStopOnError" message="tns:runAllPendingRequestsNoStopOnError">
</wsdl:input>
<wsdl:output name="runAllPendingRequestsNoStopOnErrorResponse" message="tns:runAllPendingRequestsNoStopOnErrorResponse">
</wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="ShapeCalculatorWebServiceSoapBinding" type="tns:ShapeCalculatorWebService">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="runAllPendingRequestsNoStopOnError">
<wsdl:input name="runAllPendingRequestsNoStopOnError">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="runAllPendingRequestsNoStopOnErrorResponse">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="ShapeCalculatorWebService">
<wsdl:port name="ShapeCalculatorWebService" binding="tns:ShapeCalculatorWebServiceSoapBinding">
<soap:address location="http://localhost:8080/web-service-soap-bottom-up/services/ws/ShapeCalculatorWebService"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Let's try another generate-sources.
And it works. We have a clean console output. Here is the next version of the auto-generated implementation, showing only the operation:
/* (non-Javadoc)
* @see com.eli.calc.shape.service.ws.ShapeCalculatorWebService#runAllPendingRequestsNoStopOnError()*
*/
@Generated(value = "org.apache.cxf.tools.wsdlto.WSDLToJava", date = "2016-09-22T01:50:25.464-04:00")
public int runAllPendingRequestsNoStopOnError() {
LOG.info("Executing operation runAllPendingRequestsNoStopOnError");
try {
int _return = 0;
return _return;
} catch (java.lang.Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
Reconcile Sources (In-use vs Generated)
Since the above is the generated version, we're going to manually reconcile it with the one we took and used from our first success (that we moved to another package) in the previous article.
Really, the only change that matters is we need to change our working version to return an int
. And we also need to change the interface class to return an int.
ShapeCalculatorWebService
Here is the updated, working copy of the interface:
package com.eli.calc.shape.service.ws;
import javax.annotation.Generated;
import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
/**
* This class was generated by Apache CXF 3.1.7
* 2016-09-21T12:04:15.336-04:00
* Generated source version: 3.1.7
*
*/
@WebService(targetNamespace = "http://ws.service.shape.calc.eli.com/", name = "ShapeCalculatorWebService")
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
@Generated(value = "org.apache.cxf.tools.wsdlto.WSDLToJava", date = "2016-09-21T12:04:15.336-04:00", comments = "Apache CXF 3.1.7")
public interface ShapeCalculatorWebService {
@WebMethod
@Oneway
@Generated(value = "org.apache.cxf.tools.wsdlto.WSDLToJava", date = "2016-09-21T12:04:15.336-04:00")
public int runAllPendingRequestsNoStopOnError();
}
ShapeCalculatorWebServiceImpl
And here is the updated, working copy of the implementation:
/**
* Please modify this class to meet your needs
* This class is not complete
*/
package com.eli.calc.shape.service.ws.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Generated;
import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import com.eli.calc.shape.service.ShapeCalculatorService;
import com.eli.calc.shape.service.ws.ShapeCalculatorWebService;
/**
* This class was generated by Apache CXF 3.1.7
* 2016-09-21T13:21:57.958-04:00
* Generated source version: 3.1.7
*
*/
@javax.jws.WebService(
serviceName = "ShapeCalculatorWebService",
portName = "ShapeCalculatorWebService",
targetNamespace = "http://ws.service.shape.calc.eli.com/",
wsdlLocation = "file:/C:/_dzone-article-sandbox/eclipse-workspace/ws-soap-top-down-p2/WebContent/wsdl/ShapeCalcWebService.wsdl",
endpointInterface = "com.eli.calc.shape.service.ws.ShapeCalculatorWebService")
@Generated(value = "org.apache.cxf.tools.wsdlto.WSDLToJava", date = "2016-09-21T13:21:57.958-04:00", comments = "Apache CXF 3.1.7")
public class ShapeCalculatorWebServiceImpl implements ShapeCalculatorWebService {
@Generated(value = "org.apache.cxf.tools.wsdlto.WSDLToJava", date = "2016-09-21T13:21:57.958-04:00")
private static final Logger LOG = LoggerFactory.getLogger(ShapeCalculatorWebServiceImpl.class);
@Autowired
private ShapeCalculatorService calculator;
/* (non-Javadoc)
* @see com.eli.calc.shape.service.ws.ShapeCalculatorWebService#runAllPendingRequestsNoStopOnError()*
*/
@Generated(value = "org.apache.cxf.tools.wsdlto.WSDLToJava", date = "2016-09-21T13:21:57.958-04:00")
public int runAllPendingRequestsNoStopOnError() {
LOG.debug("\n\n\nExecuting operation runAllPendingRequestsNoStopOnError\n\n\n");
try {
int numRun = calculator.runAllPendingRequestsNoStopOnError();
return numRun;
} catch (java.lang.Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
}
Yes, we could have instead copied the latest auto-generated interface to our working ( src/main/java)
area, but that introduces something new:
@XmlSeeAlso({com.eli.calc.shape.service.ws.types.ObjectFactory.class})
and I don't want to deal with that yet, as we don't need it.
Build.Deploy.Start.WSDL.Test
Make sure MySQL is running.
We stumbled onto an error during startup. There are lots of traces, but it all boils down to this:
Caused by: java.io.FileNotFoundException: C:\_dzone-article-sandbox\eclipse-workspace\ws-soap-top-down-p2\WebContent\wsdl\ShapeCalcWebService.wsdl (The system cannot find the path specified)
I purposely left something unchanged so we would see this problem. Remember that we are maintaining a working copy of our service implementation class, and we are just bringing over the changes we want from our auto-generated class. (We may switch that around and start using the auto-generated class and add our working code to it).
Also, I have been creating new projects for every part of these articles, i.e., there is a ws-soap-top-down-p1, -p2, -p3, and so on.
The point is that the above error was caused by wsdlLocation.
Our project POM, as of now, has in it <wsdl>
, but not <wsdlLocation>
, and it has well-known value:
<wsdl>${basedir}/WebContent/wsdl/(your wsdl service name here).wsdl
<wsdl>
and that is a very common suggestion in many online articles I've seen.
The above causes the following to be generated in the service implementation:
In my case, since my current working project is "-p3", it can't find the file. What could we do?
- change the "-p2" to "-p3" (yuck)
- use the auto-generated impl class and move our changes to it (yes, we could)
- add a new tag
<wsdlLocation>
, and change from absolute path toclasspath:
(see below) - comment out that entire
wsdLocation="..blah..blah.."
line in the annotated code (eh... yuck)
<wsdlLocation> Absolute vs Classpath
CXF wsdl2java plugin used the <wsdl>.... </wsdl>
to generate the above. We can instead provide our own <wsdlLocation>.... </wsdlLocation>
. This is a common online suggestion as well.
For this option, we add to our project POM:
<wsdlLocation>classpath:wsdl/ShapeCalcWebService.wsdl</wsdlLocation>
I added that immediately below the <wsdl>.... </wsdl>
.
Generating sources again gives us this:
If I move that change over and re-install , start up server, etc.. we get a clean start.
Let's view the working WSDL...
http://localhost:8080/ws-soap-top-down-p3/services/
We click that link:
http://localhost:8080/ws-soap-top-down-p3/services/ShapeCalculatorWebService?wsdl
Time for our test in SOAPUI. Let's create a fresh SOAP project with our WSDL URL.. and run the test.
Ok, looks better. If you recall from our prior tests, the SOAP response used to be blank. Now we are seeing a response.
Latest Code
You can get the latest code here.
What Is Next?
In the next article, we are going to explore more about the schema and separate out the schema information into a separate file. We will create the types that we need to meet our previous requirements.
So, stay tuned!
Opinions expressed by DZone contributors are their own.
Comments