You are viewing a plain text version of this content. The canonical link for it is here.
Posted to java-user@axis.apache.org by Frederic Desjarlais <fr...@enjiva.com> on 2001/08/18 07:15:15 UTC

Deserialization: onStartChild vs. onStartElement

Could someone help explain how to deserialize the following (cleaned up)
SOAP response (from the POV of a SOAP client receiving a response):

<SOAP-ENV:Envelope>
<SOAP-ENV:Body>
<getInfoResponse><output><text>Some text</text></output></getInfoResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Here's what I've done:

1. Called addDeserializerFactory() twice, once for <output> and another for
<text> elements (I used two different deserializer factories/classes)
2. Called addOutputParam() to set <output> as output parameter (e.g.
sd.addOutputParam("output", outputQName); )
3. Created 2 deserialization classes: OutputDeserializer and
TextDeserializer which both extend Deserializer and implement Serializer.

I used 'onStartChild' in the OutputDeserializer and I got it to work, but
I'd like to use onStartElement instead (and determine which deserializer to
pass control over to).

When should Deserializers use onStartChild vs. onStartElement?  What would a
Deserializer using onStartElement look like for the above case (and in
general)?

Thanks for your help.

- Frederic







Re: Deserialization: onStartChild vs. onStartElement

Posted by Glen Daniels <gd...@macromedia.com>.
Hi Frederic!

> Would it be correct to say that if your deserializer is working on the
> 'parent' element of a basic type, e.g. outter in the case of:
> <outter><inner>24</inner></outter> , then the parent's 'onStartChild()'
> should obtain a deserializer for that basic type and register a callback
to
> obtain its contents via the 'valueReady()' callback.  NOTE: I mean 'basic'
> as in the innermost element (that is, no children).

That's exactly right.  In fact, it doesn't matter if the element has
children or not, as long as you can rely on the deserializer for the type to
handle getting the value for you.  For instance, <inner> might be a SOAP
array, in which case you'd do the exact same thing with an ArraySerializer
that you would with a BasicDeserializer - it's opaque to you how it handles
the contents of the <inner> element as long as you get an Object of the
right type deposited in the right place by the callback.

You can also, instead of registering for the callback, just register a
target which lets the deserializer put the value in the right place
automatically (see below).

> I noticed that if a deserializer is processing a 'basic' type such as
> <inner>24</inner>, then there's not much it can do since 'onStartChild()'
> will never get called.  Even if I try to register a callback using the
basic
> type's deserializer, it won't 'call back'.  I'm not sure what happends
after
> you call 'registerValueTarget()', but that didn't seem to help when placed
> in a deserializer for <inner>24</inner>.  If I remember correctly, I was
> able to use 'registerValueTarget()' in the 'parent' element's deserializer
> to obtain the value contained within its child element.

When an element ends, any deserializer which is connected to that element
receives an endElement() event, which as you can see in the
(non-overridable) Deserializer.endElement() method, calls valueComplete().

valueComplete's job is to squirt the value object into however many Target
objects are registered in the Deserializer.  Some of these Targets may be
callbacks, in which case they result in calling valueReady().  Others may be
Targets like the FieldTarget, which simply handle placing the new value into
an appropriate slot in an object.  There are also targets for using an
arbitrary method to store a value, and also for inserting a value into a
Bean property.

So if your "outer" class looks something like this:

class MyClass {
  String inner;
}

...in your MyClass deserializer, you could do one of two things (code elided
for clarity):

public void onStartChild(...)
{
  ...
  Deserializer dser = getDeserForInner();
  // dser is now == a BasicDeserializer for String
  dser.registerValueTarget(value, "inner");
  return dser;
}

That will just set the "inner" String field directly when it gets
deserialized, with no callbacks.  The alternative looks like this:

public void onStartChild(...)
{
  Deserializer dser = getDeserForInner();
  // dser is now == BasicDeserializer for String
  dser.registerCallback(this, null);
  return dser;
}

// This gets called when the value is ready
public void valueReady(Object value, Object hint)
{
  // We know this is the inner thing
  ((MyClass)value).inner = (String)value;
}

This approach lets you set the field yourself.  Again, ArraySerializer is a
good demo of this, since it not only gets callbacks from all the subelement
deserializers, it also uses the "hint" argument to handle the case where the
items are ready out-of-order.

--Glen



RE: Deserialization: onStartChild vs. onStartElement

Posted by Frederic Desjarlais <fr...@enjiva.com>.
Glen,

Thanks for your help.  It's making more sense now.

Would it be correct to say that if your deserializer is working on the
'parent' element of a basic type, e.g. outter in the case of:
<outter><inner>24</inner></outter> , then the parent's 'onStartChild()'
should obtain a deserializer for that basic type and register a callback to
obtain its contents via the 'valueReady()' callback.  NOTE: I mean 'basic'
as in the innermost element (that is, no children).

I noticed that if a deserializer is processing a 'basic' type such as
<inner>24</inner>, then there's not much it can do since 'onStartChild()'
will never get called.  Even if I try to register a callback using the basic
type's deserializer, it won't 'call back'.  I'm not sure what happends after
you call 'registerValueTarget()', but that didn't seem to help when placed
in a deserializer for <inner>24</inner>.  If I remember correctly, I was
able to use 'registerValueTarget()' in the 'parent' element's deserializer
to obtain the value contained within its child element.

Thanks again,
Frederic


-----Original Message-----
From: Glen Daniels [mailto:gdaniels@macromedia.com]
Sent: Saturday, August 18, 2001 11:33 AM
To: axis-user@xml.apache.org
Subject: Re: Deserialization: onStartChild vs. onStartElement


Hi Frederic!

----- Original Message -----
From: "Frederic Desjarlais" <fr...@enjiva.com>
To: <ax...@xml.apache.org>
Sent: Saturday, August 18, 2001 1:15 AM
Subject: Deserialization: onStartChild vs. onStartElement


>
> Could someone help explain how to deserialize the following (cleaned up)
> SOAP response (from the POV of a SOAP client receiving a response):
>
> <SOAP-ENV:Envelope>
> <SOAP-ENV:Body>
> <getInfoResponse><output><text>Some text</text></output></getInfoResponse>
> </SOAP-ENV:Body>
> </SOAP-ENV:Envelope>

I'm assuming "cleaned up" means "simplified"?  In other words, there's more
structure to the data than appears to be here.  Not to discourage your from
writing your own ser/desers, but in a lot of common cases you can just use
the BeanSerializer...

> 1. Called addDeserializerFactory() twice, once for <output> and another
for
> <text> elements (I used two different deserializer factories/classes)
> 2. Called addOutputParam() to set <output> as output parameter (e.g.
> sd.addOutputParam("output", outputQName); )

Check.

> 3. Created 2 deserialization classes: OutputDeserializer and
> TextDeserializer which both extend Deserializer and implement Serializer.

OK.  Again, I assume there's more to "text" than just a String, right?
Otherwise you'd just use the basic mapping for String...

> I used 'onStartChild' in the OutputDeserializer and I got it to work, but
> I'd like to use onStartElement instead (and determine which deserializer
to
> pass control over to).

OK, let's look at this in detail from the point at which the SAX parser
starts the <output> element - at this point, the top handler on the stack in
the DeserializationContext is the RPCHandler.  The way the events will
happen is as follows (you can trace this by recompiling with the DEBUG_LOG
flag in DeserializationContext and/or RPCHandler set to "true"):

1. startElement[ output ]
  1.1 RPCHandler (the active SOAPHandler in DeserializationContext)
      gets an onStartChild() call, and tries to find an appropriate
      Deserializer.  It does this by first looking at the xsi:type
      attribute (if any) on the element.  If it doesn't find one,
      it tries the ServiceDescription.  In this case, since it's
      a response, and the serviceDescription has an output param
      registered, we figure the type is outputQName, and as a
      result, onStartChild() will return a new instance of your
      OutputSerializer (or whatever you call it).
  1.2 DeserializationContext now calls startElement() on your
      deserializer.  You can't override startElement() directly,
      since it's critical that the base Deserializer gets to
      handle ID/HREF linkage - however, assuming this is not
      an element with an href attribute, the base class will then
      call onStartElement(), which is there expressly for the
      purpose of being overridden by subclasses.  So that's one
      place you can insert your own functionality.
2. startElement[ text ]
  2.1 OutputSerializer (the now-active SOAPHandler) gets an
      onStartChild() call, which you can do with as you please.

** onStartChild is the main hook for dealing with structured data **

The onStartChild() method returns a SOAPHandler, which is typically a
Deserializer, which should appropriately deal with each subelement in your
structured XML.

> When should Deserializers use onStartChild vs. onStartElement?  What would
a
> Deserializer using onStartElement look like for the above case (and in
> general)?

onStartElement() is the method to use when you need to do processing for the
initial element, and onStartChild() is used to decide which handler to use
to deserialize sub-elements.

A good example of this is the ArraySerializer.  Take a look at the
onStartElement() method - it is responsible for parsing the array-related
attributes such as arrayType and offset, and setting up the ArrayList that
we're going to write into as we parse the sub-elements (array items).  Then
onStartChild() gets called for each sub-element, during which we a) check
the child's attributes to determine if is has a specific array index, b)
figure out the child's type (defaulting to the array component type), c) go
get a Deserializer for that type, and d) tell that deserializer to call us
back once it's got a value.

NOTE: in general, when deserializing structured data, you want to use the
callback pattern to get data from sub-deserializers, rather than trying to
pull the data from the deserializer yourself when you get the onEndChild()
call.  The reason for this is that the endElement event might occur before
the actual data is available:

<myData xsi:type="myNS:myData">
  <subElement href="#1"/>
</myData>
<multiRef id="1">This is the real data</multiref>

In this case, the MyDataSerializer would get an onStartChild() call for
subElement, during which we'd create a deserializer for a String (assumedly
because we know that's what to expect for this type).  Since the subElement
element has no content, the next thing to occur would be the onEndChild() in
MyDataSerializer.  At that point, the string deserializer wouldn't yet have
a value, so trying to get it wouldn't do you any good.  So instead, if we
register a callback, then later on when the system links up the multiRef
element with the subElement deserializer, our data naturally plops itself
into the right place.

> Thanks for your help.

I hope this is somewhat comprehensible... a more detailed writeup of all
this stuff should happen at some point, but hopefully this will get you a
little further.

--Glen



Re: Deserialization: onStartChild vs. onStartElement

Posted by Glen Daniels <gd...@macromedia.com>.
Hi Frederic!

----- Original Message -----
From: "Frederic Desjarlais" <fr...@enjiva.com>
To: <ax...@xml.apache.org>
Sent: Saturday, August 18, 2001 1:15 AM
Subject: Deserialization: onStartChild vs. onStartElement


>
> Could someone help explain how to deserialize the following (cleaned up)
> SOAP response (from the POV of a SOAP client receiving a response):
>
> <SOAP-ENV:Envelope>
> <SOAP-ENV:Body>
> <getInfoResponse><output><text>Some text</text></output></getInfoResponse>
> </SOAP-ENV:Body>
> </SOAP-ENV:Envelope>

I'm assuming "cleaned up" means "simplified"?  In other words, there's more
structure to the data than appears to be here.  Not to discourage your from
writing your own ser/desers, but in a lot of common cases you can just use
the BeanSerializer...

> 1. Called addDeserializerFactory() twice, once for <output> and another
for
> <text> elements (I used two different deserializer factories/classes)
> 2. Called addOutputParam() to set <output> as output parameter (e.g.
> sd.addOutputParam("output", outputQName); )

Check.

> 3. Created 2 deserialization classes: OutputDeserializer and
> TextDeserializer which both extend Deserializer and implement Serializer.

OK.  Again, I assume there's more to "text" than just a String, right?
Otherwise you'd just use the basic mapping for String...

> I used 'onStartChild' in the OutputDeserializer and I got it to work, but
> I'd like to use onStartElement instead (and determine which deserializer
to
> pass control over to).

OK, let's look at this in detail from the point at which the SAX parser
starts the <output> element - at this point, the top handler on the stack in
the DeserializationContext is the RPCHandler.  The way the events will
happen is as follows (you can trace this by recompiling with the DEBUG_LOG
flag in DeserializationContext and/or RPCHandler set to "true"):

1. startElement[ output ]
  1.1 RPCHandler (the active SOAPHandler in DeserializationContext)
      gets an onStartChild() call, and tries to find an appropriate
      Deserializer.  It does this by first looking at the xsi:type
      attribute (if any) on the element.  If it doesn't find one,
      it tries the ServiceDescription.  In this case, since it's
      a response, and the serviceDescription has an output param
      registered, we figure the type is outputQName, and as a
      result, onStartChild() will return a new instance of your
      OutputSerializer (or whatever you call it).
  1.2 DeserializationContext now calls startElement() on your
      deserializer.  You can't override startElement() directly,
      since it's critical that the base Deserializer gets to
      handle ID/HREF linkage - however, assuming this is not
      an element with an href attribute, the base class will then
      call onStartElement(), which is there expressly for the
      purpose of being overridden by subclasses.  So that's one
      place you can insert your own functionality.
2. startElement[ text ]
  2.1 OutputSerializer (the now-active SOAPHandler) gets an
      onStartChild() call, which you can do with as you please.

** onStartChild is the main hook for dealing with structured data **

The onStartChild() method returns a SOAPHandler, which is typically a
Deserializer, which should appropriately deal with each subelement in your
structured XML.

> When should Deserializers use onStartChild vs. onStartElement?  What would
a
> Deserializer using onStartElement look like for the above case (and in
> general)?

onStartElement() is the method to use when you need to do processing for the
initial element, and onStartChild() is used to decide which handler to use
to deserialize sub-elements.

A good example of this is the ArraySerializer.  Take a look at the
onStartElement() method - it is responsible for parsing the array-related
attributes such as arrayType and offset, and setting up the ArrayList that
we're going to write into as we parse the sub-elements (array items).  Then
onStartChild() gets called for each sub-element, during which we a) check
the child's attributes to determine if is has a specific array index, b)
figure out the child's type (defaulting to the array component type), c) go
get a Deserializer for that type, and d) tell that deserializer to call us
back once it's got a value.

NOTE: in general, when deserializing structured data, you want to use the
callback pattern to get data from sub-deserializers, rather than trying to
pull the data from the deserializer yourself when you get the onEndChild()
call.  The reason for this is that the endElement event might occur before
the actual data is available:

<myData xsi:type="myNS:myData">
  <subElement href="#1"/>
</myData>
<multiRef id="1">This is the real data</multiref>

In this case, the MyDataSerializer would get an onStartChild() call for
subElement, during which we'd create a deserializer for a String (assumedly
because we know that's what to expect for this type).  Since the subElement
element has no content, the next thing to occur would be the onEndChild() in
MyDataSerializer.  At that point, the string deserializer wouldn't yet have
a value, so trying to get it wouldn't do you any good.  So instead, if we
register a callback, then later on when the system links up the multiRef
element with the subElement deserializer, our data naturally plops itself
into the right place.

> Thanks for your help.

I hope this is somewhat comprehensible... a more detailed writeup of all
this stuff should happen at some point, but hopefully this will get you a
little further.

--Glen