Digester wildcard question

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Digester wildcard question

Frank W. Zammetti
Hi... I went through the Digester docs, but couldn't find the answer to my
specific question (although the answer seemed to be hinted at)... is it
possible to have a matching pattern of simply "*" so that every element in
the document being parsed fires the corresponding rule?

What I'm trying to do is have a simple XML format with a list of elements:

<myDoc>
 <item1>11</item1>
 <item2>22</item2>
 <item3>33</item3>
</myDoc>

... and I want to have a rule fire to take each of those and insert them
in to a map (just a call to a generic setter), but without knowing
before-hand that item1, item2 and item3 will be present (i.e., maybe it's
item4, item5 and item6 instead).  So I figured just match "*" would do it,
but am looking for verification.  TIA!

--
Frank W. Zammetti
Founder and Chief Software Architect
Omnytex Technologies
http://www.omnytex.com

---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Digester wildcard question

Frank W. Zammetti
Ok, I played a bit and I think I'm fairly close...

I have the following code...

// ** sc is a valid ServletContext reference
String configFile = "/WEB-INF/config.xml";
Digester digester = new Digester();
digester.setValidating(false);
AppConfig ac = new AppConfig();
ac.setDigester(digester);
digester.push(ac);
digester.addCallMethod("config/*", "set", 0);
InputStream isConfigFile = sc.getResourceAsStream(configFile);
ac = (AppConfig)digester.parse(isConfigFile);

...AppConfig (condensed) looks like this...

public class AppConfig {
  private static HashMap configMap = new HashMap();
  private Digester digester;
  public void setDigester(Digester inDigester) {
    digester = inDigester;
  }
  public void set(String val) {
    configMap.put(digester.getCurrentElementName(), val);
  }
}

...AppConfig also contains an overriden toString() so I can display it
easily for debugging.  Just for completeness, here's the XML I'm trying to
parse...

<config>
 <item1>Value1</item1>
 <item2>Value2</item2>
 <item3>Value3</item3>
</config>

...So, the idea is simply that for each element encountered, I want an
entry in the configMap added, keyed by the element name (so,
item1="Value1", and so on), the idea being that I can support XML where I
don't know what elements will be there before-hand, aside from the root
guaranteed to be <config> and the structure to be "flat", i.e., no
elements nested in any element other than the root.

When I execute this, the configMap is empty at the end, so the values are
not getting set.  However, if I change...

digester.addCallMethod("config/*", "set", 0);

...to...

digester.addCallMethod("config/item1", "set", 0);
digester.addCallMethod("config/item2", "set", 0);
digester.addCallMethod("config/item3", "set", 0);

...then at the end, the configMap in fact does have three items in it,
each with the correct value.  So, in short, the wildcard specification is
not doing what I was hoping it would, everything else *appears* to be
working like I want.

So, the question is now simple: can wildcards be used in this fashion, or
is my expectation not correct?  If not, is there a way to configure the
rules to do what I'm trying to do?  If not, does this seem like a
reasonable enhancement to submit?

Thanks once again!

--
Frank W. Zammetti
Founder and Chief Software Architect
Omnytex Technologies
http://www.omnytex.com

On Mon, June 20, 2005 2:01 pm, Frank W. Zammetti said:

> Hi... I went through the Digester docs, but couldn't find the answer to my
> specific question (although the answer seemed to be hinted at)... is it
> possible to have a matching pattern of simply "*" so that every element in
> the document being parsed fires the corresponding rule?
>
> What I'm trying to do is have a simple XML format with a list of elements:
>
> <myDoc>
>  <item1>11</item1>
>  <item2>22</item2>
>  <item3>33</item3>
> </myDoc>
>
> ... and I want to have a rule fire to take each of those and insert them
> in to a map (just a call to a generic setter), but without knowing
> before-hand that item1, item2 and item3 will be present (i.e., maybe it's
> item4, item5 and item6 instead).  So I figured just match "*" would do it,
> but am looking for verification.  TIA!
>
> --
> Frank W. Zammetti
> Founder and Chief Software Architect
> Omnytex Technologies
> http://www.omnytex.com
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Digester wildcard question

Simon Kitching
In reply to this post by Frank W. Zammetti
On Mon, 2005-06-20 at 14:01 -0400, Frank W. Zammetti wrote:

> Hi... I went through the Digester docs, but couldn't find the answer to my
> specific question (although the answer seemed to be hinted at)... is it
> possible to have a matching pattern of simply "*" so that every element in
> the document being parsed fires the corresponding rule?
>
> What I'm trying to do is have a simple XML format with a list of elements:
>
> <myDoc>
>  <item1>11</item1>
>  <item2>22</item2>
>  <item3>33</item3>
> </myDoc>
>
> ... and I want to have a rule fire to take each of those and insert them
> in to a map (just a call to a generic setter), but without knowing
> before-hand that item1, item2 and item3 will be present (i.e., maybe it's
> item4, item5 and item6 instead).  So I figured just match "*" would do it,
> but am looking for verification.  TIA!
>

The standard rule-matching engine (RulesBase) doesn't support this. It
allows leading wildcards (eg "*/item1"), but doesn't support a wildcard
on its own.

The ExtendedBaseRules class allows trailing wildcards, so "myDoc/*" will
match any direct child element of myDoc which is what you want I
believe. It also allows completely-wild expressions "*" and "!*". It
doesn't have any expression to specify "any descendant of myDoc". See
the javadoc for class ExtendedBaseRules for more info.

The RegExRules class is even more powerful (but slower).

There is a sneaky way of effectively supporting trailing-wildcard with
the standard RulesBase matching engine if you are writing your own rule;
the SetNestedElementRule class has an example of this. But
ExtendedBaseRules is probably what you are looking for.

Regards,

Simon




---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Digester wildcard question

Frank W. Zammetti
Thank you *very* much Simon, that was exactly what I needed!  I wound up
using "config/?" as the pattern, and that did the trick perfectly,
everything else worked as-is.

You know, Digester has always struck me as one of those things that is a
  bit overwhelming for a while, but it really is one of the more
powerful and useful Commons projects out there.

Thank again!

Frank

Simon Kitching wrote:

> On Mon, 2005-06-20 at 14:01 -0400, Frank W. Zammetti wrote:
>
>>Hi... I went through the Digester docs, but couldn't find the answer to my
>>specific question (although the answer seemed to be hinted at)... is it
>>possible to have a matching pattern of simply "*" so that every element in
>>the document being parsed fires the corresponding rule?
>>
>>What I'm trying to do is have a simple XML format with a list of elements:
>>
>><myDoc>
>> <item1>11</item1>
>> <item2>22</item2>
>> <item3>33</item3>
>></myDoc>
>>
>>... and I want to have a rule fire to take each of those and insert them
>>in to a map (just a call to a generic setter), but without knowing
>>before-hand that item1, item2 and item3 will be present (i.e., maybe it's
>>item4, item5 and item6 instead).  So I figured just match "*" would do it,
>>but am looking for verification.  TIA!
>>
>
>
> The standard rule-matching engine (RulesBase) doesn't support this. It
> allows leading wildcards (eg "*/item1"), but doesn't support a wildcard
> on its own.
>
> The ExtendedBaseRules class allows trailing wildcards, so "myDoc/*" will
> match any direct child element of myDoc which is what you want I
> believe. It also allows completely-wild expressions "*" and "!*". It
> doesn't have any expression to specify "any descendant of myDoc". See
> the javadoc for class ExtendedBaseRules for more info.
>
> The RegExRules class is even more powerful (but slower).
>
> There is a sneaky way of effectively supporting trailing-wildcard with
> the standard RulesBase matching engine if you are writing your own rule;
> the SetNestedElementRule class has an example of this. But
> ExtendedBaseRules is probably what you are looking for.
>
> Regards,
>
> Simon
>
>
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>
>
>
>

--
Frank W. Zammetti
Founder and Chief Software Architect
Omnytex Technologies
http://www.omnytex.com


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Digester wildcard question

Frank W. Zammetti
Hey Simon, or anyone else... I'm hoping you can help me over another
hump I'm encountering (man, that sounds *awful*, but I digress...)

I have the following code:

String configClass = "my.app.SampleAppConfigBean";
String configFile = "/WEB-INF/config.xml";
Digester digester = new Digester();
digester.setRules(new ExtendedBaseRules());
digester.setValidating(false);
digester.addObjectCreate("config", configClass);
digester.addBeanPropertySetter("config/?");
InputStream isConfigFile = servletContext.getResourceAsStream(configFile);
digester.parse(isConfigFile);

The idea, ultimately, is that I would be getting the value of
configClass from a context init param.  So I want Digester to create an
instance of the named class, and then for each element encountered in
the XML file, call the appropriate setter.

Now, I'm using essentially the same XML as before...

<config>
  <item1>Value1</item1>
  <item2>Value2</item2>
  <item3>Value3</item3>
</config>

...and here is that class I'm trying to populate...

public class SampleAppConfigBean {
   private static String item1;
   private static String item2;
   private static String item3;
   public static void setItem1(String val) {
     item1 = val;
   }
   public static String getItem1() {
     return item1;
   }
   public static void setItem2(String val) {
     item2 = val;
   }
   public static String getItem2() {
     return item2;
   }
   public static void setItem3(String val) {
     item3 = val;
   }
   public static String getItem3() {
     return item3;
   }
}

Pretty straight-forward, or so I thought :)  When I try it, I get the
following exception...

Jun 20, 2005 9:58:22 PM org.apache.commons.digester.Digester endElement
SEVERE: End event threw exception
java.lang.NoSuchMethodException: Bean has no property named item1
         at
org.apache.commons.digester.BeanPropertySetterRule.end(BeanPropertySe
tterRule.java:238)
         at
org.apache.commons.digester.Digester.endElement(Digester.java:1058)

Now, it sure looks to me like that bean has an item1 property :)  I
tried making it public, just on a whim, but that made no difference (and
I'm kind of glad it didn't because that would have confused the hell out
of me!)

So, I'm sure it's something simple that I don't know, hoping someone can
point it out.  Thanks again!

Frank

Frank W. Zammetti wrote:

> Thank you *very* much Simon, that was exactly what I needed!  I wound up
> using "config/?" as the pattern, and that did the trick perfectly,
> everything else worked as-is.
>
> You know, Digester has always struck me as one of those things that is a
>  bit overwhelming for a while, but it really is one of the more powerful
> and useful Commons projects out there.
>
> Thank again!
>
> Frank
>
> Simon Kitching wrote:
>
>> On Mon, 2005-06-20 at 14:01 -0400, Frank W. Zammetti wrote:
>>
>>> Hi... I went through the Digester docs, but couldn't find the answer
>>> to my
>>> specific question (although the answer seemed to be hinted at)... is it
>>> possible to have a matching pattern of simply "*" so that every
>>> element in
>>> the document being parsed fires the corresponding rule?
>>>
>>> What I'm trying to do is have a simple XML format with a list of
>>> elements:
>>>
>>> <myDoc>
>>> <item1>11</item1>
>>> <item2>22</item2>
>>> <item3>33</item3>
>>> </myDoc>
>>>
>>> ... and I want to have a rule fire to take each of those and insert them
>>> in to a map (just a call to a generic setter), but without knowing
>>> before-hand that item1, item2 and item3 will be present (i.e., maybe
>>> it's
>>> item4, item5 and item6 instead).  So I figured just match "*" would
>>> do it,
>>> but am looking for verification.  TIA!
>>>
>>
>>
>> The standard rule-matching engine (RulesBase) doesn't support this. It
>> allows leading wildcards (eg "*/item1"), but doesn't support a wildcard
>> on its own.
>>
>> The ExtendedBaseRules class allows trailing wildcards, so "myDoc/*" will
>> match any direct child element of myDoc which is what you want I
>> believe. It also allows completely-wild expressions "*" and "!*". It
>> doesn't have any expression to specify "any descendant of myDoc". See
>> the javadoc for class ExtendedBaseRules for more info.
>>
>> The RegExRules class is even more powerful (but slower).
>>
>> There is a sneaky way of effectively supporting trailing-wildcard with
>> the standard RulesBase matching engine if you are writing your own rule;
>> the SetNestedElementRule class has an example of this. But
>> ExtendedBaseRules is probably what you are looking for.
>>
>> Regards,
>>
>> Simon
>>
>>
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: [hidden email]
>> For additional commands, e-mail: [hidden email]
>>
>>
>>
>>
>>
>

--
Frank W. Zammetti
Founder and Chief Software Architect
Omnytex Technologies
http://www.omnytex.com


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Digester wildcard question

Frank W. Zammetti
Good call, making the methods non-static solved the problem.

I'm not sure how I feel about leaving it like that though, so I will go
off and check out the other suggestions you offered... I actually just
spent a few minutes playing with the CallMethodRule... I came up with
what I thought was a clever and elegant solution, but it doesn't seem to
work... I created the following class:

public class MyRule extends CallMethodRule {
   public MyRule() {
     super("");
   }
   public void body(String bodyText) throws Exception {
     String cem = digester.getCurrentElementName();
     methodName = "set" + cem.substring(0, 1).toUpperCase() +
cem.substring(1);
     super.body(bodyText);
   }
}

And I did:

digester.addRule("config/?", new MyRule());

...in the calling code.  I got an exception that doesn't seem to make
sense...

SEVERE: End event threw exception
java.lang.NoSuchMethodException: No such accessible method: setItem1()
on object
: jwp.sampleapp.SampleAppConfigBean

I thought it might be the same kind of static vs. non-static thing, but
I've tried the methods both static and non-static, made no difference.

I'm about ready to "give up" and just leave the methods non-static...
because the bean being populated stores static condfiguration
information though, it would be nicer to not have to instantiate it to
get at the config info.  But. it's not the end of the world or anything.
  But if you have any ideas about why the above didn't work, maybe there
is still a chance of not having to do that.

I will look at SetNestedPropertiesRule in a bit too, haven't got around
to that yet.

Frank

Simon Kitching wrote:

> Hi Frank,
>
> On Mon, 2005-06-20 at 22:13 -0400, Frank W. Zammetti wrote:
>
>>...and here is that class I'm trying to populate...
>>
>>public class SampleAppConfigBean {
>>   private static String item1;
>>   private static String item2;
>>   private static String item3;
>>   public static void setItem1(String val) {
>>     item1 = val;
>>   }
>
>
>>Now, it sure looks to me like that bean has an item1 property :)
>
>
> I'm guessing that the fact that the method is static is where the
> problem lies, because everything else looks good. The
> BeanPropertySetterRule really does expect the target properties to be
> valid javabean properties, which is different from there just being a
> setter method. Unfortunately Sun haven't clearly documented exactly what
> the rules are for javabean properties (at least anywhere I can find).
>
> You could try writing a trivial test class which calls
> java.beans.Introspector.getBeanInfo(SampleAppConfigBean) and see what
> java tells you the available bean properties are.
>
> I bet that setItem1 etc are not reported - and that if you make the
> methods non-static that they will be.
>
> If this is the case, and you really need these methods to be static then
> you could resort to using CallMethodRule instead of
> BeanPropertySetterRule, as CallMethodRule doesn't require its targets to
> comply with the javabean rules.
>
> Have you had a look at the SetNestedPropertiesRule? As its javadoc
> describes, it should handle the problem you have but with the standard
> RulesBase class (though it does require the target is a property like
> BeanPropertySetterRule does). Either approach is ok though...
>
> Regards,
>
> Simon
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [hidden email]
> For additional commands, e-mail: [hidden email]
>
>
>
>
>

--
Frank W. Zammetti
Founder and Chief Software Architect
Omnytex Technologies
http://www.omnytex.com


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Digester wildcard question

Simon Kitching
On Mon, 2005-06-20 at 23:59 -0400, Frank W. Zammetti wrote:

> Good call, making the methods non-static solved the problem.
>
> I'm not sure how I feel about leaving it like that though, so I will go
> off and check out the other suggestions you offered... I actually just
> spent a few minutes playing with the CallMethodRule... I came up with
> what I thought was a clever and elegant solution, but it doesn't seem to
> work... I created the following class:
>
> public class MyRule extends CallMethodRule {
>    public MyRule() {
>      super("");
>    }
>    public void body(String bodyText) throws Exception {
>      String cem = digester.getCurrentElementName();
>      methodName = "set" + cem.substring(0, 1).toUpperCase() +
> cem.substring(1);
>      super.body(bodyText);
>    }
> }
>
> And I did:
>
> digester.addRule("config/?", new MyRule());
>
> ...in the calling code.  I got an exception that doesn't seem to make
> sense...
>
> SEVERE: End event threw exception
> java.lang.NoSuchMethodException: No such accessible method: setItem1()
> on object
> : jwp.sampleapp.SampleAppConfigBean
>
> I thought it might be the same kind of static vs. non-static thing, but
> I've tried the methods both static and non-static, made no difference.

I'm puzzled too. It's not terribly elegant but I don't see why it won't
work in your case.


>
> I'm about ready to "give up" and just leave the methods non-static...
> because the bean being populated stores static condfiguration
> information though, it would be nicer to not have to instantiate it to
> get at the config info.  But. it's not the end of the world or anything.
>   But if you have any ideas about why the above didn't work, maybe there
> is still a chance of not having to do that.
>

Rather than distort your design, I would suggest either:

(a)
Create your own Rule class based on the source for
BeanPropertySetterRule, but replacing all of the method lookup stuff
with a simple:
  Method m = Config.class.getMethod(xmlElementName);
  m.invoke(null, bodyText);
All your config methods seem to take strings so you don't need to worry
about type-conversion.

Digester is designed to allow custom rules to be written.

(b)
Turn your config object into a singleton. Instead of using
ObjectCreateRule, use FactoryCreateRule with a custom ObjectFactory
which simply returns the singleton object. It's still a change to your
design but not so radical.

> I will look at SetNestedPropertiesRule in a bit too, haven't got around
> to that yet.

It's probably only of theoretical interest in your case if you really
want static methods, as it does depend on BeanUtils.setProperty which
requires true bean properties and hence no static methods.

Regards,

Simon


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|

Re: Digester wildcard question

Frank W. Zammetti
Simon Kitching wrote:
> I'm puzzled too. It's not terribly elegant but I don't see why it won't
> work in your case.

What?!?  Not elegant?!??  Why you little... eh, maybe your right, never
mind ;)

> Rather than distort your design, I would suggest either:
>
> (a)
> Create your own Rule class based on the source for
> BeanPropertySetterRule, but replacing all of the method lookup stuff
> with a simple:
>   Method m = Config.class.getMethod(xmlElementName);
>   m.invoke(null, bodyText);
> All your config methods seem to take strings so you don't need to worry
> about type-conversion.

I thought of that too... ironically, talking about elegance, I thought
that was less elegant than extending an existing class that already did
99% of what I wanted :)

> (b)
> Turn your config object into a singleton. Instead of using
> ObjectCreateRule, use FactoryCreateRule with a custom ObjectFactory
> which simply returns the singleton object. It's still a change to your
> design but not so radical.

I think that will actually put a bit more restrictions on the bean than
I'd like, although I certainly see your point.

I think because of what I'm really trying to do, I'm simply going to
document the fact that the setters of the bean must be non-static.  The
getters can still be static, which is really the more important
consideration, and that's what I'll do for the sample bean I am doing.
In the end it will be a developers' choice, not mine, but as long as
they know this "limitation", minor as it is, I think that is OK.  It
could even be argued it isn't really a limitation anyway, just a point
to document, and I think that's where I'm coming down on it in the end.

Thanks for all your help Simon, it has been absolutely invaluable!  I've
learned more about Digester in the last few hours than in all the time
I've used it before :)

Frank


---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]