Wednesday, December 21, 2005

Part I:Using Spring to consume web services; simple example using Google Web APIs doSpellingSuggestion

I have recently been investigating the Spring Framework and turned my attention towards Spring's offerings in the field of web services. I have had several previous encounters with web services and have even tried hand coding SOAP messages (an experience not to be recommended). The Jakarta IO-taglib is very useful for knocking up simple stuff (like XML-RPC pings) and Apache Axis has come a long way since Apache SOAP in making things much simpler especially with its JWS instant web services. As you would expect from Spring there is also a nice way to create web services (ServletEndpointSupport) but as the Axis method is so simple already it probably doesn't really add that much to this area.

Almost paradoxically it seems to me that since Axis introduced JWS it has always been much easier to produce web services than it is to consume them. The real effort is needed on the client side rather than at the server. Granted Axis has some great tools like WSDL2Java that can generate most of the Java I need to talk to a web service but despite peoples best efforts to persuade me that this is all quite simple, once I've actually looked at the source code produced it makes me recoil in horror. SoapBindings, Stubs, Skeletons and ServiceLocators, yuck! I know my horror is based on a fundamental lack of understanding but I don't really want to be bothered dealing with stuff like that, I'd rather things like that were done by someone or something else. WSDL2Java wouldn't exist at all if this stuff was easy to produce now would it?

Enter Spring...

It isn't the simplest thing in the world to consume web services with Spring but IMHO it is much easier than wiring the WSDL2Java generated Axis code together.

Now let us look a example, I am using the Google Web API which gives me enough functionality to play with (simple and more advanced) and hopefully there are enough examples of its use already out there for you to make a good comparison. It has been my experience that although Spring seems to provide tools for many diverse areas it can be quite difficult to find good explanatory documentation with examples, such was my finding in the area of web services (I know I should go out and buy Spring in Action). Granted there is the Spring Documentation chapter Chapter 16. Remoting and web services using Spring but initially I found that I had to resort to finding an example on a blog. The blog example I found first was on Petrik de Heus's blog. I downloaded the code, added the necessary libraries and an Ant build script and dug out my Google Web API key and got it working. However, I couldn't understand the example, the code went to great effort to create a GoogleMessageBean and register this bean in some mapping gizmo but at no point did I actually see the bean being used, I found the example quite confusing but at least I was now armed with the right class name (JaxRpcPortProxyFactoryBean). Petrik's example all seemed a little too magic and although Spring does magic sometimes, I felt that something was not right about this. I found my answer after a little Googling around, browsing the Spring's API docs and especially in Bill Siggelkow's Weblog and ultimately I found a great example using Babelfish from the Spring in Action source code (available for free from the Manning site, I know I'm a cheapskate for downloading the source code for a book I haven't yet bought but as they say Talent Borrows, Genius Steals). For something as simple as the Google Spelling Suggestion that returns a String it turns out that you do not need even need to extend JaxRpcPortProxyFactoryBean, you just need to create port and service interfaces (and even the service interface is optional).

applicationContext.xml


<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
<bean id="googleService" class="org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean">

<property name="serviceFactoryClass">
<value>org.apache.axis.client.ServiceFactory</value>
</property>

<property name="wsdlDocumentUrl">
<value>http://api.google.com/GoogleSearch.wsdl</value>
</property>

<property name="namespaceUri">
<value>urn:GoogleSearch</value>
</property>

<property name="serviceName">
<value>GoogleSearchService</value>
</property>

<property name="portName">
<value>GoogleSearchPort</value>
</property>

<property name="portInterface">
<value>spellsuggest.SpellingSuggestRemote</value>
</property>

<property name="serviceInterface">
<value>spellsuggest.SpellingSuggestService</value>
</property>

</bean>
</beans>

SpellingSuggestRemote interface


package spellsuggest;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface SpellingSuggestRemote extends Remote {

public String doSpellingSuggestion(String key, String input) throws RemoteException;

/* The following methods are here but are not to be used.
* They only here in order to match the endpoint methods in the WSDL.
*/


public String doGoogleSearch() throws RemoteException;
public String doGetCachedPage() throws RemoteException;

}

SpellingSuggestService interface


package spellsuggest;

public interface SpellingSuggestService {
public String doSpellingSuggestion(String key, String input);
}

Test class


package spellsuggest;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

private static ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
private static SpellingSuggestService service = (SpellingSuggestService) context.getBean("googleService");

public static void main(String[] args) {
String key = "<Insert Google Web API key here>";
String test = "webservie";
System.out.println(service.doSpellingSuggestion(key,test));
}

}

...and that is all there is to it. In my next blog entry I will explain how to access everything that the Google Web API has to offer in a Spring enabled way.

13 comments:

Mark McLaren said...

Please assist me in executing the above webservice. I did exactly the same as mentioned. I am using spring with tomcat server 5.x.



I get the follwing error ...



javax.xml.rpc.ServiceException: Error processing WSDL document:

javax.xml.rpc.ServiceException: Cannot find service: urn:GoogleSearch}GoogleSearchService



Your help is kindly appreciated...



Thanks,

Anand
Note: Comment imported. Original by Anonymous at 2006-02-10 06:17

Mark McLaren said...

In short, I'm not sure Anand. I ran the above code again this morning and it appears to be still working (although I did see a few Bad Gateway 502 errors but these pass). 502 errors are more to do with the Google web service failing to handle the demand placed on upon it than it is with any error with my coding (granted if my code was intended for a production system I'd need to add exception handling to cope with 502 errors).





The above code can be executed from the command line - it doesn't need Tomcat. If I were you, I'd first check to see if you can run it standalone before putting it onto a Tomcat server.





Also something else to check would be your library versions, I got the above code to work using Axis 1.3 and Spring 1.2.6. There may be minor changes needed for it to work with other library versions (I don't actually know).





Note also that the above code only exercises the spelling suggest part of the Google API. For details of how to do a full Google search see Part II:Using Spring to consume web services; advanced example using Google Web APIs doGoogleSearch.




Note: Comment imported. Original by markmc website: http://content.mark-mclaren.info/ at 2006-02-10 10:08

Mark McLaren said...

Hi mark,

Thanks a lot for your kindly response. I debugged and found that there was an issue with my axis jar. I was using axis 1.1. jar which was giving problem Now replaced the jar with older one axis.jar and found to be working fine, Thanks

Anand
Note: Comment imported. Original by Anonymous at 2006-02-13 07:29

Mark McLaren said...

Mark, Thanks for the article. To help other readers out, there are some other things to know about: 1) the example from 'Spring in Action' no longer works because the BabelFish service has been de-activated. 2) To run your example, you need a Google API key, and you need to sign up at http://www.google.com/apis/. 3) A big problem I had was with the proxy server on our network. If you're trying to consume a web service with Spring or Axis (or probably anything), and you are running inside a firewall, you need to set JVM-level parameters for proxySet=true, proxyHost,

proxyPort, http.proxyHost, http.proxyPort. The first 3 are used at Spring startup time to resolve the WSDL. The other 2 are used at runtime to actually call the service. Thanks, K
Note: Comment imported. Original by Ken Krueger at 2006-03-24 20:58

Mark McLaren said...

I was able to easily get your example running. I also applied it to a project that I'm consuming a large WSDL that contains a ton of nested complex types. Every thing worked fine (I made a mock service until the custmers service was ready). However, the customers WSDL used attributes instead of elements. Now when the service call comes back all of the items are attached, but all of the data elements are null. Do you have to do something to get the BeanDeserializerFactory to handle attributes? I hope this question made sense.



Thanks in advance for any advice

Derrick
Note: Comment imported. Original by Derrick at 2006-05-06 22:15

Mark McLaren said...

Hi Derrick,



I have a second blog entry which talks about using Axis to generate the beans necessary to consume complex types. I hope this could be useful to you.
Note: Comment imported. Original by markmc website: http://content.mark-mclaren.info/ at 2006-05-07 19:06

Mark McLaren said...

Thanks for your article, but I found that I could run the example for the error: IOException parsing XML document from class path resource [applicationContext.xml]; nested exception is java.io.FileNotFoundException: class path resource [applicationContext.xml] cannot be opened because it does not exist.



Where should I put the applicationContext.xml file? Now I put the file in the same pakage with the Java files
Note: Comment imported. Original by Anonymous at 2007-05-16 02:15

Mark McLaren said...

The above example uses a ClassPathXmlApplicationContext. This means that the applicationContext.xml should be located somewhere in your classpath. The above example expects the file to be in the root of the classpath but if you want to put it inside a package you have to prefix the filename with the package directory. e.g. "com/somecompany/applicationContext.xml".





HTH



Mark
Note: Comment imported. Original by markmc website: http://content.mark-mclaren.info/ at 2007-05-16 19:05

Mark McLaren said...

Hi Mark

Thanks for the example..

I have a slight problem running it..

i'm getting the following error

"The java class could not be loaded. java.lang.ExceptionInInitializerError"

Can you suggest what is going wrong


Note: Comment imported. Original by Yogesh at 2007-09-27 08:16

Mark McLaren said...

Hi Yogesh, I cannot tell from the exception, could you post the complete stacktrace?



I am pleased that people still find this tutorial useful even when Google SOAP search API has long since closed its doors to the public. Perhaps as developers we should learn from this that you cannot always rely on free services!





Mark


Note: Comment imported. Original by markmc website: http://content.mark-mclaren.info/ at 2007-09-27 19:22

Mark McLaren said...

Well i have spring-remoting in the classpath ... couldnt get rid of this exception ... also axis2, commons-logging,spring.jar is in the classpath. I ran the app by hitting Run from eclipse ... Not sure why the bean is not getting recognized ... should i run under a container like tomcat

.......

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'googleService' defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is java.lang.NoClassDefFoundError: javax/xml/rpc/JAXRPCException

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:451)

at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:249)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:155)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:246)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:160)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:285)

at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:352)

at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:122)

at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:66)

at com.kk.google.Test.(Test.java:8)

Caused by: java.lang.NoClassDefFoundError: javax/xml/rpc/JAXRPCException

at java.lang.Class.getDeclaredConstructors0(Native Method)

at java.lang.Class.privateGetDeclaredConstructors(Class.java:1618)

at java.lang.Class.getConstructor0(Class.java:1930)

at java.lang.Class.getDeclaredConstructor(Class.java:1301)

at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:54)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:752)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:717)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:386)
Note: Comment imported. Original by Gunny at 2007-10-24 21:07

Mark McLaren said...

Mark just wanna let you know that i got rid of the previous exception ... i need jax-rpc from sun to eliminate the exception.

Thanks for the nice article
Note: Comment imported. Original by Gunny at 2007-10-24 21:29

Mark McLaren said...

I have tried similar example and got below error.



Caused by: org.xml.sax.SAXException: Deserializing parameter 'Output': could not find deserializer for type {https://abctest.svr.us.net/UsrControlRoom/wsdl/linkAction}Output

at org.apache.axis.message.RPCHandler.onStartChild(RPCHandler.java:277)

at org.apache.axis.encoding.DeserializationContext.startElement(DeserializationContext.java:1035)



Please help me to resolve this.



Thanks,

Laks
Note: Comment imported. Original by Laks at 2009-10-20 19:50