|
From: Drew D. <dr...@og...> - 2005-01-26 20:06:54
|
I wrote an OGNL-based property configurer (BeanFactoryPostProcessor
implementor) that evaluates OGNL expressions in <value>, <map> and
<list> entries, etc.
It is similar in spirit to the PropertyPlaceholderConfigurer in that is
substitutes values during the post processing phase. It looks for
values delimitted by :[ and ] and processes them as OGNL expressions,
which return a value.
Advantages of OGNL expressions in Spring configurations:
* Reference static members of classes directly (great for setting
values from "static final int" constants):
<property name="aConstValue"><value>:[
@org.ognl.SomeClass@CONSTANT_VALUE ]</value>
* Access Java data structures (and Collections) more easily than in
Spring. Here are some examples, but I can't do justice to the amount of
data structure manipulation possibilities in OGNL in this small space:
- Create a Map:
#{
"name": "MyName",
"description:" "This is a description of myName"
}
- Create a List of Strings:
{ "one", "two", "three" }
- Create a List, filtered for certain elements:
#someObjectInContext.items.{? name.startsWith("foo") }
* Call methods on any object reachable by the bean factory
* Reference and navigate other objects in the factory by name
Example from my commerce library of configuring a factory that produces
OrderQueryCriteria objects. These objects have setup requiring bindings
to helper objects (OrderStatus is a custom enumerated type, SortOrdering
is similar to Hibernate's Order class but slightly different usage
pattern). One way to do this is to change the objects to take
primitive-based setters and getters to construct these, or to complexify
the Spring configuration file to provide these objects through
factories, etc. I tried that and the config file was a bit heavy with
"noise" due to the number of custom classes I needed just to get to
static members, constructors, etc.
public class OrderQueryCriteria extends AbstractQueryCriteria
{
...
public List getOrderStatus()
{
return orderStatus;
}
public void setOrderStatus(List value)
{
orderStatus = value;
}
public SortOrdering getPrimaryOrdering()
{
return primaryOrdering;
}
public void setPrimaryOrdering(SortOrdering value)
{
primaryOrdering = value;
}
public int getPageSize()
{
return pageSize;
}
public void setPageSize(int value)
{
pageSize = value;
}
}
applicationContext-dao.xml:
<bean id="orderQueryCriteria"
class="org.ognl.dao.OrderQueryCriteria" singleton="false" autowire="byName">
<property name="orderStatus"><value>:[
@EnumeratedType@getFactory(@OrderStatus@class).instances.{ #this }
]</value></property>
<property name="primaryOrdering"><value>:[ new
SortOrdering('orderDate', @SortOrdering@DESCENDING) ]</value></property>
<property name="pageSize"><value>10</value></property>
</bean>
EnumeratedType is an abstraction for managing enumerated type objects.
It allows you to get lists of instances and manages indexing the
enumerations by other properties as well. Access is through static
factory that implements the getInstances() method.
orderStatus is a List of OrderStatus objects. To construct this we need
to get the List from the EnumeratedType's factory and get a copy of the
list that is returned (the copy is done by "projecting" the instances
list via { #this }, which creates a new List as a result).
primaryOrdering is a SortOrdering object that has an Object "target" and
an ordering value (ASCENDING, DESCENDING or NONE). Note here that we
are constructing this object using it's own static constant fields.
The above code is accessing SortOrdering, EnumeratedType, and
OrderStatus without any package specifications, you may note. This
works here because the processor is looking for beans of type
ognl.ClassResolver to use to resolve class names to actual Class
objects. I've written an ImportClassResolver that is configured thus:
<!-- id does not matter; only the fact that it exists in the context -->
<bean id="imports" class="org.ognl.spring.config.ImportClassResolver">
<property name="imports">
<list>
<value>org.ognl.util.*</value>
<value>org.ognl.model.shop.*</value>
<value>org.ognl.pager.*</value>
</list>
</property>
</bean>
You can define as many of these as you like and they do inherit.
Another feature is that the OGNL "context" object allows access to the
rest of the Spring applicationContext through OGNL's "context variable"
syntax.
In the above code you saw reference to "#this" - this is the implicit
value of the current object of the navigation; also available is #root
(the original root object of the expression). This syntax also is used
to access other elements in the context:
<!-- a ListFactory produces, as the result of the factory, a List
object -->
<bean id="itemSortProperties"
class="org.ognl.spring.config.ListFactory">
<property name="list">
<value>:[
{
#{ "name": "Item #",
"property": "itemNumber",
"advanced": false
},
#{ "name": "Name",
"property": "name",
"advanced": false
},
#{ "name": "Categorization",
"property": { "product.category.name",
"product.name" },
"advanced": false
},
}
]</value>
</property>
</bean>
<bean id="itemQueryCriteria" class="org.ognl.dao.ItemQueryCriteria"
singleton="false" autowire="byName">
...
<property name="primaryOrdering"><value>:[
new SortOrdering(#itemSortProperties.{? name ==
"Categorization" }[^].property, @SortOrdering@ASCENDING)
]</value></property>
<property name="secondaryOrdering"><value>:[
new SortOrdering(#itemSortProperties.{? name == "Item #"
}[^].property, @SortOrdering@ASCENDING)
]</value></property>
<property name="tertiaryOrdering"><value>:[
new SortOrdering(#itemSortProperties.{? name == "Name"
}[^].property, @SortOrdering@ASCENDING)
]</value></property>
<property name="pageSize"><value>10</value></property>
</bean>
The above configures a List of Map objects, each of which has a "name",
"property" and "advanced" key/value pair. The itemQueryCriteria object
is configured from this list by referencing the other bean via
"#itemSortProperties". Each of these uses the selection syntax to get a
specific item out of the list, then the dynamic subscript "[^]" to
return the first item of the list.
You can also navigate through other objects in the context for other
purposes such as mirroring another configuration value:
<bean id="myObject" class="...">
<property name="foo"><value>1254</value></property>
...
</bean>
<bean id="otherObject" class="...">
<property name="bar"><value>:[ #myObject.foo ]</value></property>
</bean>
This can help cut down on error where properties are used repetitively.
I'm not sure how I should distribute the source to this or if the
project takes contributions. The code has a dependency on OGNL 2.6.3
and above (1 jar file of ~185k). Is there a contribution mechanism that
I can use to put instructions for use, the jar and the source up somewhere?
- Drew
--
+---------------------------------+
< Drew Davidson | OGNL Technology >
+---------------------------------+
| Email: dr...@og... /
| Web: http://www.ognl.org /
| Vox: (520) 531-1966 <
| Fax: (520) 531-1965 \
| Mobile: (520) 405-2967 \
+---------------------------------+
|