Tuesday, April 28, 2009

Calling SOAP Web Services in Google App Engine

We have taken the plunge and are begining to develop a couple of internal apps using GWT and Google App Engine (GAE) for Java.

The app that I'm writing needs to make SOAP calls to get external data from a service. The service provides a set of WSDLs and we use Apache Axis to generate code to call this service. However, when I tried using the generated code in the Google Eclipse Plugin environment, I received the following exception:

Caused by: java.security.AccessControlException: access denied (java.net.SocketPermission xxx.xxxx.com resolve)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
at java.security.AccessController.checkPermission(AccessController.java:427)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at com.google.appengine.tools.development.DevAppServerFactory$CustomSecurityManager.checkPermission(DevAppServerFactory.java:76)
at java.lang.SecurityManager.checkConnect(SecurityManager.java:1031)
at java.net.InetAddress.getAllByName0(InetAddress.java:1134)
at java.net.InetAddress.getAllByName(InetAddress.java:1072)
at java.net.InetAddress.getAllByName(InetAddress.java:1008)
at java.net.InetAddress.getByName(InetAddress.java:958)
at java.net.InetSocketAddress.(InetSocketAddress.java:124)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.(SSLSocketImpl.java:350)
at com.sun.net.ssl.internal.ssl.SSLSocketFactoryImpl.createSocket(SSLSocketFactoryImpl.java:69)
at org.apache.axis.components.net.JSSESocketFactory.create(JSSESocketFactory.java:92)
at org.apache.axis.transport.http.HTTPSender.getSocket(HTTPSender.java:191)
at org.apache.axis.transport.http.HTTPSender.writeToSocket(HTTPSender.java:404)
at org.apache.axis.transport.http.HTTPSender.invoke(HTTPSender.java:138)
... 45 more


This was annoying, but not a huge surprise as it is well known and documented that GAE has its own JVM implementation which prevents things like the creation of socket connections and threads. I did some investigation to see if others had solved this particular problem, calling a SOAP Web Service from GAE, but didn't really find any good answers.

In any case, I understood that HTTP communication in GAE must occur through either the URL Fetch Service or java.net.URL class. So, I needed to find a way to make Axis use one of these methods instead of opening sockets directly.

After learning more about how Axis works and then doing more searching, I came across SimpleHTTPSender. This class is an Axis Handler that uses only java.net.URL and friends for HTTP communication.

Problem solved.

Now, on to solving the next one... :)

16 comments:

Paul Gordon said...

You rock! I had exactly the same problem...just trying out your solution for myself.

Timo said...

Sorry Paul. I spoke too soon. I was able to get this working in the Eclipse plugin environment. However, this is failing when I deploy to App Engine.

Shiv Kumar said...

Timo

Thanks for the SimpleHttpSender link. I have an identical issue (java.security.AccessControlException: access denied (java.net.SocketPermission xxx.xxxx.com resolve) -- axis client calling Webservices. That helped me run the app on local host. However when I deploy on the AppEngine, it fails with ClassNotFoundException for javax.xml.soap.SOAPException. This class is defined in saaj.jar and it does exist in WEB-INF/lib when deployed.

Were you able to make your app run on the appengine?

------------------------


java.lang.NoClassDefFoundError: javax/xml/soap/SOAPException
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
at java.lang.Class.getMethod0(Unknown Source)
at java.lang.Class.getMethod(Unknown Source)
at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_$8.run(Class_.java:316)
at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_$8.run(Class_.java:311)
at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_$11.run(Class_.java:389)
at java.security.AccessController.doPrivileged(Native Method)
at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_.runPrivileged(Class_.java:387)
at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_.getMethod(Class_.java:311)
at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_.getMethod(Class_.java:161)
at org.apache.axis.message.RPCParam.clinit(RPCParam.java:65)
at org.apache.axis.client.Call.getParamList(Call.java:2087)
at org.apache.axis.client.Call.invoke(Call.java:2364)
at org.apache.axis.client.Call.invoke(Call.java:1812)

Timo said...

Hi Jason,

Sorry for the belated reply. I was not able to get this to work in app engine so I had to change the design of my application to work around this limitation. I get the same ClassNotFoundException that you mention. In the next couple of months I'm sure there will be some fixes and/or toolkits that will make calling SOAP services much easier.

Shiv Kumar said...

Timo

I got confirmation on lack of Soap Web Services support from the Google group forum (posed the same question there).

http://groups.google.com/group/google-appengine-java/browse_thread/thread/71551291802f54e

Jason

tee said...

Hi Timo,
What did you eventually wind up doing? I've run into the same problem... :(

Anonymous said...

Your blog is very nice... i like your blog ....

Daniel said...

How about Axis2, have you got that working.

Unknown said...

for anyone else stumbling here from google, see http://code.google.com/p/sfdc-wsc/

you can use sfdc-wsc to consume an axis2 webservice from app engine.

the process for me was to save my .jws as a .java, compile to a .class, use Axis2's Java2WSDL to generate a .wsdl (--style WRAPPED --use LITERAL), then use com.sforce.ws.tools.wsdlc to generate an {endpoint}.jar tied to the wsdl.

from there add the {endpoint}.jar and wsc.jar to your app lib directory and use code like this from app engine:
com.sforce.soap.{endpoint}.jws.SoapConnection connection = com.sforce.soap.{endpoint}.jws.Connector.newConnection(new ConnectorConfig());

String result = connection.{endpoint opname}({args});

Aman said...

This is fantastic, thank you!
You can read this article
http://bygsoft.wordpress.com/2010/01/09/cloudy-combo-google-app-engine-and-amazon-s3-combo-pack/

Nick L said...

Excellent post - ever see this error?

Exception in thread "main" com.google.apphosting.api.ApiProxy$CallNotFoundException: The API package 'urlfetch' or call 'Fetch()' was not found.
at com.google.apphosting.api.ApiProxy.makeSyncCall(ApiProxy.java:95)
at com.google.appengine.api.urlfetch.URLFetchServiceImpl.fetch(URLFetchServiceImpl.java:34)
at com.sforce.ws.transport.GAEHttpTransport.getContent(GAEHttpTransport.java:98)
at com.sforce.ws.transport.SoapConnection.send(SoapConnection.java:104)
at com.sforce.soap.ISBN.SoapConnection.IsValidISBN13(SoapConnection.java:1)
at MyWeather.main(MyWeather.java:24)
ERROR: JDWP Unable to get JNI 1.2 environment, jvm->GetEnv() return code = -2
JDWP exit error AGENT_ERROR_NO_JNI_ENV(183): [../../../src/share/back/util.c:820]

Nick L said...

Fixed. Turns out you cant run that class in a console app, which is what I was doing to test before deploying to GAE. Dropped that jar into the my main GAEJ project, deployed and it runs great. Problem solved.

Unknown said...

Could you please help in solving the mentioned issue using SimpleHTTPSender

Timo said...

Hi Rebecca,

I haven't done much with app engine lately, but I think kevin.osborne's solution is the one you want to take a look at.

Nick L said...

yea, the sfdc-wsc is the gold standard for web services within GAE. Very well written and well used - so lots of people have experience with it.

But, I'll take another look at my solution later today and see if there is anything else I could add. If so, I'll post here.

che--- said...

Sadly, the sfdc-wsc has too many limitations... real life enterprise wsdl's are IMHO not supported very well due to them. For example, i'm fighting with a "IllegalArgumentException: Operation.output got more than one element", because the output is a compley type.