Version 1.1 by smmccraw on 2007/07/08 10:35

Show last authors
1 This documentation was written by Andrew Lindesay (http:~/~/www.lindesay.co.nz) in 2006 as part of supported code in the LEWOStuff framework, but this material has been transcribed here. It was written around the time of WebObjects 5.2 and 5.3 on the 1.4 JVM.
2
3 GlobalID's are unique identifiers for EO's in the EOF environment. The WebServices framework is able to serialise and deserialise instance of EOKeyGlobalID, but not EOTemporaryGlobalID. This presents a considerable problem working from the client on a remote EO graph.
4
5 = Problem Description =
6
7 I am building a graph of inserted/modified/referenced EO-s in the session's default EC and then persisting the changes. In order to reference inserted objects in the EC to create relationships between in-memory EO-s, I am referring to them using their temporary EOGlobalID's. For example...
8
9 {{code}}
10
11 boolean addFoo(
12 String wosid,
13 String name,
14 String code,
15 EOGobalID barGlobalID)
16
17 {{/code}}
18
19 ...where 'barGID' may be a temporary GID (EOTemporaryGlobalID) or may be a key GID (EOKeyGlobalID). The temporary GID's are serialising out to the client from the WO application without any difficulty. In this case, the XML chunk in the SOAP envelope looks like this...
20
21 {{code}}
22
23 {panel}
24 <barGlobalID xsi:type="ns4:EOGlobalID">
25 <data xsi:type="xsd:base64Binary">AAB/AAABAAAXcQEAAAABC8pbeE6Acu4F</data>
26 </barGlobalID>
27 {panel}
28
29 {{/code}}
30
31 However when I try and use this temporary GID by sending it from the client, back into the WO application for deserialisation, I end up sending this...
32
33 {{code}}
34
35 {panel}
36 <barGlobalID xsi:type="SOAP-ENC:Dictionary">
37 <data xsi:type="xsd:base64Binary"> AAB/AAABAAAXcQEAAAABC8pbeE6Acu4F</data>
38 </barGlobalID>
39 {panel}
40
41 {{/code}}
42
43 [[I am using Apple's WebServicesCore framework and I'm not entirely sure why it is putting whitespace into the base64 encoding, but I have done independent tests which would tend to indicate that this is deserialised correctly by the deserialiser on the WO-AXIS end]]
44
45 Passing in 'null' for this argument results in no exception, passing in a temporary GID 'dictionary structure' as shown above produced the following exception and corresponding AXIS fault as shown below...
46
47 {{code}}
48
49 - IllegalArgumentException:
50 java.lang.IllegalArgumentException
51 at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:63)
52 at java.lang.reflect.Field.set(Field.java:519)
53 at org.apache.axis.encoding.FieldTarget.set(FieldTarget.java:91)
54 at org.apache.axis.encoding.DeserializerImpl.valueComplete(DeserializerImpl.java:282)
55 at org.apache.axis.encoding.DeserializerImpl.endElement(DeserializerImpl.java:541)
56 at org.apache.axis.encoding.DeserializationContextImpl.endElement(DeserializationContextImpl.java:1015)
57 at org.apache.axis.message.SAX2EventRecorder.replay(SAX2EventRecorder.java:204)
58 at org.apache.axis.message.MessageElement.publishToHandler(MessageElement.java:722)
59 at org.apache.axis.message.RPCElement.deserialize(RPCElement.java:233)
60 at org.apache.axis.message.RPCElement.getParams(RPCElement.java:347)
61 at org.apache.axis.providers.java.RPCProvider.processMessage(RPCProvider.java:184)
62 at org.apache.axis.providers.java.JavaProvider.invoke(JavaProvider.java:333)
63 at org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrategy.java:71)
64 at org.apache.axis.SimpleChain.doVisiting(SimpleChain.java:150)
65 at org.apache.axis.SimpleChain.invoke(SimpleChain.java:120)
66 at org.apache.axis.handlers.soap.SOAPService.invoke(SOAPService.java:481)
67 at org.apache.axis.server.AxisServer.invoke(AxisServer.java:323)
68 at com.webobjects.appserver._private.WOWebService.performActionNamed(WOWebService.java:375)
69
70 {{/code}}
71
72 If I send a key GID as follows...
73
74 {{code}}
75
76 {panel}
77 <barGlobalID xsi:type="SOAP-ENC:Dictionary">
78 <primaryKeys SOAP-ENC:arrayType="xsd:anyType[1]" xsi:type="SOAP-ENC:Array">
79 <item_0 xsi:type="xsd:int">1</item_0>
80 </primaryKeys>
81 <entityName xsi:type="xsd:string">BarEntity</entityName>
82 </barGlobalID>
83 {panel}
84
85 {{/code}}
86
87 ...then it deserialises without issue. When I try to get a WSDL from a registered web service that has EOGlobalID's as parameters, I tend to get the following exception and corresponding AXIS fault. For this reason it is not possible to find out how the EOGlobalID should be communicated.
88
89 {{code}}
90
91 <?xml version="1.0" encoding="UTF-8"?>
92 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
93 <soapenv:Body>
94 {panel}
95 <soapenv:Fault>
96 <faultcode>soapenv:Server.userException</faultcode>
97 <faultstring>WSDLException: faultCode=OTHER_ERROR: Can&apos;t find prefix for &apos;http://www.apple.com/webobjects/webservices/soap/&apos;. Namespace prefixes must be set on the Definition object using the addNamespace(...) method.: </faultstring>
98 <detail>
99 <ns1:stackTrace xmlns:ns1="http://xml.apache.org/axis/">WSDLException: faultCode=OTHER_ERROR: Can&apos;t find prefix for &apos;http://www.apple.com/webobjects/webservices/soap/&apos;. Namespace prefixes must be set on the Definition object using the addNamespace(...) method.:
100 {panel}
101 at com.ibm.wsdl.util.xml.DOMUtils.getPrefix(Unknown Source)
102 at com.ibm.wsdl.util.xml.DOMUtils.getQualifiedValue(Unknown Source)
103 at com.ibm.wsdl.util.xml.DOMUtils.printQualifiedAttribute(Unknown Source)
104 at com.ibm.wsdl.xml.WSDLWriterImpl.printParts(Unknown Source)
105 at com.ibm.wsdl.xml.WSDLWriterImpl.printMessages(Unknown Source)
106 at com.ibm.wsdl.xml.WSDLWriterImpl.printDefinition(Unknown Source)
107 at com.ibm.wsdl.xml.WSDLWriterImpl.writeWSDL(Unknown Source)
108 at com.ibm.wsdl.xml.WSDLWriterImpl.getDocument(Unknown Source)
109 at org.apache.axis.wsdl.fromJava.Emitter.emit(Emitter.java:267)
110 at org.apache.axis.providers.java.JavaProvider.generateWSDL(JavaProvider.java:494)
111 at org.apache.axis.strategies.WSDLGenStrategy.visit(WSDLGenStrategy.java:72)
112 at org.apache.axis.SimpleChain.doVisiting(SimpleChain.java:150)
113 at org.apache.axis.SimpleChain.generateWSDL(SimpleChain.java:137)
114 at org.apache.axis.handlers.soap.SOAPService.generateWSDL(SOAPService.java:375)
115 at org.apache.axis.server.AxisServer.generateWSDL(AxisServer.java:499)
116 at com.webobjects.appserver._private.WOWebService.performActionNamed(WOWebService.java:352)
117 at com.webobjects.appserver._private.WOActionRequestHandler._handleRequest(WOActionRequestHandler.java:240)
118 at com.webobjects.appserver._private.WOActionRequestHandler.handleRequest(WOActionRequestHandler.java:142)
119 at com.webobjects.appserver._private.WOWebServiceRequestHandler.handleRequest(WOWebServiceRequestHandler.java:95)
120 at com.webobjects.appserver.WOApplication.dispatchRequest(WOApplication.java:1306)
121 at nz.co.lindesay.common.webobjects.LEWOApplication.dispatchRequest(LEWOApplication.java:537)
122 at nz.co.chong.cbw.webobjects.Application.dispatchRequest(Application.java:82)
123 at com.webobjects.appserver._private.WOWorkerThread.runOnce(WOWorkerThread.java:173)
124 at com.webobjects.appserver._private.WOWorkerThread.run(WOWorkerThread.java:254)
125 at java.lang.Thread.run(Thread.java:552)
126 </ns1:stackTrace>
127 {panel}
128 </detail>
129 </soapenv:Fault>
130 {panel}
131 </soapenv:Body>
132 </soapenv:Envelope>
133
134 {{/code}}
135
136 Finally, if I look at the "com.webobjects.webservices.support.xml.WOGlobalIDDeserializer" class, it has a "byte[[]] data" instance variable so I can assume that at least some attempt has been made to deserialise temporary GID's.
137
138 = Attempt at a Solution =
139
140 The following is an attempt (it did not work) to write a custom serialiser/deserialiser for the AXIS environment in WebObjects such that these EOTemporaryGlobalID-s can be serialised. I think this was the point where I "dropped the SOAP" and moved to using JSON-RPC. However this will give anybody who is faced with this challenge a good starting point for further work in this area.
141
142 {{code}}
143
144 package nz.co.lindesay.common.webobjects;
145
146 // BSD LICENSE
147
148 import org.apache.axis.message.*;
149 import org.apache.axis.encoding.*;
150
151 import org.xml.sax.*;
152
153 import javax.xml.namespace.*;
154
155 import com.webobjects.foundation.*;
156 import com.webobjects.eocontrol.*;
157
158 /**
159 {panel}
160 * <P>WO's EOGlobalID deserializer is not able to handle temporary
161 * global ID's and in fact throws some rather odd exceptions. For
162 * this reason, I have developed my own deserialiser which will be
163 * able to handle temporary global ID's.</P>
164 *
165 * <P><FONT color="#FF0000">This does not work.</FONT></P>
166 */
167 {panel}
168
169 public class LEWOWebServicesGlobalIDDeserializer extends org.apache.axis.encoding.DeserializerImpl
170 {
171
172 private static Integer DATA_HINT = new Integer(1);
173 private static Integer PRIMARYKEY_HINT = new Integer(2);
174 private static Integer ENTITYNAME_HINT = new Integer(3);
175
176 protected NSData data = null;
177 protected Object[] primaryKeys = null;
178 protected String entityName = null;
179
180 // -------------------------------------------------
181
182 public LEWOWebServicesGlobalIDDeserializer() { super(); }
183
184 // -------------------------------------------------
185
186 public void setChildValue(Object val, Object hint) throws SAXException
187 {
188 if(hint.equals(DATA_HINT))
189 data = (NSData) val;
190 else
191 {
192 if(hint.equals(PRIMARYKEY_HINT))
193 primaryKeys = (Object[]) val;
194 else
195 {
196 if(hint.equals(ENTITYNAME_HINT))
197 entityName = (String) val;
198 }
199 }
200 }
201
202 // -------------------------------------------------
203
204 {panel}
205 public SOAPHandler onStartChild(
206 {panel}
207 String namespace,
208 String localName,
209 String prefix,
210 Attributes attributes,
211 DeserializationContext context) throws SAXException
212 {
213 DeserializerTarget dt = null;
214 QName typeQName = context.getTypeFromAttributes(namespace,localName,attributes);
215 Deserializer dser = context.getDeserializerForType(typeQName);
216
217 // If no deserializer, use the base DeserializerImpl.
218 if (null==dser)
219 dser = new DeserializerImpl();
220
221 if(localName.equals("data")) dt = new DeserializerTarget(this,DATA_HINT);
222 if(localName.equals("entityName")) dt = new DeserializerTarget(this,ENTITYNAME_HINT);
223 if(localName.equals("primaryKeys")) dt = new DeserializerTarget(this,PRIMARYKEY_HINT);
224
225 if(null==dt)
226 throw new SAXException("the element '"+localName+"' was encountered whilst deserialising an EOGlobalID and is not supported.");
227
228 dser.registerValueTarget(dt);
229 addChildDeserializer(dser);
230
231 return (SOAPHandler)dser;
232 }
233 {panel}
234
235 {panel}
236 // -------------------------------------------------
237
238 {panel}
239 public void onEndElement(
240 {panel}
241 String namespace,
242 String localName,
243 DeserializationContext context) throws SAXException
244 {
245 if(null!=data)
246 {
247 if((null!=entityName)||(null!=primaryKeys))
248 throw new SAXException("when deserialising a temporary global id, the elements 'entityName' and 'primaryKey' should be absent.");
249
250 setValue(new LEWOWebServicesTemporaryGlobalID(data.bytes()));
251 }
252 else
253 {
254 if(null!=entityName)
255 {
256 if(null==primaryKeys)
257 throw new SAXException("when deserialising a key global id, the element 'primarykey' should be present.");
258
259 if(0==primaryKeys.length)
260 throw new SAXException("when deserialising a key global id, the element 'primarykey' should contain one or more key values.");
261
262 setValue(EOKeyGlobalID.globalIDWithEntityName(entityName,primaryKeys));
263
264 }
265 else
266 throw new SAXException("when deserialising a global id, either the data required for a key global id or a temporary global id must be present.");
267 }
268 }
269
270 // -------------------------------------------------
271 // TEMPORARY GID SUBCLASS
272 // -------------------------------------------------
273 // WO has the temporary GID exposed as protected
274 // which means that it is not possible to directly
275 // instantiate a temporary GID. For this reason,
276 // I have subclassed it in this inner class.
277 // -------------------------------------------------
278
279 public static class LEWOWebServicesTemporaryGlobalID extends EOTemporaryGlobalID
280 {
281
282 public LEWOWebServicesTemporaryGlobalID(byte[] globallyUniqueBytes) { super(globallyUniqueBytes); }
283
284 }
285
286 // -------------------------------------------------
287
288 }
289
290 {{/code}}
291
292 {{code}}
293
294 package nz.co.lindesay.common.webobjects;
295
296 // BSD LICENSE
297
298 import com.webobjects.foundation.*;
299 import com.webobjects.appserver.*;
300 import com.webobjects.eocontrol.*;
301 import com.webobjects.webservices.support.xml.*;
302
303 import javax.xml.namespace.*;
304
305 import java.util.*;
306
307 public class LEWOWebServicesGlobalIDDeserializerFactory implements org.apache.axis.encoding.DeserializerFactory
308 {
309
310 private Set mechanisms = null;
311
312 // -------------------------------------------------
313
314 public static void registerWebServicesGlobalIDDeserialiserFactory()
315 {
316 LEWOWebServicesGlobalIDDeserializerFactory dserF = new LEWOWebServicesGlobalIDDeserializerFactory();
317 WOGlobalIDSerializerFactory serF = new WOGlobalIDSerializerFactory();
318
319 WOWebServiceRegistrar.registerFactoriesForClassWithQName(
320 serF,
321 dserF,
322 EOGlobalID.class,
323 WOSoapConstants.EOGLOBALID_QNAME);
324
325 WOWebServiceRegistrar.registerFactoriesForClassWithQName(
326 serF,
327 dserF,
328 EOGlobalID.class,
329 WOSoapConstants.EOGLOBALID_QNAME_WEBSERVICESCORE_WORKAROUND);
330 }
331
332 // -------------------------------------------------
333
334 {panel}
335 public LEWOWebServicesGlobalIDDeserializerFactory() { super(); }
336 {panel}
337
338 // -------------------------------------------------
339
340 {panel}
341 public javax.xml.rpc.encoding.Deserializer getDeserializerAs(String mechanismType)
342 {panel}
343 { return new LEWOWebServicesGlobalIDDeserializer(); }
344
345 // -------------------------------------------------
346
347 {panel}
348 public Iterator getSupportedMechanismTypes()
349 {panel}
350 {
351 if(null==mechanisms)
352 {
353 mechanisms = new HashSet();
354 mechanisms.add(org.apache.axis.Constants.AXIS_SAX);
355 }
356
357 return mechanisms.iterator();
358 {panel}
359 }
360 {panel}
361
362 // -------------------------------------------------
363
364 }
365
366 {{/code}}