I should point out that what I am about to show is not necessarily the quickest way of getting the Google Web API doGoogleSearch up and running. In this particular case Google already provides a Google Web API Developer's Kit which you could use to do all of the functionality I am about to describe, indeed some of the precompiled Google Web API package will be interchangeable with what I am about to generate using the Axis WSDL2Java tool but I hope this example will give you some ideas that can be applied to web services in general and not just the Google Web API.
In my last blog entry, I showed how you can access doSpellingSuggestion with Spring and since this service only returns a String there was no particular complexity involved. doGoogleSearch returns a more complex response and so I am going to use Axis' WSDL2Java tool to generate the beans that I will need to capture this more complex response. Remember that WSDL2Java is intended to produce Axis specific code and since I'm using Spring to do the web service access I don't actually need all the generated Axis code. I downloaded Axis 1.3 and created an Ant script containing the relevant Axis WSDL2Java Ant task.
<?xml version="1.0" encoding="UTF-8"?>
<project name="googleApi" default="wsdl2java" basedir=".">
<path id="axis.classpath">
<fileset dir="./lib">
<include name="**/*.jar" />
</fileset>
</path>
<taskdef resource="axis-tasks.properties" classpathref=""axis.classpath" />
<target name="wsdl2java">
<mkdir dir="src"/>
<axis-wsdl2java
output="src"
testcase="false"
helpergen="true"
verbose="true"
serverside="false"
url="http://api.google.com/GoogleSearch.wsdl" >
<mapping
namespace="urn:GoogleSearch"
package="com.google" />
</axis-wsdl2java>
</target>
</project>
The important thing to notice about this is that I am using the helpergen="true" option by doing this I am asking WSDL2Java to store any Axis specific code away from the valuable beans that I actually want. After running this script I have a collection of generated classes, some of which I don't actually need. The following list shows the classes that are produced by running WSDL2Java against the Google WSDL with the classes I don't need indicated via a strikethrough.
- DirectoryCategory.java
DirectoryCategory_Helper.javaGoogleSearchBindingStub.java- GoogleSearchPort.java
- GoogleSearchResult.java
GoogleSearchResult_Helper.javaGoogleSearchService.javaGoogleSearchServiceLocator.java- ResultElement.java
ResultElement_Helper.java
After deleting all the helper classes, BindingStub and Locator I have removed all the Axis specific code. I am left with the beans I need to capture my responses to doGoogleSearch and also a convenient port interface class.
In this case we are expecting the web service to return something more complicated than a String so we need to extend JaxRpcPortProxyFactoryBean in order to map the service response given to our generated beans. Modifying the example given in the Chapter 16. Remoting and web services using Spring I get:
package fullgoogleapi;
import com.google.DirectoryCategory;
import com.google.GoogleSearchResult;
import com.google.ResultElement;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.encoding.TypeMapping;
import javax.xml.rpc.encoding.TypeMappingRegistry;
import org.apache.axis.encoding.ser.BeanDeserializerFactory;
import org.apache.axis.encoding.ser.BeanSerializerFactory;
import org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean;
public class GooglePortProxyFactoryBean extends JaxRpcPortProxyFactoryBean {
protected void postProcessJaxRpcService(Service service) {
TypeMappingRegistry registry = service.getTypeMappingRegistry();
TypeMapping mapping = registry.createTypeMapping();
registerBeanMapping(mapping, GoogleSearchResult.class, "GoogleSearchResult");
registerBeanMapping(mapping, ResultElement.class, "ResultElement");
registerBeanMapping(mapping, DirectoryCategory.class, "DirectoryCategory");
registry.register("http://schemas.xmlsoap.org/soap/encoding/", mapping);
}
protected void registerBeanMapping(TypeMapping mapping, Class type, String name) {
QName qName = new QName("urn:GoogleSearch", name);
mapping.register(type, qName,
new BeanSerializerFactory(type, qName),
new BeanDeserializerFactory(type, qName));
}
}
Now to add a service interface. This is essentially a slightly modified version of the generated class GoogleSearchPort.java but in the service interface we no longer throw RMI exceptions. Contrast this to the previous doSpellingSuggest example as I will define all the methods properly and not just to satisfy the endpoint matching so that I can now use all of the methods if I want to.
package fullgoogleapi;
public interface GoogleSearchService {
public byte[] doGetCachedPage(String key, String url);
public String doSpellingSuggestion(String key, String phrase);
public com.google.GoogleSearchResult doGoogleSearch(String key, String q, int start, int maxResults, boolean filter, String restrict, boolean safeSearch, String lr, String ie, String oe);
}
My new applicationContext.xml file incorporating the extended JaxRpcPortProxyFactoryBean, generated port interface class and the service interface above will look something like this:
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="googleService" class="fullgoogleapi.GooglePortProxyFactoryBean">
<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>com.google.GoogleSearchPort</value>
</property>
<property name="serviceInterface">
<value>fullgoogleapi.GoogleSearchService</value>
</property>
</bean>
</beans>
The test class will now look something like this:
package fullgoogleapi;
import com.google.GoogleSearchResult;
import org.apache.commons.collections.BeanMap;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApplication {
private static ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
private static GoogleSearchService service = (GoogleSearchService) context.getBean("googleService");
public static void main(String [] args) throws Exception {
String key="<Insert Google Web API key here>";
String test = "webservie";
System.out.println(service.doSpellingSuggestion(key,test));
String q ="shrdlu winograd maclisp teletype";
int start = 0;
int maxResults = 10;
boolean filter = true;
String restrict = "";
boolean safeSearch = true;
String lr = "";
String ie = "latin1";
String oe = "latin1";
GoogleSearchResult gsr = service.doGoogleSearch(key, q, start, maxResults, filter, restrict, safeSearch, lr, ie, oe);
BeanMap googleResults = new BeanMap(gsr);
System.out.println(googleResults.toString());
}
}
If you have used Axis before I hope you'll agree that this is considerably simpler than wiring together all the generated BindingStubs and Locators. In this example I've reduced the role of WSDL2Java from an Axis code generator to a simple WSDL aware bean generator. The minute you run into problems you will probably have to go away and learn how to use Axis's TCP Monitor etc. Using this method, if you are lucky, you may get away with not even having to look through a WSDL or even browse a SOAP message. It would be nice if you knew all the intricacies of SOAP, Axis and web services but with this method I think it becomes slightly less important than it was before. I would expect you to be more productive at consuming web services using the Spring enabled web services approach than the method that Axis previously offered.