Last modified 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>>url:http://www.lindesay.co.nz||shape="rect"]]) 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 packagepackage nz.co.lindesay.common.webobjects;
134
135 // BSD LICENSE
136
137 import org.apache.axis.message.*;
138 import org.apache.axis.encoding.*;
139
140 import org.xml.sax.*;
141
142 import javax.xml.namespace.*;
143
144 import com.webobjects.foundation.*;
145 import com.webobjects.eocontrol.*;
146
147 /**
148 * <P>WO's EOGlobalID deserializer is not able to handle temporary
149 * global ID's and in fact throws some rather odd exceptions. For
150 * this reason, I have developed my own deserialiser which will be
151 * able to handle temporary global ID's.</P>
152 *
153 * <P><FONT color="#FF0000">This does not work.</FONT></P>
154 */
155
156 public class LEWOWebServicesGlobalIDDeserializer extends org.apache.axis.encoding.DeserializerImpl
157 {
158
159 private static Integer DATA_HINT = new Integer(1);
160 private static Integer PRIMARYKEY_HINT = new Integer(2);
161 private static Integer ENTITYNAME_HINT = new Integer(3);
162
163 protected NSData data = null;
164 protected Object[] primaryKeys = null;
165 protected String entityName = null;
166
167 // -------------------------------------------------
168
169 public LEWOWebServicesGlobalIDDeserializer() { super(); }
170
171 // -------------------------------------------------
172
173 public void setChildValue(Object val, Object hint) throws SAXException
174 {
175 if(hint.equals(DATA_HINT))
176 data = (NSData) val;
177 else
178 {
179 if(hint.equals(PRIMARYKEY_HINT))
180 primaryKeys = (Object[]) val;
181 else
182 {
183 if(hint.equals(ENTITYNAME_HINT))
184 entityName = (String) val;
185 }
186 }
187 }
188
189 // -------------------------------------------------
190
191 public SOAPHandler onStartChild(
192 String namespace,
193 String localName,
194 String prefix,
195 Attributes attributes,
196 DeserializationContext context) throws SAXException
197 {
198 DeserializerTarget dt = null;
199 QName typeQName = context.getTypeFromAttributes(namespace,localName,attributes);
200 Deserializer dser = context.getDeserializerForType(typeQName);
201
202 // If no deserializer, use the base DeserializerImpl.
203 if (null==dser)
204 dser = new DeserializerImpl();
205
206 if(localName.equals("data")) dt = new DeserializerTarget(this,DATA_HINT);
207 if(localName.equals("entityName")) dt = new DeserializerTarget(this,ENTITYNAME_HINT);
208 if(localName.equals("primaryKeys")) dt = new DeserializerTarget(this,PRIMARYKEY_HINT);
209
210 if(null==dt)
211 throw new SAXException("the element '"+localName+"' was encountered whilst deserialising an EOGlobalID and is not supported.");
212
213 dser.registerValueTarget(dt);
214 addChildDeserializer(dser);
215
216 return (SOAPHandler)dser;
217 }
218
219 // -------------------------------------------------
220
221 public void onEndElement(
222 String namespace,
223 String localName,
224 DeserializationContext context) throws SAXException
225 {
226 if(null!=data)
227 {
228 if((null!=entityName)||(null!=primaryKeys))
229 throw new SAXException("when deserialising a temporary global id, the elements 'entityName' and 'primaryKey' should be absent.");
230
231 setValue(new LEWOWebServicesTemporaryGlobalID(data.bytes()));
232 }
233 else
234 {
235 if(null!=entityName)
236 {
237 if(null==primaryKeys)
238 throw new SAXException("when deserialising a key global id, the element 'primarykey' should be present.");
239
240 if(0==primaryKeys.length)
241 throw new SAXException("when deserialising a key global id, the element 'primarykey' should contain one or more key values.");
242
243 setValue(EOKeyGlobalID.globalIDWithEntityName(entityName,primaryKeys));
244
245 }
246 else
247 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.");
248 }
249 }
250
251 // -------------------------------------------------
252 // TEMPORARY GID SUBCLASS
253 // -------------------------------------------------
254 // WO has the temporary GID exposed as protected
255 // which means that it is not possible to directly
256 // instantiate a temporary GID. For this reason,
257 // I have subclassed it in this inner class.
258 // -------------------------------------------------
259
260 public static class LEWOWebServicesTemporaryGlobalID extends EOTemporaryGlobalID
261 {
262
263 public LEWOWebServicesTemporaryGlobalID(byte[] globallyUniqueBytes) { super(globallyUniqueBytes); }
264
265 }
266
267 // -------------------------------------------------
268
269 }
270 {{/code}}
271
272 {{code}}
273
274 package nz.co.lindesay.common.webobjects;
275
276 // BSD LICENSE
277
278 import com.webobjects.foundation.*;
279 import com.webobjects.appserver.*;
280 import com.webobjects.eocontrol.*;
281 import com.webobjects.webservices.support.xml.*;
282
283 import javax.xml.namespace.*;
284
285 import java.util.*;
286
287 public class LEWOWebServicesGlobalIDDeserializerFactory implements org.apache.axis.encoding.DeserializerFactory
288 {
289
290 private Set mechanisms = null;
291
292 // -------------------------------------------------
293
294 public static void registerWebServicesGlobalIDDeserialiserFactory()
295 {
296 LEWOWebServicesGlobalIDDeserializerFactory dserF = new LEWOWebServicesGlobalIDDeserializerFactory();
297 WOGlobalIDSerializerFactory serF = new WOGlobalIDSerializerFactory();
298
299 WOWebServiceRegistrar.registerFactoriesForClassWithQName(
300 serF,
301 dserF,
302 EOGlobalID.class,
303 WOSoapConstants.EOGLOBALID_QNAME);
304
305 WOWebServiceRegistrar.registerFactoriesForClassWithQName(
306 serF,
307 dserF,
308 EOGlobalID.class,
309 WOSoapConstants.EOGLOBALID_QNAME_WEBSERVICESCORE_WORKAROUND);
310 }
311
312 // -------------------------------------------------
313
314 public LEWOWebServicesGlobalIDDeserializerFactory() { super(); }
315
316 // -------------------------------------------------
317
318 public javax.xml.rpc.encoding.Deserializer getDeserializerAs(String mechanismType)
319 { return new LEWOWebServicesGlobalIDDeserializer(); }
320
321 // -------------------------------------------------
322
323 public Iterator getSupportedMechanismTypes()
324 {
325 if(null==mechanisms)
326 {
327 mechanisms = new HashSet();
328 mechanisms.add(org.apache.axis.Constants.AXIS_SAX);
329 }
330
331 return mechanisms.iterator();
332 }
333
334 // -------------------------------------------------
335
336 }
337
338 {{/code}}