Wednesday, July 14, 2004

Arbitrary Complex Data in CFMX Web Services

I've been banging my head against a brick wall with this for a couple of days now, and it's finally dawned upon me that I'm trying to achieve something that the existing mechanism just isn't designed to handle:

I want to put a web-service interface layer on my CFCs.
I originally wanted to have my web-service interface CFCs in a sub-directory under the webroot, so they were publicly accessible but had their own Application.cfm; I also wanted to keep my back-end CFCs outside the webroot.
(maybe it's my background at an IT Security company, but the thought of putting my back-end business logic and data components in an environment where anyone can get at them just makes me feel queasy)
But, after spending an age battling with inter-component references and JRUN mappings , all to no avail, I eventually condeded defeat.
Looks like you HAVE to have your WS interface CFCs in the same directory. Mappings just get ignored.
Oh yeah, and don't use underscores in your CFC names. They won't work.
Oh, and don't use mixed-case either.

Nice one MM...

Anyway, I decided that I'd deal with security later (my old colleagues at the security company would be horrified, but sometimes you just have to suck it and see, y'know?)

Onto the main issue:

I wanted to create a CFC type to be returned from all web service methods, which wouldhave these fields:

  • intReturnCode

    an integer constant, indicating whether the request succeeded, and if not, what went wrong.

  • vcErrorDescription

    a description of the error, if any (e.g. "Could not find the person you asked for" )

  • objReturnData

    This would hold the actual data to be returned, whatever that may be - a query, an object, an array....whatever.


All sounds fine and dandy, and I had a nice object model designed to achieve this - then I started trying to get it work.

Over the last two days, I've seen more hair-pulling, teeth-clenching, blood-pressure-heightening, eye-popping, chair-kickingly frustrating error messages than I have done for years. There's been some great ones, from the old favourite

"Could not generate stub objects for web service invocation"

through

"Web service operation 'listPersonsByInitial' with parameters {VCEMAIL={[whatever]},VCINITIAL={[whatever]},VCPASSWORD={[whatever]},} could not be found."
...just after adding the web service 'listPersonsByInitial'

but this is my personal favourite:

"java.lang.IncompatibleClassChangeError : Dependent CFC type(s) have been modified. Please refresh your web service client."
When did I first get this error? When I was refreshing my web service client page, because I'd modified the dependent CFCs. GRRRRRRRRRRRRR! I seemed to get this every five minutes or so, even when I hadn't even changed the code!

Anyway, after swearing a lot, spending ages googling for other people with the same errors ( try googling for 'coldfusion "java.lang.IncompatibleClassChangeError"' ) and coming up with a few people with similar problems but NO applicable solutions, I posted some messages to the CF-Talk mailing list. After a few replies along the lines of "er....have you tried (one of the first things I tried) ?" I ended up saying:

"If anyone can come out with a 'I've done this and it worked first time, and I've never had any problems with it...' I'll be eternally grateful if they can walk me through it. Otherwise, I'm giving up - this is way too much of a headache."

That was yesterday. To date, no one has....

But it dawned on me just now that I'm actually trying to get the engine to do something which it fundamentally wasn't designed to do.

It seems like the WSDL which describes the web service gets cached on the client. If you want to change the web service, every client has to update their WSDL. Which is fine, I guess it makes sense that way, and I could live with that in development if it worked consistently.

The problem is that in order for a web service to be able to understand your data, the WSDL needs to describe any non-native type (pretty much anything more complicated than a string or basic number) right down to the last detail. What fields are in your complex data, what type they are... so if you're trying to return arbitrary complex data, you're not enforcing a rigid type in your data, you're leaving it to run-time. So the WSDL can't possibly describe your data ahead of time.

Bottom line: it's my own fault. I'm trying to push the SOAP/web services mechanism way outside of its scope. What I actually need is a whole host of facade classes, one for every bit of copmlex data, even if it's only slightly different.

Bugger.

Back to the drawing board then....

7 comments:

Anonymous said...

There is a solution to get the CFC's outside the webroot. You need to add a mapping within the jrun evironment as coldfusion silently ignores its own mapping when using components as webservice. At the top of my head I think the mapping is locate in web.xml

now if you could help me with returning arrays of complex types,*that* would be nice ;-)

bolke at xs <four> all dot nl

Krystoph said...

Holy crap on a cracker. There is a ton of spam responses to your post.

Ok, this isnt spam, but i am running into the same problem you had but its a little different.

I am trying to connect to and SAP web service however I continually get this error...

"Web service operation "Zpersname" with parameters {BADGE={55555},} could not be found.

I could connect to the service when it didnt expect any variable to be passed and it would send a response. But now that I am sending data I get that lame error which seems to mean NOTHING. Its like saying "sorry, the door is no longer here." Dont know where it went, why its not there and what happend to make it disapear.

Ok, so back to the task at hand, sure as sh*t the variable that I am passing data to is specified as a complex type. so my question is

(Drum roll)
Will having the SAP guys change the field to simpleType help me pass data to them from the CFInvoke tag? Or am I really way off base here.

Any insight you could provide would be greatly appreciated.

Thanks,
~krys

grantmr said...

please for the love of god tell me the solution to 'could not generate stub objects'!!!

i can't find anything useful to solve this problem...

thanks - grantmr{at}gmail[dotcom]

Alistair Davidson said...

GrantM -

Ah, the good old "Could Not Generate Stub Objects For Web Service Invocation" chestnut.....

This crops up all the time, for any number of reasons - maybe the wsdl file is invalid, maybe it couldn't connect to the URL at all, or maybe just because it's a Thursday morning...

Unfortunately, once you've got this error, sometimes the only way to
get rid of it - even when you've fixed the actual problem - is to stop CF, delete everything under the (cfroot)/stubs directory and restart cf.

Just use the usual debugging strategies - look at the wsdl url in a browser, make sure it's available and parses correctly, etc. If you have a url, i can point a webservice call at it from here just to check if the problem is the webservice or your call to it.

turtmcfly said...

Anyone for the weirdest fix for the 'could Not Generate Stub Objects For Web Service Invocation'? BTW - on Coldfusion 8 this seems to manifest itself as 'Unable to read WSDL from url' error.

The webservice being consumed was written by me and running on a CF8 machine with Apache 2.2.6 and Fedora Core 6. Anyway...

After hours of googling, head scratching, typing etc I thought I'd actually check the WSDL being auto-generated CF8. I went to this really cool page;

http://gradvs1.mgateway.com/main/

You can give it your WSDL URL, then actually fire any methods in your component.

So, the WDSL found the single method in my component, but here's the weird bit. When I entered values for the parameters in my method and in hit 'Invoke Operation' the SOAP Response had nine lines from my Apache config file httpd.conf, a couple of lines of Coldfusion stuff to do with CFTOKEN *and then* my properly structured XML response. N o wonder all my cfinvoke were failing! The particular bit of the Apache config file I was getting back was (I've made the first characters of the Apache functions '[' since I the HTML checker here thinks they're tags otherwise)

[Files ~ "\.(cgi|shtml|xfm)$">
SSLOptions +StdEnvVars
[/Files>
[IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule /staging/([0-9]+)/?$ /staging/index.cfm?promotion=$1 [PT]
RewriteRule /dev/([0-9]+)/?$ /dev/index.cfm?promotion=$1 [PT]
RewriteRule /([0-9]+)/?$ /index.cfm?promotion=$1 [PT]
[/IfModule>


I removed the first three of these lines, restarted Apache, and bingo - problem gone. Weird thing is I've not put them back but the problem didn't come back. I really don't like fixes like this and am confident it will rear it's head again, but it certainly got me over an immediate hump

Helena said...

Thanks for the hint on the post and in the comments.

I still have this annoying message,

“Could not generate stub objects for web service invocation…javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated It is recommended that you use a web browser to retrieve and examine the requested WSDL document for correctness. If the requested WSDL document can&apos;t be retrieved or it is dynamically generated, it is likely that the target web service has programming errors.”

But now I could test my WSDL I know it isn’t a problem from this side.

Ok … I will keep on searching what is wrong …

Alistair Davidson said...

Hi Helena

a "javax.net.ssl.SSLPeerUnverifiedException" suggests that you're trying to access a web service over SSL, but the server doesn't have an SSL certificate.

If it's on a server you control, you can follow the instructions here

If not, then I suggest you contact the owners of the server and point this out to them!

Hope that helps

Al