You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@commons.apache.org by Valerio Schiavoni <va...@gmail.com> on 2006/04/21 15:23:25 UTC

[Digester] parameter value from a parent node attribute

Hello,
i have the following xml:
<root>
<element id="A">
   <subelement id="B"/>
</element>
</root>

whenever I match path element/subelement, i need to invoke a 2-parameters
method  defined on the object on the stack
,call it twoParamMethod(String idElement,String idSubElement).

i was able to code up to this code:

digester.addCallMethod("root/element/subelement", "twoParamMethod", 2);
//HOW DO I GET THE VALUE FOR THE FIRST PARAMETER ?
 digester.addCallParam("root/element/subelement", 1, "id"); //value for the
second parameter

i need to this to fill in a map (stored in the root object) key="element
id", values="list of id-s of subelements";

thanks,
valerio

--
http://valerioschiavoni.blogspot.com
http://jroller.com/page/vschiavoni

Re: [Digester] parameter value from a parent node attribute

Posted by Valerio Schiavoni <va...@gmail.com>.
Hello Simon

On 4/26/06, Simon Kitching <sk...@apache.org> wrote:
>
>   <committer>
>     <element id="xx">
>       <children>
>         <subelement refid="y1"/>
>         <subelement refid="y2"/>


you got it !

A ConfigurationDescriptor is created at <committer> only. This class has
> methods
>   addElementType(String id);
>   addChildForType(String id, String refId)


correct again.

It would be far easier if you had an object that represented an element
> with a particular id (ie something was created for each <element> tag)


even if it is 'easier', I don't agree completely: creating a full Object
only to store the the value of a String sounds a bit weird.

If you just can't or won't alter your model, you could possibly use a
> digester "named stack" to keep track of the element id:


actually, i was investigating into this solution, and after you showed me
the way, I was able to get it correctly working!

Regardless, if you do alter any digester stack (and esp. the main one),
> you should only ever push things in begin(), and only ever pop them in
> end().


i will  tattoo this on my  leg :)

--
http://valerioschiavoni.blogspot.com
http://jroller.com/page/vschiavoni

Re: [Digester] parameter value from a parent node attribute

Posted by Simon Kitching <sk...@apache.org>.
Hi Valerio,

If I understand you correctly, your input has:
  <committer>
    <element id="xx">
      <children>
        <subelement refid="y1"/>
        <subelement refid="y2"/>
etc.

A ConfigurationDescriptor is created at <committer> only. This class has
methods
  addElementType(String id);
  addChildForType(String id, String refId)

It would be far easier if you had an object that represented an element
with a particular id (ie something was created for each <element> tag)
rather than an object that contains a "map". Your object model and xml
model would then be similar, and you could have:
  class Element
    setId(String id)
    addChild(String refId)
which is *much* easier to write digester rules for, and just seems a
nicer object model to me.

As an alternative, if you can alter your ConfigurationDescriptor class
to have a concept of "current id", then this would also make digester
rules easier (though I don't think it's so elegant):
  class ConfigurationDescriptor
    setCurrentElementType(String id)
    addChild(String refId); // uses current element type

If you just can't or won't alter your model, you could possibly use a
digester "named stack" to keep track of the element id:

public class ElementRule {
  public void begin(..., attrs) {
    // just save away info for use by ChildElementRule
    String id = attrs.get("id");
    digester.push(CUSTOM_STACK_NAME, id);
  }

  public void end(...) {
    digester.pop(CUSTOM_STACK_NAME);
  }
}

public class ChildElementRule {
  public void begin(..., attrs) {

    String refId = attrs.get("refId");
    String currentElementId = (String) digester.peek(CUSTOM_STACK_NAME);

    ConfigurationDescriptor hd = 
      (ConfigrationDescriptor) digester.peek();

    hd.addChildForType(currentElementId, refId);
  }
}

You could do something similar without using named stacks, but would
then have to use 
   digester.peek(1)
to "skip" over the id when accessing the ConfigurationDescriptor object
etc.

I may have misunderstood this code, as I don't see why you are
pushing/popping a Map containing the id instead of just using a String
containing the id..

Regardless, if you do alter any digester stack (and esp. the main one),
you should only ever push things in begin(), and only ever pop them in
end().

Regards,

Simon

On Wed, 2006-04-26 at 11:46 +0200, Valerio Schiavoni wrote:
> Hello Simon,
> thanks for the explanation you provided. I thought about writing a
> custom rule, and it actually worked for all but one test case, and
> this is why i'm replying.

> 
> 
> On 4/22/06, Simon Kitching <sk...@apache.org> wrote:
>         > <root>
>         > <element id="A">
>         >    <subelement id="B"/>
>         > </element>
>         > </root>
> 
> in the following code, root == committer; and the path
> root/element/subelement 
> is committer/element/children/subelement (but same semantics);
> 
> digester.addObjectCreate("committer", ConfigurationDescriptor.class);
> digester.addRule("committer/element",new ElementRule()); 
> where this first custom rule looks like:
> 
> private class ElementRule extends Rule {
>         public void begin( String ns, String name, 
>                 Attributes att) {
>             Map<String,String> ctx = new HashMap<String,String>(); 
>             String value = att.getValue("id");
>             ConfigurationDescriptor hd = 
> (ConfigurationDescriptor) getDigester().peek();
>             hd.addElementType(value);
>             ctx.put("ROOT_ID", value); 
>             getDigester().push( ctx);
>           }
>     }
>  
> digester.addRule("committer/element/children/element", new
> ChildElementRule());
> private class ChildElementRule extends Rule{
>         
>         public void begin( String ns, String name, Attributes att) {
>             
>             Map<String,String> ctx = ( Map) getDigester().pop();
>             String parentId =  ctx.get("ROOT_ID"); 
>             String childElementId = att.getValue("refid");
>             
>             ConfigurationDescriptor hd = (ConfigurationDescriptor)
> getDigester().peek();
>             hd.addChildForType(parentId, childElementId); 
>             
>           }
>     }
> 
> and everything goes fine until i run a test with the following input:
> rootCategory_twoChildrenType.xml : 
> 
> <committer>
> <element id="category"> 
> <children>
> <element refid="client" maxocc="-1" />
> <element refid="folder" maxocc="-1" />
> </children>
> </element>
> <element id="client"/> 
> </committer>
> 
> and I run a test like:
> public void testExactlyTwoChildrenElement() throws Throwable {
>         ConfigurationDescriptor hd =
> hr.read(rootCategory_twoChildrenType);
>         assertEquals("Assert on childrens by ID"+ROOT_TYPE, 
>                 2, hd.typeChildrenForType(ROOT_TYPE).size());
>     }
> 
> and i get a ClassCastException in the ChildElementRule when I do
> something like:
> Map<String,String> ctx = ( Map) getDigester().pop(); 
> where it founds a ConfigurationDescriptor class instead.
> 
> the code of the digester is here: http://veleno.pastebin.com/682606
> and i get a stacktrace: http://veleno.pastebin.com/682612
> 
> probably somewhere I should do a pop() but i don't understand where :(
> 
> 
> 
>         >
>         > whenever I match path element/subelement, i need to invoke a
>         2-parameters
>         > method  defined on the object on the stack
>         > ,call it twoParamMethod(String idElement,String
>         idSubElement).
>         >
>         > i was able to code up to this code: 
>         >
>         > digester.addCallMethod("root/element/subelement",
>         "twoParamMethod", 2);
>         > //HOW DO I GET THE VALUE FOR THE FIRST PARAMETER ?
>         
>         You can get the "previous object on the stack" via: 
>           // See addCallParam(String pattern, int paramIndex, int
>         stackIndex)
>           digester.addCallParam("root/element/subelement", 0, 1);
>         
>         Assuming you have an ObjectCreateRule for <element> and
>         <subelement> 
>         this will pass the <element> as the 0th parameter.
>         
>         However the CallMethodRule is probably not what you want; by
>         default it
>         calls the object that is on the top of the stack. So in your
>         case,
>         you'll end up invoking methods on the object created by the 
>         ObjectCreateRule for <subelement>. Maybe you want SetTopRule?
>         Or maybe
>         you want one of the CallMethodRule constructors that take a
>         targetOffset
>         parameter (NB: these don't have factory methods on Digester
>         because they 
>         are only rarely needed).
>         
>         >  digester.addCallParam("root/element/subelement", 1,
>         "id"); //value for the
>         > second parameter
>         >
>         > i need to this to fill in a map (stored in the root object)
>         key="element 
>         > id", values="list of id-s of subelements";
>         
>         Before doing any further implementation, though, I suggest you
>         think
>         very carefully about your object model. When people need to
>         start doing
>         such tricky things with stack offsets it usually means that
>         the java
>         object model they are building doesn't correspond properly to
>         the xml.
>         Often the solution is to fix the object model rather than
>         write tricky
>         digester rules. 
>         
>         Regards,
>         
>         Simon
>         
>         
>         
>         ---------------------------------------------------------------------
>         To unsubscribe, e-mail:
>         commons-user-unsubscribe@jakarta.apache.org
>         For additional commands, e-mail:
>         commons-user-help@jakarta.apache.org
>         
> 
> 
> 
> -- 
> http://valerioschiavoni.blogspot.com
> http://jroller.com/page/vschiavoni


---------------------------------------------------------------------
To unsubscribe, e-mail: commons-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-user-help@jakarta.apache.org


Re: [Digester] parameter value from a parent node attribute

Posted by Valerio Schiavoni <va...@gmail.com>.
Hello Simon,
thanks for the explanation you provided. I thought about writing a custom
rule, and it actually worked for all but one test case, and this is why i'm
replying.

On 4/22/06, Simon Kitching <sk...@apache.org> wrote:
>
> > <root>
> > <element id="A">
> >    <subelement id="B"/>
> > </element>
> > </root>


in the following code, root == committer; and the path
root/element/subelement
is committer/element/children/subelement (but same semantics);

digester.addObjectCreate("committer", ConfigurationDescriptor.class);
digester.addRule("committer/element",new ElementRule());
where this first custom rule looks like:

private class ElementRule extends Rule {
        public void begin( String ns, String name,
                Attributes att) {
            Map<String,String> ctx = new HashMap<String,String>();
            String value = att.getValue("id");
            ConfigurationDescriptor hd =
(ConfigurationDescriptor) getDigester().peek();
            hd.addElementType(value);
            ctx.put("ROOT_ID", value);
            getDigester().push( ctx);
          }
    }

digester.addRule("committer/element/children/element", new
ChildElementRule());
private class ChildElementRule extends Rule{

        public void begin( String ns, String name, Attributes att) {

            Map<String,String> ctx = ( Map) getDigester().pop();
            String parentId =  ctx.get("ROOT_ID");
            String childElementId = att.getValue("refid");

            ConfigurationDescriptor hd = (ConfigurationDescriptor)
getDigester().peek();
            hd.addChildForType(parentId, childElementId);

          }
    }

and everything goes fine until i run a test with the following input:
rootCategory_twoChildrenType.xml :

<committer>
<element id="category">
<children>
<element refid="client" maxocc="-1" />
<element refid="folder" maxocc="-1" />
</children>
</element>
<element id="client"/>
</committer>

and I run a test like:
public void testExactlyTwoChildrenElement() throws Throwable {
        ConfigurationDescriptor hd = hr.read(rootCategory_twoChildrenType);
        assertEquals("Assert on childrens by ID"+ROOT_TYPE,
                2, hd.typeChildrenForType(ROOT_TYPE).size());
    }

and i get a ClassCastException in the ChildElementRule when I do something
like:
Map<String,String> ctx = ( Map) getDigester().pop();
where it founds a ConfigurationDescriptor class instead.

the code of the digester is here: http://veleno.pastebin.com/682606
and i get a stacktrace: http://veleno.pastebin.com/682612

probably somewhere I should do a pop() but i don't understand where :(



>
> > whenever I match path element/subelement, i need to invoke a
> 2-parameters
> > method  defined on the object on the stack
> > ,call it twoParamMethod(String idElement,String idSubElement).
> >
> > i was able to code up to this code:
> >
> > digester.addCallMethod("root/element/subelement", "twoParamMethod", 2);
> > //HOW DO I GET THE VALUE FOR THE FIRST PARAMETER ?
>
> You can get the "previous object on the stack" via:
>   // See addCallParam(String pattern, int paramIndex, int stackIndex)
>   digester.addCallParam("root/element/subelement", 0, 1);
>
> Assuming you have an ObjectCreateRule for <element> and <subelement>
> this will pass the <element> as the 0th parameter.
>
> However the CallMethodRule is probably not what you want; by default it
> calls the object that is on the top of the stack. So in your case,
> you'll end up invoking methods on the object created by the
> ObjectCreateRule for <subelement>. Maybe you want SetTopRule? Or maybe
> you want one of the CallMethodRule constructors that take a targetOffset
> parameter (NB: these don't have factory methods on Digester because they
> are only rarely needed).
>
> >  digester.addCallParam("root/element/subelement", 1, "id"); //value for
> the
> > second parameter
> >
> > i need to this to fill in a map (stored in the root object) key="element
> > id", values="list of id-s of subelements";
>
> Before doing any further implementation, though, I suggest you think
> very carefully about your object model. When people need to start doing
> such tricky things with stack offsets it usually means that the java
> object model they are building doesn't correspond properly to the xml.
> Often the solution is to fix the object model rather than write tricky
> digester rules.
>
> Regards,
>
> Simon
>
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: commons-user-unsubscribe@jakarta.apache.org
> For additional commands, e-mail: commons-user-help@jakarta.apache.org
>
>


--
http://valerioschiavoni.blogspot.com
http://jroller.com/page/vschiavoni

Re: [Digester] parameter value from a parent node attribute

Posted by Simon Kitching <sk...@apache.org>.
On Fri, 2006-04-21 at 15:23 +0200, Valerio Schiavoni wrote:
> Hello,
> i have the following xml:
> <root>
> <element id="A">
>    <subelement id="B"/>
> </element>
> </root>
> 
> whenever I match path element/subelement, i need to invoke a 2-parameters
> method  defined on the object on the stack
> ,call it twoParamMethod(String idElement,String idSubElement).
> 
> i was able to code up to this code:
> 
> digester.addCallMethod("root/element/subelement", "twoParamMethod", 2);
> //HOW DO I GET THE VALUE FOR THE FIRST PARAMETER ?

You can get the "previous object on the stack" via:
  // See addCallParam(String pattern, int paramIndex, int stackIndex)
  digester.addCallParam("root/element/subelement", 0, 1);

Assuming you have an ObjectCreateRule for <element> and <subelement>
this will pass the <element> as the 0th parameter.

However the CallMethodRule is probably not what you want; by default it
calls the object that is on the top of the stack. So in your case,
you'll end up invoking methods on the object created by the
ObjectCreateRule for <subelement>. Maybe you want SetTopRule? Or maybe
you want one of the CallMethodRule constructors that take a targetOffset
parameter (NB: these don't have factory methods on Digester because they
are only rarely needed).

>  digester.addCallParam("root/element/subelement", 1, "id"); //value for the
> second parameter
> 
> i need to this to fill in a map (stored in the root object) key="element
> id", values="list of id-s of subelements";

Before doing any further implementation, though, I suggest you think
very carefully about your object model. When people need to start doing
such tricky things with stack offsets it usually means that the java
object model they are building doesn't correspond properly to the xml.
Often the solution is to fix the object model rather than write tricky
digester rules.

Regards,

Simon



---------------------------------------------------------------------
To unsubscribe, e-mail: commons-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-user-help@jakarta.apache.org


Re: [Digester] parameter value from a parent node attribute

Posted by Rahul Akolkar <ra...@gmail.com>.
On 4/21/06, Rahul Akolkar <ra...@gmail.com> wrote:
> On 4/21/06, Valerio Schiavoni <va...@gmail.com> wrote:
> > Hello,
> > i have the following xml:
> > <root>
> > <element id="A">
> >   <subelement id="B"/>
> > </element>
> > </root>
> >
> > whenever I match path element/subelement, i need to invoke a 2-parameters
> > method  defined on the object on the stack
> > ,call it twoParamMethod(String idElement,String idSubElement).
> >
> > i was able to code up to this code:
> >
> > digester.addCallMethod("root/element/subelement", "twoParamMethod", 2);
> > //HOW DO I GET THE VALUE FOR THE FIRST PARAMETER ?
> <snip/>
>
> Personally, I wouldn't use CallMethodRule here, but I rarely use it
> anywhere. In this case, some options:
>
>  * Add a SetNextRule where you have access to both the "element" and
> "subelement", and therefore, all params to call the method
>
<snip/>

... or a SetTopRule.

>  * Add a custom rule that peeks one down the stack to find the
> "element" (custom rules are not hard, ofcourse if you can do what you
> need with "provided" rules, thats better most of the times)
>
> -Rahul
>
<snap/>

---------------------------------------------------------------------
To unsubscribe, e-mail: commons-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-user-help@jakarta.apache.org


Re: [Digester] parameter value from a parent node attribute

Posted by Rahul Akolkar <ra...@gmail.com>.
On 4/21/06, Valerio Schiavoni <va...@gmail.com> wrote:
> Hello,
> i have the following xml:
> <root>
> <element id="A">
>   <subelement id="B"/>
> </element>
> </root>
>
> whenever I match path element/subelement, i need to invoke a 2-parameters
> method  defined on the object on the stack
> ,call it twoParamMethod(String idElement,String idSubElement).
>
> i was able to code up to this code:
>
> digester.addCallMethod("root/element/subelement", "twoParamMethod", 2);
> //HOW DO I GET THE VALUE FOR THE FIRST PARAMETER ?
<snip/>

Personally, I wouldn't use CallMethodRule here, but I rarely use it
anywhere. In this case, some options:

 * Add a SetNextRule where you have access to both the "element" and
"subelement", and therefore, all params to call the method

 * Add a custom rule that peeks one down the stack to find the
"element" (custom rules are not hard, ofcourse if you can do what you
need with "provided" rules, thats better most of the times)

-Rahul


>  digester.addCallParam("root/element/subelement", 1, "id"); //value for the
> second parameter
>
> i need to this to fill in a map (stored in the root object) key="element
> id", values="list of id-s of subelements";
>
> thanks,
> valerio
>
> --
> http://valerioschiavoni.blogspot.com
> http://jroller.com/page/vschiavoni
>
>

---------------------------------------------------------------------
To unsubscribe, e-mail: commons-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-user-help@jakarta.apache.org