Version 3.1 by Pascal Robert on 2007/09/03 19:17

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 <barGlobalID xsi:type="ns4:EOGlobalID">
24 <data xsi:type="xsd:base64Binary">AAB/AAABAAAXcQEAAAABC8pbeE6Acu4F</data>
25 </barGlobalID>
26
27 {{/code}}
28
29 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...
30
31 {{code}}
32
33 <barGlobalID xsi:type="SOAP-ENC:Dictionary">
34 <data xsi:type="xsd:base64Binary"> AAB/AAABAAAXcQEAAAABC8pbeE6Acu4F</data>
35 </barGlobalID>
36
37 {{/code}}
38
39 WO: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.
40
41 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...
42
43 {{code}}
44
45 - IllegalArgumentException:
46 java.lang.IllegalArgumentException
47 at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:63)
48 at java.lang.reflect.Field.set(Field.java:519)
49 at org.apache.axis.encoding.FieldTarget.set(FieldTarget.java:91)
50 at org.apache.axis.encoding.DeserializerImpl.valueComplete(DeserializerImpl.java:282)
51 at org.apache.axis.encoding.DeserializerImpl.endElement(DeserializerImpl.java:541)
52 at org.apache.axis.encoding.DeserializationContextImpl.endElement(DeserializationContextImpl.java:1015)
53 at org.apache.axis.message.SAX2EventRecorder.replay(SAX2EventRecorder.java:204)
54 at org.apache.axis.message.MessageElement.publishToHandler(MessageElement.java:722)
55 at org.apache.axis.message.RPCElement.deserialize(RPCElement.java:233)
56 at org.apache.axis.message.RPCElement.getParams(RPCElement.java:347)
57 at org.apache.axis.providers.java.RPCProvider.processMessage(RPCProvider.java:184)
58 at org.apache.axis.providers.java.JavaProvider.invoke(JavaProvider.java:333)
59 at org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrategy.java:71)
60 at org.apache.axis.SimpleChain.doVisiting(SimpleChain.java:150)
61 at org.apache.axis.SimpleChain.invoke(SimpleChain.java:120)
62 at org.apache.axis.handlers.soap.SOAPService.invoke(SOAPService.java:481)
63 at org.apache.axis.server.AxisServer.invoke(AxisServer.java:323)
64 at com.webobjects.appserver._private.WOWebService.performActionNamed(WOWebService.java:375)
65
66 {{/code}}
67
68 If I send a key GID as follows...
69
70 {{code}}
71
72 <barGlobalID xsi:type="SOAP-ENC:Dictionary">
73 <primaryKeys SOAP-ENC:arrayType="xsd:anyType[1]" xsi:type="SOAP-ENC:Array">
74 <item_0 xsi:type="xsd:int">1</item_0>
75 </primaryKeys>
76 <entityName xsi:type="xsd:string">BarEntity</entityName>
77 </barGlobalID>
78
79 {{/code}}
80
81 ...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.
82
83 {{code}}
84
85 <?xml version="1.0" encoding="UTF-8"?>
86 <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">
87 <soapenv:Body>
88 <soapenv:Fault>
89 <faultcode>soapenv:Server.userException</faultcode>
90 <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>
91 <detail>
92 <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.:
93 at com.ibm.wsdl.util.xml.DOMUtils.getPrefix(Unknown Source)
94 at com.ibm.wsdl.util.xml.DOMUtils.getQualifiedValue(Unknown Source)
95 at com.ibm.wsdl.util.xml.DOMUtils.printQualifiedAttribute(Unknown Source)
96 at com.ibm.wsdl.xml.WSDLWriterImpl.printParts(Unknown Source)
97 at com.ibm.wsdl.xml.WSDLWriterImpl.printMessages(Unknown Source)
98 at com.ibm.wsdl.xml.WSDLWriterImpl.printDefinition(Unknown Source)
99 at com.ibm.wsdl.xml.WSDLWriterImpl.writeWSDL(Unknown Source)
100 at com.ibm.wsdl.xml.WSDLWriterImpl.getDocument(Unknown Source)
101 at org.apache.axis.wsdl.fromJava.Emitter.emit(Emitter.java:267)
102 at org.apache.axis.providers.java.JavaProvider.generateWSDL(JavaProvider.java:494)
103 at org.apache.axis.strategies.WSDLGenStrategy.visit(WSDLGenStrategy.java:72)
104 at org.apache.axis.SimpleChain.doVisiting(SimpleChain.java:150)
105 at org.apache.axis.SimpleChain.generateWSDL(SimpleChain.java:137)
106 at org.apache.axis.handlers.soap.SOAPService.generateWSDL(SOAPService.java:375)
107 at org.apache.axis.server.AxisServer.generateWSDL(AxisServer.java:499)
108 at com.webobjects.appserver._private.WOWebService.performActionNamed(WOWebService.java:352)
109 at com.webobjects.appserver._private.WOActionRequestHandler._handleRequest(WOActionRequestHandler.java:240)
110 at com.webobjects.appserver._private.WOActionRequestHandler.handleRequest(WOActionRequestHandler.java:142)
111 at com.webobjects.appserver._private.WOWebServiceRequestHandler.handleRequest(WOWebServiceRequestHandler.java:95)
112 at com.webobjects.appserver.WOApplication.dispatchRequest(WOApplication.java:1306)
113 at nz.co.lindesay.common.webobjects.LEWOApplication.dispatchRequest(LEWOApplication.java:537)
114 at nz.co.chong.cbw.webobjects.Application.dispatchRequest(Application.java:82)
115 at com.webobjects.appserver._private.WOWorkerThread.runOnce(WOWorkerThread.java:173)
116 at com.webobjects.appserver._private.WOWorkerThread.run(WOWorkerThread.java:254)
117 at java.lang.Thread.run(Thread.java:552)
118 </ns1:stackTrace>
119 </detail>
120 </soapenv:Fault>
121 </soapenv:Body>
122 </soapenv:Envelope>
123
124 {{/code}}
125
126 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.
127
128 = Attempt at a Solution =
129
130 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.
131
132 {{code}}
133
134 packagepackage nz.co.lindesay.common.webobjects;
135
136 // BSD LICENSE
137
138 import org.apache.axis.message.*;
139 import org.apache.axis.encoding.*;
140
141 import org.xml.sax.*;
142
143 import javax.xml.namespace.*;
144
145 import com.webobjects.foundation.*;
146 import com.webobjects.eocontrol.*;
147
148 /**
149 * <P>WO's EOGlobalID deserializer is not able to handle temporary
150 * global ID's and in fact throws some rather odd exceptions. For
151 * this reason, I have developed my own deserialiser which will be
152 * able to handle temporary global ID's.</P>
153 *
154 * <P><FONT color="#FF0000">This does not work.</FONT></P>
155 */
156
157 public class LEWOWebServicesGlobalIDDeserializer extends org.apache.axis.encoding.DeserializerImpl
158 {
159
160 private static Integer DATA_HINT = new Integer(1);
161 private static Integer PRIMARYKEY_HINT = new Integer(2);
162 private static Integer ENTITYNAME_HINT = new Integer(3);
163
164 protected NSData data = null;
165 protected Object[] primaryKeys = null;
166 protected String entityName = null;
167
168 // -------------------------------------------------
169
170 public LEWOWebServicesGlobalIDDeserializer() { super(); }
171
172 // -------------------------------------------------
173
174 public void setChildValue(Object val, Object hint) throws SAXException
175 {
176 if(hint.equals(DATA_HINT))
177 data = (NSData) val;
178 else
179 {
180 if(hint.equals(PRIMARYKEY_HINT))
181 primaryKeys = (Object[]) val;
182 else
183 {
184 if(hint.equals(ENTITYNAME_HINT))
185 entityName = (String) val;
186 }
187 }
188 }
189
190 // -------------------------------------------------
191
192 public SOAPHandler onStartChild(
193 String namespace,
194 String localName,
195 String prefix,
196 Attributes attributes,
197 DeserializationContext context) throws SAXException
198 {
199 DeserializerTarget dt = null;
200 QName typeQName = context.getTypeFromAttributes(namespace,localName,attributes);
201 Deserializer dser = context.getDeserializerForType(typeQName);
202
203 // If no deserializer, use the base DeserializerImpl.
204 if (null==dser)
205 dser = new DeserializerImpl();
206
207 if(localName.equals("data")) dt = new DeserializerTarget(this,DATA_HINT);
208 if(localName.equals("entityName")) dt = new DeserializerTarget(this,ENTITYNAME_HINT);
209 if(localName.equals("primaryKeys")) dt = new DeserializerTarget(this,PRIMARYKEY_HINT);
210
211 if(null==dt)
212 throw new SAXException("the element '"+localName+"' was encountered whilst deserialising an EOGlobalID and is not supported.");
213
214 dser.registerValueTarget(dt);
215 addChildDeserializer(dser);
216
217 return (SOAPHandler)dser;
218 }
219
220 // -------------------------------------------------
221
222 public void onEndElement(
223 String namespace,
224 String localName,
225 DeserializationContext context) throws SAXException
226 {
227 if(null!=data)
228 {
229 if((null!=entityName)||(null!=primaryKeys))
230 throw new SAXException("when deserialising a temporary global id, the elements 'entityName' and 'primaryKey' should be absent.");
231
232 setValue(new LEWOWebServicesTemporaryGlobalID(data.bytes()));
233 }
234 else
235 {
236 if(null!=entityName)
237 {
238 if(null==primaryKeys)
239 throw new SAXException("when deserialising a key global id, the element 'primarykey' should be present.");
240
241 if(0==primaryKeys.length)
242 throw new SAXException("when deserialising a key global id, the element 'primarykey' should contain one or more key values.");
243
244 setValue(EOKeyGlobalID.globalIDWithEntityName(entityName,primaryKeys));
245
246 }
247 else
248 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.");
249 }
250 }
251
252 // -------------------------------------------------
253 // TEMPORARY GID SUBCLASS
254 // -------------------------------------------------
255 // WO has the temporary GID exposed as protected
256 // which means that it is not possible to directly
257 // instantiate a temporary GID. For this reason,
258 // I have subclassed it in this inner class.
259 // -------------------------------------------------
260
261 public static class LEWOWebServicesTemporaryGlobalID extends EOTemporaryGlobalID
262 {
263
264 public LEWOWebServicesTemporaryGlobalID(byte[] globallyUniqueBytes) { super(globallyUniqueBytes); }
265
266 }
267
268 // -------------------------------------------------
269
270 }
271
272 {{/code}}
273
274 {{code}}
275
276 package nz.co.lindesay.common.webobjects;
277
278 // BSD LICENSE
279
280 import com.webobjects.foundation.*;
281 import com.webobjects.appserver.*;
282 import com.webobjects.eocontrol.*;
283 import com.webobjects.webservices.support.xml.*;
284
285 import javax.xml.namespace.*;
286
287 import java.util.*;
288
289 public class LEWOWebServicesGlobalIDDeserializerFactory implements org.apache.axis.encoding.DeserializerFactory
290 {
291
292 private Set mechanisms = null;
293
294 // -------------------------------------------------
295
296 public static void registerWebServicesGlobalIDDeserialiserFactory()
297 {
298 LEWOWebServicesGlobalIDDeserializerFactory dserF = new LEWOWebServicesGlobalIDDeserializerFactory();
299 WOGlobalIDSerializerFactory serF = new WOGlobalIDSerializerFactory();
300
301 WOWebServiceRegistrar.registerFactoriesForClassWithQName(
302 serF,
303 dserF,
304 EOGlobalID.class,
305 WOSoapConstants.EOGLOBALID_QNAME);
306
307 WOWebServiceRegistrar.registerFactoriesForClassWithQName(
308 serF,
309 dserF,
310 EOGlobalID.class,
311 WOSoapConstants.EOGLOBALID_QNAME_WEBSERVICESCORE_WORKAROUND);
312 }
313
314 // -------------------------------------------------
315
316 public LEWOWebServicesGlobalIDDeserializerFactory() { super(); }
317
318 // -------------------------------------------------
319
320 public javax.xml.rpc.encoding.Deserializer getDeserializerAs(String mechanismType)
321 { return new LEWOWebServicesGlobalIDDeserializer(); }
322
323 // -------------------------------------------------
324
325 public Iterator getSupportedMechanismTypes()
326 {
327 if(null==mechanisms)
328 {
329 mechanisms = new HashSet();
330 mechanisms.add(org.apache.axis.Constants.AXIS_SAX);
331 }
332
333 return mechanisms.iterator();
334 }
335
336 // -------------------------------------------------
337
338 }
339
340 {{/code}}